#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;
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[] = {
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;
}
*
* 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;
return -1;
}
+ sock->magic = COMMAND_SOCKET_MAGIC;
sock->copy = NULL;
if (sock->path) sock->copy = strdup(sock->path);
#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;
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;
}
* 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;
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;
}
+ /*
+ * 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;
}
#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.
*/
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;
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}
*/
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.
break;
#endif
+#ifdef WITH_COMMAND_SOCKET
+ case RAD_LISTEN_COMMAND:
+ name = "control";
+ break;
+#endif
+
default:
name = "??";
break;
} 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;
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");
#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
static FILE *outputfp = NULL;
static int echo = FALSE;
+static const char *secret = "testing123";
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);
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;
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;
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;
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;
}
}
#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);
}
/*
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.
goto reconnect;
}
+ if (memcmp(line, "secret ", 7) == 0) {
+ if (!secret) {
+ secret = line + 7;
+ do_challenge(sockfd);
+ }
+ line = NULL;
+ continue;
+ }
+
/*
* Exit, done, etc.
*/
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 */