Signed / unsigned fixes and function prototypes
[freeradius.git] / src / main / radmin.c
index d4661b8..d67f50d 100644 (file)
@@ -27,27 +27,52 @@ RCSID("$Id$")
 #include <freeradius-devel/radiusd.h>
 #include <freeradius-devel/radpaths.h>
 
-#ifdef HAVE_READLINE_READLINE_H
-#include <readline/readline.h>
-#include <readline/history.h>
-#endif
-
 #ifdef HAVE_SYS_SOCKET_H
 #include <sys/socket.h>
 #endif
 
 #ifdef HAVE_SYS_UN_H
 #include <sys/un.h>
+#ifndef SUN_LEN
+#define SUN_LEN(su)  (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+#endif
 #endif
 
 #ifdef HAVE_GETOPT_H
 #include <getopt.h>
 #endif
 
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+
+#ifdef HAVE_LIBREADLINE
+
+#if defined(HAVE_READLINE_READLINE_H)
+#include <readline/readline.h>
+#define USE_READLINE (1)
+#elif defined(HAVE_READLINE_H)
+#include <readline.h>
+#define USE_READLINE (1)
+#endif /* !defined(HAVE_READLINE_H) */
+
+#endif /* HAVE_LIBREADLINE */
+
+#ifdef HAVE_READLINE_HISTORY
+#if defined(HAVE_READLINE_HISTORY_H)
+#include <readline/history.h>
+#define USE_READLINE_HISTORY (1)
+#elif defined(HAVE_HISTORY_H)
+#include <history.h>
+#define USE_READLINE_HISTORY (1)
+#endif /* defined(HAVE_READLINE_HISTORY_H) */
+
+#endif /* HAVE_READLINE_HISTORY */
+
 /*
  *     For configuration file stuff.
  */
-const char *radius_dir = RADDBDIR;
+char *radius_dir = RADDBDIR;
 const char *progname = "radmin";
 
 /*
@@ -65,9 +90,13 @@ int radius_xlat(UNUSED char *out, UNUSED int outlen, UNUSED const char *fmt,
        return -1;
 }
 
+static FILE *outputfp = NULL;
+static int echo = FALSE;
+
 static int fr_domain_socket(const char *path)
 {
-        int sockfd;
+       int sockfd = -1;
+#ifdef HAVE_SYS_UN_H
        size_t len;
        socklen_t socklen;
         struct sockaddr_un saremote;
@@ -85,14 +114,26 @@ static int fr_domain_socket(const char *path)
         }
 
         saremote.sun_family = AF_UNIX;
-       memcpy(saremote.sun_path, path, len); /* not zero terminated */
+       memcpy(saremote.sun_path, path, len + 1); /* SUN_LEN does strlen */
        
-       socklen = sizeof(saremote.sun_family) + len;
+       socklen = SUN_LEN(&saremote);
 
         if (connect(sockfd, (struct sockaddr *)&saremote, socklen) < 0) {
+               struct stat buf;
+
+               close(sockfd);
                fprintf(stderr, "%s: Failed connecting to %s: %s\n",
                        progname, path, strerror(errno));
-               close(sockfd);
+
+               /*
+                *      The file doesn't exist.  Tell the user how to
+                *      fix it.
+                */
+               if ((stat(path, &buf) < 0) &&
+                   (errno == ENOENT)) {
+                       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");
+               }
+
                return -1;
         }
 
@@ -116,39 +157,144 @@ static int fr_domain_socket(const char *path)
                }
        }
 #endif
-
+#endif
        return sockfd;
 }
 
 static int usage(void)
 {
-       printf("Usage: %s [ -d raddb_dir ] [ -f socket_file ] [ -n name ] [ -q ]\n", progname);
+       printf("Usage: %s [ args ]\n", progname);
        printf("  -d raddb_dir    Configuration files are in \"raddbdir/*\".\n");
+       printf("  -e command      Execute 'command' and then exit.\n");
+       printf("  -E              Echo commands as they are being executed.\n");
        printf("  -f socket_file  Open socket_file directly, without reading radius.conf\n");
+       printf("  -i input_file   Read commands from 'input_file'.\n");
        printf("  -n name         Read raddb/name.conf instead of raddb/radiusd.conf\n");
+       printf("  -o output_file  Write commands to 'output_file'.\n");
        printf("  -q              Quiet mode.\n");
 
        exit(1);
 }
 
