From: Alan T. DeKok Date: Mon, 30 Apr 2012 14:24:40 +0000 (+0200) Subject: Absolutely horrible hacks to get radmin on a TCP socket. X-Git-Tag: release_3_0_0_beta0~206 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7d287e2ad715052c09fc9b4050212c0ba3b98450;p=thirdparty%2Ffreeradius-server.git Absolutely horrible hacks to get radmin on a TCP socket. 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 --- diff --git a/src/include/radiusd.h b/src/include/radiusd.h index 69f9bd6c8fc..ea1530dea5b 100644 --- a/src/include/radiusd.h +++ b/src/include/radiusd.h @@ -122,6 +122,10 @@ typedef struct auth_req REQUEST; #include #include +#ifdef WITH_COMMAND_SOCKET +#define PW_RADMIN_PORT 18120 +#endif + #ifdef __cplusplus extern "C" { #endif diff --git a/src/main/command.c b/src/main/command.c index 34c7b03b3f9..cab3b711de9 100644 --- a/src/main/command.c +++ b/src/main/command.c @@ -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; /* */ 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 \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; diff --git a/src/main/listen.c b/src/main/listen.c index fd9d902e929..cb774d5d1fc 100644 --- a/src/main/listen.c +++ b/src/main/listen.c @@ -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"); diff --git a/src/main/radmin.c b/src/main/radmin.c index 1cef681686b..b4f6da10648 100644 --- a/src/main/radmin.c +++ b/src/main/radmin.c @@ -31,6 +31,10 @@ RCSID("$Id$") #include #endif +#ifdef HAVE_NETINET_IN_H +#include +#endif + #ifdef HAVE_SYS_UN_H #include #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 ' before running any commands\n"); + line = NULL; + continue; + } + size = run_command(sockfd, line, buffer, sizeof(buffer)); if (size <= 0) break; /* error, or clean exit */