]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Absolutely horrible hacks to get radmin on a TCP socket.
authorAlan T. DeKok <aland@freeradius.org>
Mon, 30 Apr 2012 14:24:40 +0000 (16:24 +0200)
committerAlan T. DeKok <aland@freeradius.org>
Mon, 30 Apr 2012 15:00:10 +0000 (17:00 +0200)
We're pulling the rug out from under the code multiple times,
and casting pointers of type "foo" to type "bar" repeatedly.
Trust us, we know what we're doing!

radmin now requires a CHAP-style challenge/response when using
TCP sockets.  It's crap, but it's better than nothing

src/include/radiusd.h
src/main/command.c
src/main/listen.c
src/main/radmin.c

index 69f9bd6c8fccb35f854400692c99c61c51d5fff7..ea1530dea5ba425fa0d1be0a1f7b07cb67a895d0 100644 (file)
@@ -122,6 +122,10 @@ typedef struct auth_req REQUEST;
 #include <freeradius-devel/stats.h>
 #include <freeradius-devel/realms.h>
 
+#ifdef WITH_COMMAND_SOCKET
+#define PW_RADMIN_PORT 18120
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
index 34c7b03b3f9ce22696a6433ef4c7780b53c14810..cab3b711de9b186f8f524a8a769a6e8a93d7e6f1 100644 (file)
@@ -68,12 +68,21 @@ struct fr_command_table_t {
 
 #define COMMAND_BUFFER_SIZE (1024)
 
+typedef struct fr_cs_buffer_t {
+       int     auth;
+       int     mode;
+       ssize_t offset;
+       ssize_t next;
+       char buffer[COMMAND_BUFFER_SIZE];
+} fr_cs_buffer_t;
+
+#define COMMAND_SOCKET_MAGIC (0xffdeadee)
 typedef struct fr_command_socket_t {
+       uint32_t magic;
        char    *path;
        char    *copy;          /* <sigh> */
        uid_t   uid;
        gid_t   gid;
-       int     mode;
        char    *uid_name;
        char    *gid_name;
        char    *mode_name;
@@ -89,12 +98,7 @@ typedef struct fr_command_socket_t {
        rad_listen_t    *inject_listener;
        RADCLIENT       *inject_client;
 
-       /*
-        *      The next few entries do buffer management.
-        */
-       ssize_t offset;
-       ssize_t next;
-       char buffer[COMMAND_BUFFER_SIZE];
+       fr_cs_buffer_t  co;
 } fr_command_socket_t;
 
 static const CONF_PARSER command_config[] = {
@@ -2007,12 +2011,19 @@ static fr_command_table_t command_table[] = {
 
 static void command_socket_free(rad_listen_t *this)
 {
-       fr_command_socket_t *sock = this->data;
+       fr_command_socket_t *cmd = this->data;
 
-       if (!sock->copy) return;
-       unlink(sock->copy);
-       free(sock->copy);
-       sock->copy = NULL;
+       if (cmd->magic != COMMAND_SOCKET_MAGIC) {
+               listen_socket_t *sock = this->data;
+
+               free(sock->packet);
+               return;
+       }
+
+       if (!cmd->copy) return;
+       unlink(cmd->copy);
+       free(cmd->copy);
+       cmd->copy = NULL;
 }
 
 
@@ -2021,7 +2032,7 @@ static void command_socket_free(rad_listen_t *this)
  *
  *     FIXME: TCP + SSL, after RadSec is in.
  */
-static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
+static int command_socket_parse_unix(CONF_SECTION *cs, rad_listen_t *this)
 {
        fr_command_socket_t *sock;
 
@@ -2033,6 +2044,7 @@ static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
                return -1;
        }
 
+       sock->magic = COMMAND_SOCKET_MAGIC;
        sock->copy = NULL;
        if (sock->path) sock->copy = strdup(sock->path);
 
@@ -2076,10 +2088,10 @@ static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
 #endif
 
        if (!sock->mode_name) {
-               sock->mode = FR_READ;
+               sock->co.mode = FR_READ;
        } else {
-               sock->mode = fr_str2int(mode_names, sock->mode_name, 0);
-               if (!sock->mode) {
+               sock->co.mode = fr_str2int(mode_names, sock->mode_name, 0);
+               if (!sock->co.mode) {
                        radlog(L_ERR, "Invalid mode name \"%s\"",
                               sock->mode_name);
                        return -1;
@@ -2116,10 +2128,40 @@ static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
        return 0;
 }
 
+static int command_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
+{
+       int rcode;
+       const CONF_PAIR *cp;
+       listen_socket_t *sock;
+
+       cp = cf_pair_find(cs, "socket");
+       if (cp) return command_socket_parse_unix(cs, this);
+
+       rcode = common_socket_parse(cs, this);
+       if (rcode < 0) return -1;
+
+       if (this->tls) {
+               cf_log_err(cf_sectiontoitem(cs),
+                          "TLS is not supported for control sockets");
+               return -1;
+       }
+
+       sock = this->data;
+       if (sock->proto != IPPROTO_TCP) {
+               cf_log_err(cf_sectiontoitem(cs),
+                          "UDP is not supported for control sockets");
+               return -1;
+       }
+}
+
 static int command_socket_print(const rad_listen_t *this, char *buffer, size_t bufsize)
 {
        fr_command_socket_t *sock = this->data;
 
+       if (sock->magic != COMMAND_SOCKET_MAGIC) {
+               return socket_print(this, buffer, bufsize);
+       }
+
        snprintf(buffer, bufsize, "command file %s", sock->path);
        return 1;
 }
@@ -2216,14 +2258,13 @@ static void print_help(rad_listen_t *listener,
  *     It takes packets, not requests.  It sees if the packet looks
  *     OK.  If so, it does a number of sanity checks on it.
  */
-static int command_domain_recv(rad_listen_t *listener)
+static int command_domain_recv_co(rad_listen_t *listener, fr_cs_buffer_t *co)
 {
        int i, rcode;
        ssize_t len;
        int argc;
        char *my_argv[MAX_ARGV], **argv;
        fr_command_table_t *table;
-       fr_command_socket_t *co = listener->data;
 
        do {
                ssize_t c;
@@ -2318,32 +2359,6 @@ static int command_domain_recv(rad_listen_t *listener)
        if ((strcmp(argv[0], "exit") == 0) ||
            (strcmp(argv[0], "quit") == 0)) goto close_socket;
 
-#if 0
-       if (!co->user[0]) {
-               if (strcmp(argv[0], "login") != 0) {
-                       cprintf(listener, "ERROR: Login required\n");
-                       goto do_next;
-               }
-
-               if (argc < 3) {
-                       cprintf(listener, "ERROR: login <user> <password>\n");
-                       goto do_next;
-               }
-
-               /*
-                *      FIXME: Generate && process fake RADIUS request.
-                */
-               if ((strcmp(argv[1], "root") == 0) &&
-                   (strcmp(argv[2], "password") == 0)) {
-                       strlcpy(co->user, argv[1], sizeof(co->user));
-                       goto do_next;
-               }
-
-               cprintf(listener, "ERROR: Login incorrect\n");
-               goto do_next;
-       }
-#endif
-
        table = command_table;
  retry:
        len = 0;
@@ -2427,10 +2442,130 @@ static int command_domain_recv(rad_listen_t *listener)
 }
 
 
+       /*
+        *      Write 32-bit magic number && version information.
+        */
+static int command_write_magic(int newfd, listen_socket_t *sock)
+{
+       uint32_t magic;
+
+       magic = htonl(0xf7eead15);
+       if (write(newfd, &magic, 4) < 0) {
+               radlog(L_ERR, "Failed writing initial data to socket: %s",
+                      strerror(errno));
+               return -1;
+       }
+       
+       if (sock) {
+               magic = htonl(2);       /* protocol version */
+       } else {
+               magic = htonl(1);
+       }
+       if (write(newfd, &magic, 4) < 0) {
+               radlog(L_ERR, "Failed writing initial data to socket: %s",
+                      strerror(errno));
+               return -1;
+       }
+
+       /*
+        *      Write an initial challenge
+        */
+       if (sock) {
+               int i;
+               fr_cs_buffer_t *co;
+
+               co = rad_malloc(sizeof(*co));
+               memset(co, 0, sizeof(*co));
+
+               sock->packet = (void *) co;
+
+               for (i = 0; i < 16; i++) {
+                       co->buffer[i] = fr_rand();
+               }
+
+               /*
+                *      FIXME: EINTR, etc.
+                */
+               write(newfd, co->buffer, 16);
+       }
+
+       return 0;
+}
+
+
+static int command_tcp_recv(rad_listen_t *this)
+{
+       listen_socket_t *sock = this->data;
+       fr_cs_buffer_t *co = (void *) sock->packet;
+
+       rad_assert(co != NULL);
+
+       if (!co->auth) {
+               uint8_t expected[16];
+
+               /*
+                *      No response yet: keep reading it.
+                */
+               if (co->offset < 16) {
+                       ssize_t r;
+                       
+                       r = read(this->fd,
+                                co->buffer + 16 + co->offset, 16 - co->offset);
+                       if (r == 0) {
+                       close_socket:
+                               command_close_socket(this);
+                               return 0;
+                       }
+
+                       if (r < 0) {
+#ifdef ECONNRESET
+                               if (errno == ECONNRESET) goto close_socket;
+#endif
+                               if (errno == EINTR) return 0;
+                               
+                               radlog(L_ERR, "Failed reading from control socket; %s",
+                                      strerror(errno));
+                               goto close_socket;
+                       }
+
+                       co->offset += r;
+
+                       if (co->offset < 16) return 0;
+               }
+
+               fr_hmac_md5(sock->client->secret, strlen(sock->client->secret),
+                           co->buffer, 16, expected);
+
+               if (rad_digest_cmp(expected, co->buffer + 16, 16 != 0)) {
+                       radlog(L_ERR, "radmin failed challenge: Closing socket");
+                       goto close_socket;
+               }
+
+               co->auth = TRUE;
+               co->offset = 0;
+       }
+
+       return command_domain_recv_co(this, co);
+}
+
+/*
+ *     Should never be called.  The functions should just call write().
+ */
+static int command_tcp_send(UNUSED rad_listen_t *listener, UNUSED REQUEST *request)
+{
+       return 0;
+}
+
+static int command_domain_recv(rad_listen_t *listener)
+{
+       fr_command_socket_t *sock = listener->data;
+
+       return command_domain_recv_co(listener, &sock->co);
+}
+
 static int command_domain_accept(rad_listen_t *listener)
 {
        int newfd;
-       uint32_t magic;
        rad_listen_t *this;
        socklen_t salen;
        struct sockaddr_storage src;
@@ -2498,25 +2633,11 @@ static int command_domain_accept(rad_listen_t *listener)
         }
 #endif
 
-       /*
-        *      Write 32-bit magic number && version information.
-        */
-       magic = htonl(0xf7eead15);
-       if (write(newfd, &magic, 4) < 0) {
-               radlog(L_ERR, "Failed writing initial data to socket: %s",
-                      strerror(errno));
-               close(newfd);
-               return 0;
-       }
-       magic = htonl(1);       /* protocol version */
-       if (write(newfd, &magic, 4) < 0) {
-               radlog(L_ERR, "Failed writing initial data to socket: %s",
-                      strerror(errno));
+       if (command_write_magic(newfd, NULL) < 0) {
                close(newfd);
                return 0;
        }
 
-
        /*
         *      Add the new listener.
         */
@@ -2533,10 +2654,10 @@ static int command_domain_accept(rad_listen_t *listener)
        this->next = NULL;
        this->data = sock;      /* fix it back */
 
-       sock->offset = 0;
        sock->user[0] = '\0';
        sock->path = ((fr_command_socket_t *) listener->data)->path;
-       sock->mode = ((fr_command_socket_t *) listener->data)->mode;
+       sock->co.offset = 0;
+       sock->co.mode = ((fr_command_socket_t *) listener->data)->co.mode;
 
        this->fd = newfd;
        this->recv = command_domain_recv;
index fd9d902e92983684fc899be8b13a8927b2e0e57d..cb774d5d1fc49d3e9f8895662912f2c460875037 100644 (file)
@@ -82,6 +82,12 @@ typedef struct rad_listen_master_t {
 
 static rad_listen_t *listen_alloc(RAD_LISTEN_TYPE type);
 
+#ifdef WITH_COMMAND_SOCKET
+static int command_tcp_recv(rad_listen_t *listener);
+static int command_tcp_send(rad_listen_t *listener, REQUEST *request);
+static int command_write_magic(int newfd, listen_socket_t *sock);
+#endif
+
 /*
  *     Xlat for %{listen:foo}
  */
@@ -656,14 +662,26 @@ static int dual_tcp_accept(rad_listen_t *listener)
 
        this->fd = newfd;
        this->status = RAD_LISTEN_STATUS_INIT;
-       this->recv = dual_tcp_recv;
 
+
+#ifdef WITH_COMMAND_SOCKET
+       if (this->type == RAD_LISTEN_COMMAND) {
+               this->recv = command_tcp_recv;
+               this->send = command_tcp_send;
+               command_write_magic(this->fd, sock);
+       } else
+#endif
+       {
+
+               this->recv = dual_tcp_recv;
+               
 #ifdef WITH_TLS
-       if (this->tls) {
-               this->recv = dual_tls_recv;
+               if (this->tls) {
+                       this->recv = dual_tls_recv;
                this->send = dual_tls_send;
-       }
+               }
 #endif
+       }
 
        /*
         *      FIXME: set O_NONBLOCK on the accept'd fd.
@@ -731,6 +749,12 @@ static int socket_print(const rad_listen_t *this, char *buffer, size_t bufsize)
                break;
 #endif
 
+#ifdef WITH_COMMAND_SOCKET
+       case RAD_LISTEN_COMMAND:
+               name = "control";
+               break;
+#endif
+
        default:
                name = "??";
                break;
@@ -953,9 +977,18 @@ static int common_socket_parse(CONF_SECTION *cs, rad_listen_t *this)
 
                } else if (strcmp(proto, "tcp") == 0) {
                        sock->proto = IPPROTO_TCP;
-
-                       rcode = cf_section_parse(cf_section_sub_find(cs, "limit"), sock, limit_config);
-                       if (rcode < 0) return -1;
+                       CONF_SECTION *limit;
+                       
+                       limit = cf_section_sub_find(cs, "limit");
+                       if (limit) {
+                               rcode = cf_section_parse(limit, sock,
+                                                        limit_config);
+                               if (rcode < 0) return -1;
+                       } else {
+                               sock->limit.max_connections = 60;
+                               sock->limit.idle_timeout = 30;
+                               sock->limit.lifetime = 0;
+                       }
 
                        if ((sock->limit.idle_timeout > 0) && (sock->limit.idle_timeout < 5))
                                sock->limit.idle_timeout = 5;
@@ -2073,6 +2106,12 @@ static int listen_bind(rad_listen_t *this)
                        break;
 #endif
 
+#ifdef WITH_COMMAND_SOCKET
+               case RAD_LISTEN_COMMAND:
+                       sock->my_port = PW_RADMIN_PORT;
+                       break;
+#endif
+
 #ifdef WITH_COA
                case RAD_LISTEN_COA:
                        svp = getservbyname ("radius-dynauth", "udp");
index 1cef681686b4e5dce02bad8d72bb6d3f5544dfa7..b4f6da106482038fe9c23664544bf2701ab9fc32 100644 (file)
@@ -31,6 +31,10 @@ RCSID("$Id$")
 #include <sys/socket.h>
 #endif
 
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#endif
+
 #ifdef HAVE_SYS_UN_H
 #include <sys/un.h>
 #ifndef SUN_LEN
@@ -93,6 +97,7 @@ int check_config = FALSE;
 
 static FILE *outputfp = NULL;
 static int echo = FALSE;
+static const char *secret = "testing123";
 
 static int fr_domain_socket(const char *path)
 {
@@ -162,6 +167,72 @@ static int fr_domain_socket(const char *path)
        return sockfd;
 }
 
+static int client_socket(const char *server)
+{
+       int sockfd, port;
+       fr_ipaddr_t ipaddr;
+       char *p, buffer[1024];
+
+       strlcpy(buffer, server, sizeof(buffer));
+
+       p = strchr(buffer, ':');
+       if (!p) {
+               port = PW_RADMIN_PORT;
+       } else {
+               port = atoi(p + 1);
+               *p = '\0';
+       }
+
+       if (ip_hton(buffer, AF_INET, &ipaddr) < 0) {
+               fprintf(stderr, "%s: Failed looking up host %s: %s\n",
+                       progname, buffer, strerror(errno));
+               exit(1);
+       }
+
+       sockfd = fr_tcp_client_socket(NULL, &ipaddr, port);
+       if (sockfd < 0) {
+               fprintf(stderr, "%s: Failed opening socket %s: %s\n",
+                       progname, server, strerror(errno));
+               exit(1);
+       }
+       
+       return sockfd;
+}
+
+static void do_challenge(int sockfd)
+{
+       ssize_t total, r;
+       uint8_t challenge[16];
+
+       for (total = 0; total < sizeof(challenge); ) {
+               r = read(sockfd, challenge + total, sizeof(challenge) - total);
+               if (r == 0) exit(1);
+               
+               if (r < 0) {
+#ifdef ECONNRESET
+                       if (errno == ECONNRESET) {
+                               fprintf(stderr, "%s: Connection reset",
+                                       progname);
+                               exit(1);
+                       }
+#endif
+                       if (errno == EINTR) continue;
+                       
+                       fprintf(stderr, "%s: Failed reading data: %s\n",
+                               progname, strerror(errno));
+                       exit(1);
+               }
+               total += r;
+               fflush(stdout);
+       }
+
+       fr_hmac_md5(secret, strlen(secret), challenge, sizeof(challenge),
+                   challenge);
+
+       write(sockfd, challenge, sizeof(challenge));
+}
+
+
 static int usage(void)
 {
        printf("Usage: %s [ args ]\n", progname);
@@ -282,7 +353,7 @@ int main(int argc, char **argv)
        int argval, quiet = 0;
        int done_license = 0;
        int sockfd;
-       uint32_t magic;
+       uint32_t magic, needed;
        char *line = NULL;
        ssize_t len, size;
        const char *file = NULL;
@@ -291,6 +362,7 @@ int main(int argc, char **argv)
        const char *input_file = NULL;
        FILE *inputfp = stdin;
        const char *output_file = NULL;
+       const char *server = NULL;
        
        char *commands[MAX_COMMANDS];
        int num_commands = -1;
@@ -302,13 +374,17 @@ int main(int argc, char **argv)
        else
                progname++;
 
-       while ((argval = getopt(argc, argv, "d:hi:e:Ef:n:o:q")) != EOF) {
+       while ((argval = getopt(argc, argv, "d:hi:e:Ef:n:o:qs:S")) != EOF) {
                switch(argval) {
                case 'd':
                        if (file) {
                                fprintf(stderr, "%s: -d and -f cannot be used together.\n", progname);
                                exit(1);
                        }
+                       if (server) {
+                               fprintf(stderr, "%s: -d and -s cannot be used together.\n", progname);
+                               exit(1);
+                       }
                        radius_dir = optarg;
                        break;
 
@@ -357,6 +433,19 @@ int main(int argc, char **argv)
                case 'q':
                        quiet = 1;
                        break;
+
+               case 's':
+                       if (file) {
+                               fprintf(stderr, "%s: -s and -f cannot be used together.\n", progname);
+                               exit(1);
+                       }
+                       radius_dir = NULL;
+                       server = optarg;
+                       break;
+
+               case 'S':
+                       secret = NULL;
+                       break;
                }
        }
 
@@ -447,12 +536,16 @@ int main(int argc, char **argv)
 #endif
 
  reconnect:
-       /*
-        *      FIXME: Get destination from command line, if possible?
-        */
-       sockfd = fr_domain_socket(file);
-       if (sockfd < 0) {
-               exit(1);
+       if (file) {
+               /*
+                *      FIXME: Get destination from command line, if possible?
+                */
+               sockfd = fr_domain_socket(file);
+               if (sockfd < 0) {
+                       exit(1);
+               }
+       } else {
+               sockfd = client_socket(server);
        }
 
        /*
@@ -476,11 +569,20 @@ int main(int argc, char **argv)
        
        memcpy(&magic, buffer + 4, 4);
        magic = ntohl(magic);
-       if (magic != 1) {
-               fprintf(stderr, "%s: Socket version mismatch: Need 1, got %d\n",
-                       progname, magic);
+
+       if (!server) {
+               needed = 1;
+       } else {
+               needed = 2;
+       }
+
+       if (magic != needed) {
+               fprintf(stderr, "%s: Socket version mismatch: Need %d, got %d\n",
+                       progname, needed, magic);
                exit(1);
-       }       
+       }
+
+       if (server && secret) do_challenge(sockfd);
 
        /*
         *      Run one command.
@@ -593,6 +695,15 @@ int main(int argc, char **argv)
                        goto reconnect;
                }
 
+               if (memcmp(line, "secret ", 7) == 0) {
+                       if (!secret) {
+                               secret = line + 7;
+                               do_challenge(sockfd);
+                       }
+                       line = NULL;
+                       continue;
+               }
+
                /*
                 *      Exit, done, etc.
                 */
@@ -601,6 +712,12 @@ int main(int argc, char **argv)
                        break;
                }
 
+               if (server && !secret) {
+                       fprintf(stderr, "ERROR: You must enter 'secret <SECRET>' before running any commands\n");
+                       line = NULL;
+                       continue;
+               }
+
                size = run_command(sockfd, line, buffer, sizeof(buffer));
                if (size <= 0) break; /* error, or clean exit */