+static ssize_t run_command(int sockfd, const char *command,
+                          char *buffer, size_t bufsize)
+{
+       char *p;
+       ssize_t size, len;
+
+       if (echo) {
+               fprintf(outputfp, "%s\n", command);
+       }
+
+       /*
+        *      Write the text to the socket.
+        */
+       if (write(sockfd, command, strlen(command)) < 0) return -1;
+       if (write(sockfd, "\r\n", 2) < 0) return -1;
+
+       /*
+        *      Read the response
+        */
+       size = 0;
+       buffer[0] = '\0';
+
+       memset(buffer, 0, bufsize);
+
+       while (1) {
+               int rcode;
+               fd_set readfds;
+
+               FD_ZERO(&readfds);
+               FD_SET(sockfd, &readfds);
+
+               rcode = select(sockfd + 1, &readfds, NULL, NULL, NULL);
+               if (rcode < 0) {
+                       if (errno == EINTR) continue;
+
+                       fprintf(stderr, "%s: Failed selecting: %s\n",
+                               progname, strerror(errno));
+                       exit(1);
+               }
+
+#ifdef MSG_DONTWAIT
+               len = recv(sockfd, buffer + size,
+                          bufsize - size - 1, MSG_DONTWAIT);
+#else
+               /*
+                *      Read one byte at a time (ugh)
+                */
+               len = recv(sockfd, buffer + size, 1, 0);
+#endif
+               if (len < 0) {
+                       /*
+                        *      No data: keep looping
+                        */
+                       if ((errno == EAGAIN) || (errno == EINTR)) {
+                               continue;
+                       }
+
+                       fprintf(stderr, "%s: Error reading socket: %s\n",
+                               progname, strerror(errno));
+                       exit(1);
+               }
+               if (len == 0) return 0; /* clean exit */
+
+               size += len;
+               buffer[size] = '\0';
+
+               /*
+                *      There really is a better way of doing this.
+                */
+               p = strstr(buffer, "radmin> ");
+               if (p &&
+                   ((p == buffer) || 
+                    (p[-1] == '\n') ||
+                    (p[-1] == '\r'))) {
+                       *p = '\0';
+
+                       if (p[-1] == '\n') p[-1] = '\0';
+                       break;
+               }
+       }
+
+       /*
+        *      Blank prompt.  Go get another command.
+        */
+       if (!buffer[0]) return 1;
+
+       buffer[size] = '\0'; /* this is at least right */
+
+       return 2;
+}
+
+#define MAX_COMMANDS (4)
+
 int main(int argc, char **argv)
 {
        int argval, quiet = 0;
        int done_license = 0;
-       int sockfd, port;
+       int sockfd;
        uint32_t magic;
-       char *line;
+       char *line = NULL;
        ssize_t len, size;
        const char *file = NULL;
        const char *name = "radiusd";
-       char *p, buffer[2048];
+       char *p, buffer[65536];
+       const char *input_file = NULL;
+       FILE *inputfp = stdin;
+       const char *output_file = NULL;
+       
+       char *commands[MAX_COMMANDS];
+       int num_commands = -1;
+
+       outputfp = stdout;      /* stdout is not a constant value... */
 
        if ((progname = strrchr(argv[0], FR_DIR_SEP)) == NULL)
                progname = argv[0];
        else
                progname++;
 
-       while ((argval = getopt(argc, argv, "d:hf:n:q")) != EOF) {
+       while ((argval = getopt(argc, argv, "d:hi:e:Ef:n:o:q")) != EOF) {
                switch(argval) {
                case 'd':
                        if (file) {
@@ -158,6 +304,20 @@ int main(int argc, char **argv)
                        radius_dir = optarg;
                        break;
 
+               case 'e':
+                       num_commands++; /* starts at -1 */
+                       if (num_commands >= MAX_COMMANDS) {
+                               fprintf(stderr, "%s: Too many '-e'\n",
+                                       progname);
+                               exit(1);
+                       }
+                       commands[num_commands] = optarg;
+                       break;
+
+               case 'E':
+                       echo = TRUE;
+                       break;
+
                case 'f':
                        radius_dir = NULL;
                        file = optarg;
@@ -168,10 +328,24 @@ int main(int argc, char **argv)
                        usage();
                        break;
 
+               case 'i':
+                       if (strcmp(optarg, "-") != 0) {
+                               input_file = optarg;
+                       }
+                       quiet = 1;
+                       break;
+
                case 'n':
                        name = optarg;
                        break;
 
+               case 'o':
+                       if (strcmp(optarg, "-") != 0) {
+                               output_file = optarg;
+                       }
+                       quiet = 1;
+                       break;
+
                case 'q':
                        quiet = 1;
                        break;
@@ -232,11 +406,34 @@ int main(int argc, char **argv)
                }
        }
 
-       if (!isatty(STDIN_FILENO)) quiet = 1;
+       if (input_file) {
+               inputfp = fopen(input_file, "r");
+               if (!inputfp) {
+                       fprintf(stderr, "%s: Failed opening %s: %s\n",
+                               progname, input_file, strerror(errno));
+                       exit(1);
+               }
+       }
+
+       if (output_file) {
+               outputfp = fopen(output_file, "w");
+               if (!outputfp) {
+                       fprintf(stderr, "%s: Failed creating %s: %s\n",
+                               progname, output_file, strerror(errno));
+                       exit(1);
+               }
+       }
 
-#ifdef HAVE_READLINE_READLINE_H
+       /*
+        *      Check if stdin is a TTY only if input is from stdin
+        */
+       if (input_file && !quiet && !isatty(STDIN_FILENO)) quiet = 1;
+
+#ifdef USE_READLINE
        if (!quiet) {
+#ifdef USE_READLINE_HISTORY
                using_history();
+#endif
                rl_bind_key('\t', rl_insert);
        }
 #endif
@@ -277,6 +474,26 @@ int main(int argc, char **argv)
                exit(1);
        }       
 
+       /*
+        *      Run one command.
+        */
+       if (num_commands >= 0) {
+               int i;
+
+               for (i = 0; i <= num_commands; i++) {
+                       size = run_command(sockfd, commands[i],
+                                          buffer, sizeof(buffer));
+                       if (size < 0) exit(1);
+                       
+                       if (buffer[0]) {
+                               fputs(buffer, outputfp);
+                               fprintf(outputfp, "\n");
+                               fflush(outputfp);
+                       }
+               }
+               exit(0);
+       }
+
        if (!done_license && !quiet) {
                printf("radmin " RADIUSD_VERSION " - FreeRADIUS Server administration tool.\n");
                printf("Copyright (C) 2008 The FreeRADIUS server project and contributors.\n");
@@ -293,11 +510,13 @@ int main(int argc, char **argv)
         */
 
        while (1) {
+#ifndef USE_READLINE
                if (!quiet) {
-#ifndef HAVE_READLINE_READLINE_H
                        printf("radmin> ");
                        fflush(stdout);
+               }
 #else
+               if (!quiet) {
                        line = readline("radmin> ");
                        
                        if (!line) break;
@@ -307,13 +526,15 @@ int main(int argc, char **argv)
                                continue;
                        }
                        
+#ifdef USE_READLINE_HISTORY
                        add_history(line);
 #endif
-               } else {        /* quiet, or no readline */
-                       
-                       line = fgets(buffer, sizeof(buffer), stdin);
+               } else          /* quiet, or no readline */
+#endif
+               {
+                       line = fgets(buffer, sizeof(buffer), inputfp);
                        if (!line) break;
-                       
+
                        p = strchr(buffer, '\n');
                        if (!p) {
                                fprintf(stderr, "%s: Input line too long\n",
@@ -322,20 +543,49 @@ int main(int argc, char **argv)
                        }
                        
                        *p = '\0';
+
+                       /*
+                        *      Strip off leading spaces.
+                        */
+                       for (p = line; *p != '\0'; p++) {
+                               if ((p[0] == ' ') ||
+                                   (p[0] == '\t')) {
+                                       line = p + 1;
+                                       continue;
+                               }
+
+                               if (p[0] == '#') {
+                                       line = NULL;
+                                       break;
+                               }
+
+                               break;
+                       }
+
+                       /*
+                        *      Comments: keep going.
+                        */
+                       if (!line) continue;
+
+                       /*
+                        *      Strip off CR / LF
+                        */
+                       for (p = line; *p != '\0'; p++) {
+                               if ((p[0] == '\r') ||
+                                   (p[0] == '\n')) {
+                                       p[0] = '\0';
+                                       break;
+                               }
+                       }
                }
 
                if (strcmp(line, "reconnect") == 0) {
                        close(sockfd);
+                       line = NULL;
                        goto reconnect;
                }
 
                /*
-                *      Write the text to the socket.
-                */
-               if (write(sockfd, line, strlen(line)) < 0) break;
-               if (write(sockfd, "\r\n", 2) < 0) break;
-
-               /*
                 *      Exit, done, etc.
                 */
                if ((strcmp(line, "exit") == 0) ||
@@ -343,79 +593,17 @@ int main(int argc, char **argv)
                        break;
                }
 
-               /*
-                *      Read the response
-                */
-               size = 0;
-               buffer[0] = '\0';
-
-               port = 1;
-               memset(buffer, 0, sizeof(buffer));
-
-               while (port == 1) {
-                       int rcode;
-                       fd_set readfds;
-
-                       FD_ZERO(&readfds);
-                       FD_SET(sockfd, &readfds);
-
-                       rcode = select(sockfd + 1, &readfds, NULL, NULL, NULL);
-                       if (rcode < 0) {
-                               if (errno == EINTR) continue;
+               size = run_command(sockfd, line, buffer, sizeof(buffer));
+               if (size <= 0) break; /* error, or clean exit */
 
-                               fprintf(stderr, "%s: Failed selecting: %s\n",
-                                       progname, strerror(errno));
-                               exit(1);
-                       }
-
-                       len = recv(sockfd, buffer + size,
-                                  sizeof(buffer) - size - 1, MSG_DONTWAIT);
-                       if (len < 0) {
-                               /*
-                                *      No data: keep looping
-                                */
-                               if ((errno == EAGAIN) || (errno == EINTR)) {
-                                       continue;
-                               }
-
-                               fprintf(stderr, "%s: Error reading socket: %s\n",
-                                       progname, strerror(errno));
-                               exit(1);
-                       }
-                       if (len == 0) break; /* clean close of socket */
-
-                       size += len;
-                       buffer[size] = '\0';
-
-                       /*
-                        *      There really has to be a better way of
-                        *      doing this.
-                        */
-                       p = strstr(buffer, "radmin> ");
-                       if (p &&
-                           ((p == buffer) || 
-                            (p[-1] == '\n') ||
-                            (p[-1] == '\r'))) {
-                               *p = '\0';
-
-                               if (p[-1] == '\n') p[-1] = '\0';
-
-                               port = 0;
-                               break;
-                       }
-               }
-
-               /*
-                *      Blank prompt.  Go get another line.
-                */
-               if (!buffer[0]) continue;
+               if (size == 1) continue; /* no output. */
 
-               buffer[size] = '\0'; /* this is at least right */
-               puts(buffer);
-               fflush(stdout);
+               fputs(buffer, outputfp);
+               fflush(outputfp);
+               fprintf(outputfp, "\n");
        }
 
-       printf("\n");
+       fprintf(outputfp, "\n");
 
        return 0;
 }