Merge pull request #11 from amne/master
[freeradius.git] / src / main / radmin.c
1 /*
2  * radmin.c     RADIUS Administration tool.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2008   The FreeRADIUS server project
21  * Copyright 2008   Alan DeKok <aland@deployingradius.com>
22  */
23
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/radpaths.h>
29
30 #ifdef HAVE_SYS_SOCKET_H
31 #include <sys/socket.h>
32 #endif
33
34 #ifdef HAVE_SYS_UN_H
35 #include <sys/un.h>
36 #ifndef SUN_LEN
37 #define SUN_LEN(su)  (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
38 #endif
39 #endif
40
41 #ifdef HAVE_GETOPT_H
42 #include <getopt.h>
43 #endif
44
45 #ifdef HAVE_SYS_STAT_H
46 #include <sys/stat.h>
47 #endif
48
49 #ifdef HAVE_LIBREADLINE
50
51 #if defined(HAVE_READLINE_READLINE_H)
52 #include <readline/readline.h>
53 #define USE_READLINE (1)
54 #elif defined(HAVE_READLINE_H)
55 #include <readline.h>
56 #define USE_READLINE (1)
57 #endif /* !defined(HAVE_READLINE_H) */
58
59 #endif /* HAVE_LIBREADLINE */
60
61 #ifdef HAVE_READLINE_HISTORY
62 #if defined(HAVE_READLINE_HISTORY_H)
63 #include <readline/history.h>
64 #define USE_READLINE_HISTORY (1)
65 #elif defined(HAVE_HISTORY_H)
66 #include <history.h>
67 #define USE_READLINE_HISTORY (1)
68 #endif /* defined(HAVE_READLINE_HISTORY_H) */
69
70 #endif /* HAVE_READLINE_HISTORY */
71
72 /*
73  *      For configuration file stuff.
74  */
75 char *radius_dir = RADDBDIR;
76 const char *progname = "radmin";
77
78 /*
79  *      The rest of this is because the conffile.c, etc. assume
80  *      they're running inside of the server.  And we don't (yet)
81  *      have a "libfreeradius-server", or "libfreeradius-util".
82  */
83 int debug_flag = 0;
84 struct main_config_t mainconfig;
85 char *request_log_file = NULL;
86 char *debug_log_file = NULL;
87 int radius_xlat(UNUSED char *out, UNUSED int outlen, UNUSED const char *fmt,
88                 UNUSED REQUEST *request, UNUSED RADIUS_ESCAPE_STRING func)
89 {
90         return -1;
91 }
92 int check_config = FALSE;
93
94 static FILE *outputfp = NULL;
95 static int echo = FALSE;
96
97 static int fr_domain_socket(const char *path)
98 {
99         int sockfd = -1;
100 #ifdef HAVE_SYS_UN_H
101         size_t len;
102         socklen_t socklen;
103         struct sockaddr_un saremote;
104
105         len = strlen(path);
106         if (len >= sizeof(saremote.sun_path)) {
107                 fprintf(stderr, "%s: Path too long in filename\n", progname);
108                 return -1;
109         }
110
111         if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
112                 fprintf(stderr, "%s: Failed creating socket: %s\n",
113                         progname, strerror(errno));
114                 return -1;
115         }
116
117         saremote.sun_family = AF_UNIX;
118         memcpy(saremote.sun_path, path, len + 1); /* SUN_LEN does strlen */
119         
120         socklen = SUN_LEN(&saremote);
121
122         if (connect(sockfd, (struct sockaddr *)&saremote, socklen) < 0) {
123                 struct stat buf;
124
125                 close(sockfd);
126                 fprintf(stderr, "%s: Failed connecting to %s: %s\n",
127                         progname, path, strerror(errno));
128
129                 /*
130                  *      The file doesn't exist.  Tell the user how to
131                  *      fix it.
132                  */
133                 if ((stat(path, &buf) < 0) &&
134                     (errno == ENOENT)) {
135                         fprintf(stderr, "  Perhaps you need to run the commands:\n\tcd /etc/raddb\n\tln -s sites-available/control-socket sites-enabled/control-socket\n  and then re-start the server?\n");
136                 }
137
138                 return -1;
139         }
140
141 #ifdef O_NONBLOCK
142         {
143                 int flags;
144                 
145                 if ((flags = fcntl(sockfd, F_GETFL, NULL)) < 0)  {
146                         fprintf(stderr, "%s: Failure getting socket flags: %s",
147                                 progname, strerror(errno));
148                         close(sockfd);
149                         return -1;
150                 }
151                 
152                 flags |= O_NONBLOCK;
153                 if( fcntl(sockfd, F_SETFL, flags) < 0) {
154                         fprintf(stderr, "%s: Failure setting socket flags: %s",
155                                 progname, strerror(errno));
156                         close(sockfd);
157                         return -1;
158                 }
159         }
160 #endif
161 #endif
162         return sockfd;
163 }
164
165 static int usage(void)
166 {
167         printf("Usage: %s [ args ]\n", progname);
168         printf("  -d raddb_dir    Configuration files are in \"raddbdir/*\".\n");
169         printf("  -e command      Execute 'command' and then exit.\n");
170         printf("  -E              Echo commands as they are being executed.\n");
171         printf("  -f socket_file  Open socket_file directly, without reading radius.conf\n");
172         printf("  -h              Print usage help information.\n");
173         printf("  -i input_file   Read commands from 'input_file'.\n");
174         printf("  -n name         Read raddb/name.conf instead of raddb/radiusd.conf\n");
175         printf("  -o output_file  Write commands to 'output_file'.\n");
176         printf("  -q              Quiet mode.\n");
177
178         exit(1);
179 }
180
181 static ssize_t run_command(int sockfd, const char *command,
182                            char *buffer, size_t bufsize)
183 {
184         char *p;
185         ssize_t size, len;
186
187         if (echo) {
188                 fprintf(outputfp, "%s\n", command);
189         }
190
191         /*
192          *      Write the text to the socket.
193          */
194         if (write(sockfd, command, strlen(command)) < 0) return -1;
195         if (write(sockfd, "\r\n", 2) < 0) return -1;
196
197         /*
198          *      Read the response
199          */
200         size = 0;
201         buffer[0] = '\0';
202
203         memset(buffer, 0, bufsize);
204
205         while (1) {
206                 int rcode;
207                 fd_set readfds;
208
209                 FD_ZERO(&readfds);
210                 FD_SET(sockfd, &readfds);
211
212                 rcode = select(sockfd + 1, &readfds, NULL, NULL, NULL);
213                 if (rcode < 0) {
214                         if (errno == EINTR) continue;
215
216                         fprintf(stderr, "%s: Failed selecting: %s\n",
217                                 progname, strerror(errno));
218                         exit(1);
219                 }
220
221                 if (rcode == 0) {
222                         fprintf(stderr, "%s: Server closed the connection.\n",
223                                 progname);
224                         exit(1);
225                 }
226
227 #ifdef MSG_DONTWAIT
228                 len = recv(sockfd, buffer + size,
229                            bufsize - size - 1, MSG_DONTWAIT);
230 #else
231                 /*
232                  *      Read one byte at a time (ugh)
233                  */
234                 len = recv(sockfd, buffer + size, 1, 0);
235 #endif
236                 if (len < 0) {
237                         /*
238                          *      No data: keep looping
239                          */
240                         if ((errno == EAGAIN) || (errno == EINTR)) {
241                                 continue;
242                         }
243
244                         fprintf(stderr, "%s: Error reading socket: %s\n",
245                                 progname, strerror(errno));
246                         exit(1);
247                 }
248                 if (len == 0) return 0; /* clean exit */
249
250                 size += len;
251                 buffer[size] = '\0';
252
253                 /*
254                  *      There really is a better way of doing this.
255                  */
256                 p = strstr(buffer, "radmin> ");
257                 if (p &&
258                     ((p == buffer) || 
259                      (p[-1] == '\n') ||
260                      (p[-1] == '\r'))) {
261                         *p = '\0';
262
263                         if (p[-1] == '\n') p[-1] = '\0';
264                         break;
265                 }
266         }
267
268         /*
269          *      Blank prompt.  Go get another command.
270          */
271         if (!buffer[0]) return 1;
272
273         buffer[size] = '\0'; /* this is at least right */
274
275         return 2;
276 }
277
278 #define MAX_COMMANDS (4)
279
280 int main(int argc, char **argv)
281 {
282         int argval, quiet = 0;
283         int done_license = 0;
284         int sockfd;
285         uint32_t magic;
286         char *line = NULL;
287         ssize_t len, size;
288         const char *file = NULL;
289         const char *name = "radiusd";
290         char *p, buffer[65536];
291         const char *input_file = NULL;
292         FILE *inputfp = stdin;
293         const char *output_file = NULL;
294         
295         char *commands[MAX_COMMANDS];
296         int num_commands = -1;
297
298         outputfp = stdout;      /* stdout is not a constant value... */
299
300         if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL)
301                 progname = argv[0];
302         else
303                 progname++;
304
305         while ((argval = getopt(argc, argv, "d:hi:e:Ef:n:o:q")) != EOF) {
306                 switch(argval) {
307                 case 'd':
308                         if (file) {
309                                 fprintf(stderr, "%s: -d and -f cannot be used together.\n", progname);
310                                 exit(1);
311                         }
312                         radius_dir = optarg;
313                         break;
314
315                 case 'e':
316                         num_commands++; /* starts at -1 */
317                         if (num_commands >= MAX_COMMANDS) {
318                                 fprintf(stderr, "%s: Too many '-e'\n",
319                                         progname);
320                                 exit(1);
321                         }
322                         commands[num_commands] = optarg;
323                         break;
324
325                 case 'E':
326                         echo = TRUE;
327                         break;
328
329                 case 'f':
330                         radius_dir = NULL;
331                         file = optarg;
332                         break;
333
334                 default:
335                 case 'h':
336                         usage();
337                         break;
338
339                 case 'i':
340                         if (strcmp(optarg, "-") != 0) {
341                                 input_file = optarg;
342                         }
343                         quiet = 1;
344                         break;
345
346                 case 'n':
347                         name = optarg;
348                         break;
349
350                 case 'o':
351                         if (strcmp(optarg, "-") != 0) {
352                                 output_file = optarg;
353                         }
354                         quiet = 1;
355                         break;
356
357                 case 'q':
358                         quiet = 1;
359                         break;
360                 }
361         }
362
363         if (radius_dir) {
364                 int rcode;
365                 CONF_SECTION *cs, *subcs;
366
367                 file = NULL;    /* MUST read it from the conffile now */
368
369                 snprintf(buffer, sizeof(buffer), "%s/%s.conf",
370                          radius_dir, name);
371
372                 cs = cf_file_read(buffer);
373                 if (!cs) {
374                         fprintf(stderr, "%s: Errors reading %s\n",
375                                 progname, buffer);
376                         exit(1);
377                 }
378
379                 subcs = NULL;
380                 while ((subcs = cf_subsection_find_next(cs, subcs, "listen")) != NULL) {
381                         const char *value;
382                         CONF_PAIR *cp = cf_pair_find(subcs, "type");
383                         
384                         if (!cp) continue;
385
386                         value = cf_pair_value(cp);
387                         if (!value) continue;
388
389                         if (strcmp(value, "control") != 0) continue;
390
391                         /*
392                          *      Now find the socket name (sigh)
393                          */
394                         rcode = cf_item_parse(subcs, "socket",
395                                               PW_TYPE_STRING_PTR,
396                                               &file, NULL);
397                         if (rcode < 0) {
398                                 fprintf(stderr, "%s: Failed parsing listen section\n", progname);
399                                 exit(1);
400                         }
401
402                         if (!file) {
403                                 fprintf(stderr, "%s: No path given for socket\n",
404                                         progname);
405                                 exit(1);
406                         }
407                         break;
408                 }
409
410                 if (!file) {
411                         fprintf(stderr, "%s: Could not find control socket in %s\n",
412                                 progname, buffer);
413                         exit(1);
414                 }
415         }
416
417         if (input_file) {
418                 inputfp = fopen(input_file, "r");
419                 if (!inputfp) {
420                         fprintf(stderr, "%s: Failed opening %s: %s\n",
421                                 progname, input_file, strerror(errno));
422                         exit(1);
423                 }
424         }
425
426         if (output_file) {
427                 outputfp = fopen(output_file, "w");
428                 if (!outputfp) {
429                         fprintf(stderr, "%s: Failed creating %s: %s\n",
430                                 progname, output_file, strerror(errno));
431                         exit(1);
432                 }
433         }
434
435         /*
436          *      Check if stdin is a TTY only if input is from stdin
437          */
438         if (input_file && !quiet && !isatty(STDIN_FILENO)) quiet = 1;
439
440 #ifdef USE_READLINE
441         if (!quiet) {
442 #ifdef USE_READLINE_HISTORY
443                 using_history();
444 #endif
445                 rl_bind_key('\t', rl_insert);
446         }
447 #endif
448
449  reconnect:
450         /*
451          *      FIXME: Get destination from command line, if possible?
452          */
453         sockfd = fr_domain_socket(file);
454         if (sockfd < 0) {
455                 exit(1);
456         }
457
458         /*
459          *      Read initial magic && version information.
460          */
461         for (size = 0; size < 8; size += len) {
462                 len = read(sockfd, buffer + size, 8 - size);
463                 if (len < 0) {
464                         fprintf(stderr, "%s: Error reading initial data from socket: %s\n",
465                                 progname, strerror(errno));
466                         exit(1);
467                 }
468         }
469
470         memcpy(&magic, buffer, 4);
471         magic = ntohl(magic);
472         if (magic != 0xf7eead15) {
473                 fprintf(stderr, "%s: Socket %s is not FreeRADIUS administration socket\n", progname, file);
474                 exit(1);
475         }
476         
477         memcpy(&magic, buffer + 4, 4);
478         magic = ntohl(magic);
479         if (magic != 1) {
480                 fprintf(stderr, "%s: Socket version mismatch: Need 1, got %d\n",
481                         progname, magic);
482                 exit(1);
483         }       
484
485         /*
486          *      Run one command.
487          */
488         if (num_commands >= 0) {
489                 int i;
490
491                 for (i = 0; i <= num_commands; i++) {
492                         size = run_command(sockfd, commands[i],
493                                            buffer, sizeof(buffer));
494                         if (size < 0) exit(1);
495                         
496                         if (buffer[0]) {
497                                 fputs(buffer, outputfp);
498                                 fprintf(outputfp, "\n");
499                                 fflush(outputfp);
500                         }
501                 }
502                 exit(0);
503         }
504
505         if (!done_license && !quiet) {
506                 printf("radmin " RADIUSD_VERSION " - FreeRADIUS Server administration tool.\n");
507                 printf("Copyright (C) 2008-2011 The FreeRADIUS server project and contributors.\n");
508                 printf("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n");
509                 printf("PARTICULAR PURPOSE.\n");
510                 printf("You may redistribute copies of FreeRADIUS under the terms of the\n");
511                 printf("GNU General Public License v2.\n");
512
513                 done_license = 1;
514         }
515
516         /*
517          *      FIXME: Do login?
518          */
519
520         while (1) {
521 #ifndef USE_READLINE
522                 if (!quiet) {
523                         printf("radmin> ");
524                         fflush(stdout);
525                 }
526 #else
527                 if (!quiet) {
528                         line = readline("radmin> ");
529                         
530                         if (!line) break;
531                         
532                         if (!*line) {
533                                 free(line);
534                                 continue;
535                         }
536                         
537 #ifdef USE_READLINE_HISTORY
538                         add_history(line);
539 #endif
540                 } else          /* quiet, or no readline */
541 #endif
542                 {
543                         line = fgets(buffer, sizeof(buffer), inputfp);
544                         if (!line) break;
545
546                         p = strchr(buffer, '\n');
547                         if (!p) {
548                                 fprintf(stderr, "%s: Input line too long\n",
549                                         progname);
550                                 exit(1);
551                         }
552                         
553                         *p = '\0';
554
555                         /*
556                          *      Strip off leading spaces.
557                          */
558                         for (p = line; *p != '\0'; p++) {
559                                 if ((p[0] == ' ') ||
560                                     (p[0] == '\t')) {
561                                         line = p + 1;
562                                         continue;
563                                 }
564
565                                 if (p[0] == '#') {
566                                         line = NULL;
567                                         break;
568                                 }
569
570                                 break;
571                         }
572
573                         /*
574                          *      Comments: keep going.
575                          */
576                         if (!line) continue;
577
578                         /*
579                          *      Strip off CR / LF
580                          */
581                         for (p = line; *p != '\0'; p++) {
582                                 if ((p[0] == '\r') ||
583                                     (p[0] == '\n')) {
584                                         p[0] = '\0';
585                                         break;
586                                 }
587                         }
588                 }
589
590                 if (strcmp(line, "reconnect") == 0) {
591                         close(sockfd);
592                         line = NULL;
593                         goto reconnect;
594                 }
595
596                 /*
597                  *      Exit, done, etc.
598                  */
599                 if ((strcmp(line, "exit") == 0) ||
600                     (strcmp(line, "quit") == 0)) {
601                         break;
602                 }
603
604                 size = run_command(sockfd, line, buffer, sizeof(buffer));
605                 if (size <= 0) break; /* error, or clean exit */
606
607                 if (size == 1) continue; /* no output. */
608
609                 fputs(buffer, outputfp);
610                 fflush(outputfp);
611                 fprintf(outputfp, "\n");
612         }
613
614         fprintf(outputfp, "\n");
615
616         return 0;
617 }
618