From edebf579f2ea29e6e84360cb13731f5858a1555b Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sat, 7 Nov 2009 23:43:25 +0100 Subject: [PATCH] Use the TCP socket infrastructure for control sockets. The control socket code was completely different from how meta connections are handled, resulting in lots of extra code to handle requests. Also, not every operating system has UNIX sockets, so we have to resort to another type of sockets or pipes for those anyway. To reduce code duplication and make control sockets work the same on all platforms, we now just connect to the TCP port where tincd is already listening on. To authenticate, the program that wants to control a running tinc daemon must send the contents of a cookie file. The cookie is a random 256 bits number that is regenerated every time tincd starts. The cookie file should only be readable by the same user that can start a tincd. Instead of the binary-ish protocol previously used, we now use an ASCII protocol similar to that of the meta connections, but this can still change. --- src/connection.c | 14 +- src/connection.h | 5 +- src/control.c | 330 ++++++++--------------------------------- src/control.h | 1 + src/control_common.h | 19 +-- src/edge.c | 16 +- src/edge.h | 2 +- src/graph.c | 38 +---- src/graph.h | 1 - src/node.c | 10 +- src/node.h | 2 +- src/protocol.c | 4 +- src/protocol.h | 2 + src/protocol_auth.c | 11 ++ src/subnet.c | 13 +- src/subnet.h | 2 +- src/tincctl.c | 339 ++++++++++++++++++++----------------------- src/tincd.c | 23 ++- 18 files changed, 287 insertions(+), 545 deletions(-) diff --git a/src/connection.c b/src/connection.c index 2372890d..519cf5b3 100644 --- a/src/connection.c +++ b/src/connection.c @@ -24,6 +24,7 @@ #include "splay_tree.h" #include "cipher.h" #include "conf.h" +#include "control_common.h" #include "list.h" #include "logger.h" #include "net.h" /* Don't ask. */ @@ -91,20 +92,19 @@ void connection_del(connection_t *c) { splay_delete(connection_tree, c); } -int dump_connections(struct evbuffer *out) { +bool dump_connections(connection_t *cdump) { splay_node_t *node; connection_t *c; for(node = connection_tree->head; node; node = node->next) { c = node->data; - if(evbuffer_add_printf(out, - " %s at %s options %x socket %d status %04x\n", - c->name, c->hostname, c->options, c->socket, - bitfield_to_int(&c->status, sizeof c->status)) == -1) - return errno; + send_request(cdump, "%d %d %s at %s options %x socket %d status %04x", + CONTROL, REQ_DUMP_CONNECTIONS, + c->name, c->hostname, c->options, c->socket, + bitfield_to_int(&c->status, sizeof c->status)); } - return 0; + return send_request(cdump, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS); } bool read_connection_config(connection_t *c) { diff --git a/src/connection.h b/src/connection.h index 9476996f..6d295312 100644 --- a/src/connection.h +++ b/src/connection.h @@ -40,7 +40,8 @@ typedef struct connection_status_t { int encryptout:1; /* 1 if we can encrypt outgoing traffic */ int decryptin:1; /* 1 if we have to decrypt incoming traffic */ int mst:1; /* 1 if this connection is part of a minimum spanning tree */ - int unused:23; + int control:1; + int unused:22; } connection_status_t; #include "edge.h" @@ -97,7 +98,7 @@ extern connection_t *new_connection(void) __attribute__ ((__malloc__)); extern void free_connection(connection_t *); extern void connection_add(connection_t *); extern void connection_del(connection_t *); -extern int dump_connections(struct evbuffer *); +extern bool dump_connections(struct connection_t *); extern bool read_connection_config(connection_t *); #endif /* __TINC_CONNECTION_H__ */ diff --git a/src/control.c b/src/control.c index b62e1439..22078151 100644 --- a/src/control.c +++ b/src/control.c @@ -18,313 +18,111 @@ */ #include "system.h" +#include "crypto.h" #include "conf.h" #include "control.h" #include "control_common.h" #include "graph.h" #include "logger.h" +#include "protocol.h" #include "utils.h" #include "xalloc.h" static int control_socket = -1; static struct event control_event; static splay_tree_t *control_socket_tree; -extern char *controlsocketname; +char controlcookie[65]; +extern char *controlcookiename; -static void handle_control_data(struct bufferevent *event, void *data) { - tinc_ctl_request_t req; - tinc_ctl_request_t res; - struct evbuffer *res_data = NULL; - void *req_data; - - if(EVBUFFER_LENGTH(event->input) < sizeof req) - return; - - /* Copy the structure to ensure alignment */ - memcpy(&req, EVBUFFER_DATA(event->input), sizeof req); - - if(EVBUFFER_LENGTH(event->input) < req.length) - return; - req_data = EVBUFFER_DATA(event->input) + sizeof req; +static bool control_return(connection_t *c, int type, int error) { + return send_request(c, "%d %d %d", CONTROL, type, error); +} - if(req.length < sizeof req) - goto failure; +static bool control_ok(connection_t *c, int type) { + return control_return(c, type, 0); +} - memset(&res, 0, sizeof res); - res.type = req.type; - res.id = req.id; +bool control_h(connection_t *c, char *request) { + int type; - res_data = evbuffer_new(); - if(res_data == NULL) { - res.res_errno = ENOMEM; - goto respond; + if(!c->status.control || c->allow_request != CONTROL) { + logger(LOG_ERR, "Unauthorized control request from %s (%s)", c->name, c->hostname); + return false; } - if(req.type == REQ_STOP) { - logger(LOG_NOTICE, "Got '%s' command", "stop"); - event_loopexit(NULL); - goto respond; + if(sscanf(request, "%*d %d", &type) != 1) { + logger(LOG_ERR, "Got bad %s from %s (%s)", "CONTROL", c->name, c->hostname); + return false; } - if(req.type == REQ_DUMP_NODES) { - logger(LOG_NOTICE, "Got '%s' command", "dump nodes"); - res.res_errno = dump_nodes(res_data); - goto respond; - } + switch (type) { + case REQ_STOP: + event_loopexit(NULL); + return control_ok(c, REQ_STOP); - if(req.type == REQ_DUMP_EDGES) { - logger(LOG_NOTICE, "Got '%s' command", "dump edges"); - res.res_errno = dump_edges(res_data); - goto respond; - } + case REQ_DUMP_NODES: + return dump_nodes(c); + + case REQ_DUMP_EDGES: + return dump_edges(c); - if(req.type == REQ_DUMP_SUBNETS) { - logger(LOG_NOTICE, "Got '%s' command", "dump subnets"); - res.res_errno = dump_subnets(res_data); - goto respond; - } + case REQ_DUMP_SUBNETS: + return dump_subnets(c); - if(req.type == REQ_DUMP_CONNECTIONS) { - logger(LOG_NOTICE, "Got '%s' command", "dump connections"); - res.res_errno = dump_connections(res_data); - goto respond; - } - - if(req.type == REQ_DUMP_GRAPH) { - logger(LOG_NOTICE, "Got '%s' command", "dump graph"); - res.res_errno = dump_graph(res_data); - goto respond; - } + case REQ_DUMP_CONNECTIONS: + return dump_connections(c); - if(req.type == REQ_PURGE) { - logger(LOG_NOTICE, "Got '%s' command", "purge"); - purge(); - goto respond; - } + case REQ_PURGE: + purge(); + return control_ok(c, REQ_PURGE); - if(req.type == REQ_SET_DEBUG) { - debug_t new_debug_level; - - logger(LOG_NOTICE, "Got '%s' command", "debug"); - if(req.length != sizeof req + sizeof debug_level) - res.res_errno = EINVAL; - else { - memcpy(&new_debug_level, req_data, sizeof new_debug_level); - logger(LOG_NOTICE, "Changing debug level from %d to %d", - debug_level, new_debug_level); - if(evbuffer_add_printf(res_data, - "Changing debug level from %d to %d\n", - debug_level, new_debug_level) == -1) - res.res_errno = errno; - debug_level = new_debug_level; + case REQ_SET_DEBUG: { + int new_level; + if(sscanf(request, "%*d %*d %d", &new_level) != 1) + return false; + send_request(c, "%d %d %d", CONTROL, REQ_SET_DEBUG, debug_level); + if(new_level >= 0) + debug_level = new_level; + return true; } - goto respond; - } - - if(req.type == REQ_RETRY) { - logger(LOG_NOTICE, "Got '%s' command", "retry"); - retry(); - goto respond; - } - - if(req.type == REQ_RELOAD) { - logger(LOG_NOTICE, "Got '%s' command", "reload"); - res.res_errno = reload_configuration(); - goto respond; - } - - logger(LOG_DEBUG, "Malformed control command received"); - res.res_errno = EINVAL; - -respond: - res.length = (sizeof res) - + ((res_data == NULL) ? 0 : EVBUFFER_LENGTH(res_data)); - evbuffer_drain(event->input, req.length); - if(bufferevent_write(event, &res, sizeof res) == -1) - goto failure; - if(res_data != NULL) { - if(bufferevent_write_buffer(event, res_data) == -1) - goto failure; - evbuffer_free(res_data); - } - return; - -failure: - logger(LOG_INFO, "Closing control socket on error"); - evbuffer_free(res_data); - close(event->ev_read.ev_fd); - splay_delete(control_socket_tree, event); -} -static void handle_control_error(struct bufferevent *event, short what, void *data) { - if(what & EVBUFFER_EOF) - logger(LOG_DEBUG, "Control socket connection closed by peer"); - else - logger(LOG_DEBUG, "Error while reading from control socket: %s", strerror(errno)); + case REQ_RETRY: + retry(); + return control_ok(c, REQ_RETRY); - close(event->ev_read.ev_fd); - splay_delete(control_socket_tree, event); -} - -static void handle_new_control_socket(int fd, short events, void *data) { - int newfd; - struct bufferevent *ev; - tinc_ctl_greeting_t greeting; - - newfd = accept(fd, NULL, NULL); - - if(newfd < 0) { - logger(LOG_ERR, "Accepting a new connection failed: %s", strerror(errno)); - event_del(&control_event); - return; - } - - ev = bufferevent_new(newfd, handle_control_data, NULL, handle_control_error, NULL); - if(!ev) { - logger(LOG_ERR, "Could not create bufferevent for new control connection: %s", strerror(errno)); - close(newfd); - return; - } + case REQ_RELOAD: + logger(LOG_NOTICE, "Got '%s' command", "reload"); + int result = reload_configuration(); + return control_return(c, REQ_RELOAD, result); - memset(&greeting, 0, sizeof greeting); - greeting.version = TINC_CTL_VERSION_CURRENT; - greeting.pid = getpid(); - if(bufferevent_write(ev, &greeting, sizeof greeting) == -1) { - logger(LOG_ERR, - "Cannot send greeting for new control connection: %s", - strerror(errno)); - bufferevent_free(ev); - close(newfd); - return; + default: + return send_request(c, "%d %d", CONTROL, REQ_INVALID); } - - bufferevent_enable(ev, EV_READ); - splay_insert(control_socket_tree, ev); - - logger(LOG_DEBUG, "Control socket connection accepted"); -} - -static int control_compare(const struct event *a, const struct event *b) { - return a < b ? -1 : a > b ? 1 : 0; } bool init_control() { - int result; + randomize(controlcookie, sizeof controlcookie / 2); + bin2hex(controlcookie, controlcookie, sizeof controlcookie / 2); + controlcookie[sizeof controlcookie - 1] = 0; -#ifdef HAVE_MINGW - struct sockaddr_in addr; - memset(&addr, 0, sizeof addr); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(0x7f000001); - addr.sin_port = htons(55555); - int option = 1; - - control_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - if(control_socket < 0) { - logger(LOG_ERR, "Creating control socket failed: %s", sockstrerror(sockerrno)); - goto bail; + FILE *f = fopen(controlcookiename, "w"); + if(!f) { + logger(LOG_ERR, "Cannot write control socket cookie file %s: %s", controlcookiename, strerror(errno)); + return false; } - setsockopt(control_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof option); +#ifdef HAVE_FCHMOD + fchmod(f, 0600); #else - struct sockaddr_un addr; - char *lastslash; - - if(strlen(controlsocketname) >= sizeof addr.sun_path) { - logger(LOG_ERR, "Control socket filename too long!"); - goto bail; - } - - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, controlsocketname, sizeof addr.sun_path - 1); - - control_socket = socket(PF_UNIX, SOCK_STREAM, 0); - - if(control_socket < 0) { - logger(LOG_ERR, "Creating UNIX socket failed: %s", strerror(errno)); - goto bail; - } - - /* - * Restrict connections to our control socket by ensuring the parent - * directory can be traversed only by root. Note this is not totally - * race-free unless all ancestors are writable only by trusted users, - * which we don't verify. - */ - - struct stat statbuf; - lastslash = strrchr(controlsocketname, '/'); - if(lastslash != NULL) { - *lastslash = 0; /* temporarily change controlsocketname to be dir */ - if(mkdir(controlsocketname, 0700) < 0 && errno != EEXIST) { - logger(LOG_ERR, "Unable to create control socket directory %s: %s", controlsocketname, strerror(errno)); - *lastslash = '/'; - goto bail; - } - - result = stat(controlsocketname, &statbuf); - *lastslash = '/'; - } else - result = stat(".", &statbuf); - - if(result < 0) { - logger(LOG_ERR, "Examining control socket directory failed: %s", strerror(errno)); - goto bail; - } - - if(statbuf.st_uid != 0 || (statbuf.st_mode & S_IXOTH) != 0 || (statbuf.st_gid != 0 && (statbuf.st_mode & S_IXGRP)) != 0) { - logger(LOG_ERR, "Control socket directory ownership/permissions insecure."); - goto bail; - } + chmod(controlcookiename, 0600); #endif - result = bind(control_socket, (struct sockaddr *)&addr, sizeof addr); - - if(result < 0 && sockinuse(sockerrno)) { -#ifndef HAVE_MINGW - result = connect(control_socket, (struct sockaddr *)&addr, sizeof addr); - if(result < 0) { - logger(LOG_WARNING, "Removing old control socket."); - unlink(controlsocketname); - result = bind(control_socket, (struct sockaddr *)&addr, sizeof addr); - } else -#endif - { - if(netname) - logger(LOG_ERR, "Another tincd is already running for net `%s'.", netname); - else - logger(LOG_ERR, "Another tincd is already running."); - goto bail; - } - } + fprintf(f, "%s %s %d\n", controlcookie, myport, getpid()); + fclose(f); - if(result < 0) { - logger(LOG_ERR, "Can't bind to %s: %s", controlsocketname, strerror(errno)); - goto bail; - } - - if(listen(control_socket, 3) < 0) { - logger(LOG_ERR, "Can't listen on %s: %s", controlsocketname, strerror(errno)); - goto bail; - } - - control_socket_tree = splay_alloc_tree((splay_compare_t)control_compare, (splay_action_t)bufferevent_free); - - event_set(&control_event, control_socket, EV_READ | EV_PERSIST, handle_new_control_socket, NULL); - event_add(&control_event, NULL); return true; - -bail: - if(control_socket != -1) { - closesocket(control_socket); - control_socket = -1; - } - return false; } void exit_control() { - event_del(&control_event); - closesocket(control_socket); - unlink(controlsocketname); + unlink(controlcookiename); } diff --git a/src/control.h b/src/control.h index 91708fca..ce8145a3 100644 --- a/src/control.h +++ b/src/control.h @@ -22,5 +22,6 @@ extern bool init_control(); extern void exit_control(); +extern char controlcookie[]; #endif diff --git a/src/control_common.h b/src/control_common.h index 720c2360..c89f9de7 100644 --- a/src/control_common.h +++ b/src/control_common.h @@ -20,8 +20,11 @@ #ifndef __TINC_CONTROL_PROTOCOL_H__ #define __TINC_CONTROL_PROTOCOL_H__ +#include "protocol.h" + enum request_type { - REQ_STOP, + REQ_INVALID = -1, + REQ_STOP = 0, REQ_RELOAD, REQ_RESTART, REQ_DUMP_NODES, @@ -36,18 +39,4 @@ enum request_type { #define TINC_CTL_VERSION_CURRENT 0 -/* This greeting is sent by the server on socket open. */ -typedef struct tinc_ctl_greeting_t { - int version; - pid_t pid; -} tinc_ctl_greeting_t; - -/* A single request or response header. */ -typedef struct tinc_ctl_request_t { - size_t length; /* total length, including the header */ - enum request_type type; - int id; - int res_errno; /* used only for responses */ -} tinc_ctl_request_t; - #endif diff --git a/src/edge.c b/src/edge.c index 80bdddbd..f5aa0994 100644 --- a/src/edge.c +++ b/src/edge.c @@ -21,6 +21,7 @@ #include "system.h" #include "splay_tree.h" +#include "control_common.h" #include "edge.h" #include "logger.h" #include "netutl.h" @@ -105,7 +106,7 @@ edge_t *lookup_edge(node_t *from, node_t *to) { return splay_search(from->edge_tree, &v); } -int dump_edges(struct evbuffer *out) { +bool dump_edges(connection_t *c) { splay_node_t *node, *node2; node_t *n; edge_t *e; @@ -116,16 +117,13 @@ int dump_edges(struct evbuffer *out) { for(node2 = n->edge_tree->head; node2; node2 = node2->next) { e = node2->data; address = sockaddr2hostname(&e->address); - if(evbuffer_add_printf(out, - " %s to %s at %s options %x weight %d\n", - e->from->name, e->to->name, address, - e->options, e->weight) == -1) { - free(address); - return errno; - } + send_request(c, "%d %d %s to %s at %s options %x weight %d", + CONTROL, REQ_DUMP_EDGES, + e->from->name, e->to->name, address, + e->options, e->weight); free(address); } } - return 0; + return send_request(c, "%d %d", CONTROL, REQ_DUMP_EDGES); } diff --git a/src/edge.h b/src/edge.h index cf62b71b..ea45f497 100644 --- a/src/edge.h +++ b/src/edge.h @@ -49,6 +49,6 @@ extern void free_edge_tree(splay_tree_t *); extern void edge_add(edge_t *); extern void edge_del(edge_t *); extern edge_t *lookup_edge(struct node_t *, struct node_t *); -extern int dump_edges(struct evbuffer *); +extern bool dump_edges(struct connection_t *); #endif /* __TINC_EDGE_H__ */ diff --git a/src/graph.c b/src/graph.c index 5a0aab0b..06bf36d7 100644 --- a/src/graph.c +++ b/src/graph.c @@ -382,44 +382,8 @@ void check_reachability() { } } -/* Dump nodes and edges to a graphviz file. - - The file can be converted to an image with - dot -Tpng graph_filename -o image_filename.png -Gconcentrate=true -*/ - -int dump_graph(struct evbuffer *out) { - splay_node_t *node; - node_t *n; - edge_t *e; - - if(evbuffer_add_printf(out, "digraph {\n") == -1) - return errno; - - /* dump all nodes first */ - for(node = node_tree->head; node; node = node->next) { - n = node->data; - if(evbuffer_add_printf(out, " %s [label = \"%s\"];\n", - n->name, n->name) == -1) - return errno; - } - - /* now dump all edges */ - for(node = edge_weight_tree->head; node; node = node->next) { - e = node->data; - if(evbuffer_add_printf(out, " %s -> %s;\n", - e->from->name, e->to->name) == -1) - return errno; - } - - if(evbuffer_add_printf(out, "}\n") == -1) - return errno; - - return 0; -} - void graph(void) { - subnet_cache_flush(); + subnet_cache_flush(); sssp_dijkstra(); check_reachability(); mst_kruskal(); diff --git a/src/graph.h b/src/graph.h index 4f9bb5d4..c8d5fda6 100644 --- a/src/graph.h +++ b/src/graph.h @@ -24,6 +24,5 @@ extern void graph(void); extern void mst_kruskal(void); extern void sssp_bfs(void); -extern int dump_graph(struct evbuffer *); #endif /* __TINC_GRAPH_H__ */ diff --git a/src/node.c b/src/node.c index 75f01e30..e3eca447 100644 --- a/src/node.c +++ b/src/node.c @@ -20,6 +20,7 @@ #include "system.h" +#include "control_common.h" #include "splay_tree.h" #include "logger.h" #include "net.h" @@ -154,19 +155,18 @@ void update_node_udp(node_t *n, const sockaddr_t *sa) { } } -int dump_nodes(struct evbuffer *out) { +bool dump_nodes(connection_t *c) { splay_node_t *node; node_t *n; for(node = node_tree->head; node; node = node->next) { n = node->data; - if(evbuffer_add_printf(out, " %s at %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %d (min %d max %d)\n", + send_request(c, "%d %d %s at %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %d (min %d max %d)", CONTROL, REQ_DUMP_NODES, n->name, n->hostname, cipher_get_nid(&n->outcipher), digest_get_nid(&n->outdigest), digest_length(&n->outdigest), n->outcompression, n->options, bitfield_to_int(&n->status, sizeof n->status), n->nexthop ? n->nexthop->name : "-", - n->via ? n->via->name : "-", n->distance, n->mtu, n->minmtu, n->maxmtu) == -1) - return errno; + n->via ? n->via->name : "-", n->distance, n->mtu, n->minmtu, n->maxmtu); } - return 0; + return send_request(c, "%d %d", CONTROL, REQ_DUMP_NODES); } diff --git a/src/node.h b/src/node.h index 02e16c6c..f5ebde3b 100644 --- a/src/node.h +++ b/src/node.h @@ -89,7 +89,7 @@ extern void node_add(node_t *); extern void node_del(node_t *); extern node_t *lookup_node(char *); extern node_t *lookup_node_udp(const sockaddr_t *); -extern int dump_nodes(struct evbuffer *); +extern bool dump_nodes(struct connection_t *); extern void update_node_udp(node_t *, const sockaddr_t *); #endif /* __TINC_NODE_H__ */ diff --git a/src/protocol.c b/src/protocol.c index ac4b767e..02f2841f 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -38,7 +38,7 @@ static bool (*request_handlers[])(connection_t *, char *) = { ping_h, pong_h, add_subnet_h, del_subnet_h, add_edge_h, del_edge_h, - key_changed_h, req_key_h, ans_key_h, tcppacket_h, + key_changed_h, req_key_h, ans_key_h, tcppacket_h, control_h, }; /* Request names */ @@ -48,7 +48,7 @@ static char (*request_name[]) = { "STATUS", "ERROR", "TERMREQ", "PING", "PONG", "ADD_SUBNET", "DEL_SUBNET", - "ADD_EDGE", "DEL_EDGE", "KEY_CHANGED", "REQ_KEY", "ANS_KEY", "PACKET", + "ADD_EDGE", "DEL_EDGE", "KEY_CHANGED", "REQ_KEY", "ANS_KEY", "PACKET", "CONTROL", }; static splay_tree_t *past_request_tree; diff --git a/src/protocol.h b/src/protocol.h index a5ac8edc..f290148f 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -44,6 +44,7 @@ typedef enum request_t { ADD_EDGE, DEL_EDGE, KEY_CHANGED, REQ_KEY, ANS_KEY, PACKET, + CONTROL, LAST /* Guardian for the highest request number */ } request_t; @@ -119,5 +120,6 @@ extern bool key_changed_h(struct connection_t *, char *); extern bool req_key_h(struct connection_t *, char *); extern bool ans_key_h(struct connection_t *, char *); extern bool tcppacket_h(struct connection_t *, char *); +extern bool control_h(struct connection_t *, char *); #endif /* __TINC_PROTOCOL_H__ */ diff --git a/src/protocol_auth.c b/src/protocol_auth.c index a38b9adf..85272301 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -23,6 +23,8 @@ #include "splay_tree.h" #include "conf.h" #include "connection.h" +#include "control.h" +#include "control_common.h" #include "crypto.h" #include "edge.h" #include "graph.h" @@ -51,6 +53,15 @@ bool id_h(connection_t *c, char *request) { return false; } + /* Check if this is a control connection */ + + if(name[0] == '^' && !strcmp(name + 1, controlcookie)) { + c->status.control = true; + c->allow_request = CONTROL; + c->last_ping_time = time(NULL) + 3600; + return send_request(c, "%d %d %d", ACK, TINC_CTL_VERSION_CURRENT, getpid()); + } + /* Check if identity is a valid name */ if(!check_id(name)) { diff --git a/src/subnet.c b/src/subnet.c index 29ea96d9..0669b8a0 100644 --- a/src/subnet.c +++ b/src/subnet.c @@ -21,6 +21,7 @@ #include "system.h" #include "splay_tree.h" +#include "control_common.h" #include "device.h" #include "logger.h" #include "net.h" @@ -273,7 +274,7 @@ bool str2net(subnet_t *subnet, const char *subnetstr) { bool net2str(char *netstr, int len, const subnet_t *subnet) { if(!netstr || !subnet) { - logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!\n", netstr, subnet); + logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", netstr, subnet); return false; } @@ -527,7 +528,7 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) { free(envp[i]); } -int dump_subnets(struct evbuffer *out) { +bool dump_subnets(connection_t *c) { char netstr[MAXNETSTR]; subnet_t *subnet; splay_node_t *node; @@ -536,10 +537,10 @@ int dump_subnets(struct evbuffer *out) { subnet = node->data; if(!net2str(netstr, sizeof netstr, subnet)) continue; - if(evbuffer_add_printf(out, " %s owner %s\n", - netstr, subnet->owner->name) == -1) - return errno; + send_request(c, "%d %d %s owner %s", + CONTROL, REQ_DUMP_SUBNETS, + netstr, subnet->owner->name); } - return 0; + return send_request(c, "%d %d", CONTROL, REQ_DUMP_SUBNETS); } diff --git a/src/subnet.h b/src/subnet.h index c6aa93f2..466eb20c 100644 --- a/src/subnet.h +++ b/src/subnet.h @@ -80,7 +80,7 @@ extern subnet_t *lookup_subnet(const struct node_t *, const subnet_t *); extern subnet_t *lookup_subnet_mac(const mac_t *); extern subnet_t *lookup_subnet_ipv4(const ipv4_t *); extern subnet_t *lookup_subnet_ipv6(const ipv6_t *); -extern int dump_subnets(struct evbuffer *); +extern bool dump_subnets(struct connection_t *); extern void subnet_cache_flush(void); #endif /* __TINC_SUBNET_H__ */ diff --git a/src/tincctl.c b/src/tincctl.c index 784c6a94..8a07dfa0 100644 --- a/src/tincctl.c +++ b/src/tincctl.c @@ -42,8 +42,10 @@ int kill_tincd = 0; /* If nonzero, generate public/private keypair for this host/net. */ int generate_keys = 0; +static char *name = NULL; static char *identname = NULL; /* program name for syslog */ -static char *controlsocketname = NULL; /* pid file location */ +static char *controlcookiename = NULL; /* cookie file location */ +static char controlcookie[1024]; char *netname = NULL; char *confbase = NULL; @@ -69,7 +71,7 @@ static void usage(bool status) { printf("Valid options are:\n" " -c, --config=DIR Read configuration options from DIR.\n" " -n, --net=NETNAME Connect to net NETNAME.\n" - " --controlsocket=FILENAME Open control socket at FILENAME.\n" + " --controlcookie=FILENAME Read control socket from FILENAME.\n" " --help Display this help and exit.\n" " --version Output version information and exit.\n" "\n" @@ -121,7 +123,7 @@ static bool parse_options(int argc, char **argv) { break; case 5: /* open control socket here */ - controlsocketname = xstrdup(optarg); + controlcookiename = xstrdup(optarg); break; case '?': @@ -196,7 +198,6 @@ FILE *ask_and_open(const char *filename, const char *what, const char *mode) { static bool keygen(int bits) { rsa_t key; FILE *f; - char *name = NULL; char *filename; fprintf(stderr, "Generating %d bits keys:\n", bits); @@ -272,14 +273,16 @@ static void make_names(void) { xasprintf(&confbase, "%s", installdir); } } + if(!controlcookiename) + xasprintf(&controlcookiename, "%s/cookie", confbase); RegCloseKey(key); if(*installdir) return; } #endif - if(!controlsocketname) - xasprintf(&controlsocketname, "%s/run/%s.control/socket", LOCALSTATEDIR, identname); + if(!controlcookiename) + xasprintf(&controlcookiename, "%s/run/%s.control/socket", LOCALSTATEDIR, identname); if(netname) { if(!confbase) @@ -292,123 +295,67 @@ static void make_names(void) { } } -static int fullread(int fd, void *data, size_t datalen) { - int rv, len = 0; +static bool recvline(int fd, char *line, size_t len) { + static char buffer[4096]; + static size_t blen = 0; + char *newline = NULL; - while(len < datalen) { - rv = recv(fd, data + len, datalen - len, 0); - if(rv == -1 && errno == EINTR) + while(!(newline = memchr(buffer, '\n', blen))) { + int result = recv(fd, buffer + blen, sizeof buffer - blen, 0); + if(result == -1 && errno == EINTR) continue; - else if(rv == -1) - return rv; - else if(rv == 0) { -#ifdef HAVE_MINGW - errno = 0; -#else - errno = ENODATA; -#endif - return -1; - } - len += rv; + else if(result <= 0) + return false; + blen += result; } - return 0; -} - -/* - Send a request (raw) -*/ -static int send_ctl_request(int fd, enum request_type type, - void const *outdata, size_t outdatalen, - int *res_errno_p, void **indata_p, - size_t *indatalen_p) { - tinc_ctl_request_t req; - int rv; - void *indata; - - memset(&req, 0, sizeof req); - req.length = sizeof req + outdatalen; - req.type = type; - req.res_errno = 0; - -#ifdef HAVE_MINGW - if(send(fd, (void *)&req, sizeof req, 0) != sizeof req || send(fd, outdata, outdatalen, 0) != outdatalen) - return -1; -#else - struct iovec vector[2] = { - {&req, sizeof req}, - {(void*) outdata, outdatalen} - }; - - if(res_errno_p == NULL) - return -1; - while((rv = writev(fd, vector, 2)) == -1 && errno == EINTR) ; - if(rv != req.length) - return -1; -#endif - - if(fullread(fd, &req, sizeof req) == -1) - return -1; - - if(req.length < sizeof req) { - errno = EINVAL; - return -1; - } + if(newline - buffer >= len) + return false; - if(req.length > sizeof req) { - if(indata_p == NULL) { - errno = EINVAL; - return -1; - } + len = newline - buffer; - indata = xmalloc(req.length - sizeof req); + memcpy(line, buffer, len); + line[len] = 0; + memmove(buffer, newline + 1, blen - len - 1); + blen -= len + 1; - if(fullread(fd, indata, req.length - sizeof req) == -1) { - free(indata); - return -1; - } + return true; +} - *indata_p = indata; - if(indatalen_p != NULL) - *indatalen_p = req.length - sizeof req; - } +static bool sendline(int fd, char *format, ...) { + static char buffer[4096]; + char *p = buffer; + size_t blen = 0; + va_list ap; - *res_errno_p = req.res_errno; + va_start(ap, format); + blen = vsnprintf(buffer, sizeof buffer, format, ap); + va_end(ap); - return 0; -} - -/* - Send a request (with printfs) -*/ -static int send_ctl_request_cooked(int fd, enum request_type type, void const *outdata, size_t outdatalen) { - int res_errno = -1; - char *buf = NULL; - size_t buflen = 0; - - if(send_ctl_request(fd, type, outdata, outdatalen, &res_errno, - (void**) &buf, &buflen)) { - fprintf(stderr, "Error sending request: %s\n", strerror(errno)); - return -1; - } + if(blen < 0 || blen >= sizeof buffer) + return false; - if(buf != NULL) { - printf("%*s", (int)buflen, buf); - free(buf); - } + buffer[blen] = '\n'; + blen++; - if(res_errno != 0) { - fprintf(stderr, "Server reported error: %s\n", strerror(res_errno)); - return -1; + while(blen) { + int result = send(fd, p, blen, 0); + if(result == -1 && errno == EINTR) + continue; + else if(result <= 0); + return false; + p += result; + blen -= result; } - return 0; + return true; } int main(int argc, char *argv[], char *envp[]) { - tinc_ctl_greeting_t greeting; int fd; int result; + int port; + int pid; program_name = argv[0]; @@ -460,17 +407,28 @@ int main(int argc, char *argv[], char *envp[]) { * ancestors are writable only by trusted users, which we don't verify. */ + FILE *f = fopen(controlcookiename, "r"); + if(!f) { + fprintf(stderr, "Could not open control socket cookie file %s: %s\n", controlcookiename, strerror(errno)); + return 1; + } + if(fscanf(f, "%1024s %d %d", controlcookie, &port, &pid) != 3) { + fprintf(stderr, "Could not parse control socket cookie file %s\n", controlcookiename); + return 1; + } + #ifdef HAVE_MINGW if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); return 1; } +#endif struct sockaddr_in addr; memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(0x7f000001); - addr.sin_port = htons(55555); + addr.sin_port = htons(port); fd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if(fd < 0) { @@ -483,77 +441,69 @@ int main(int argc, char *argv[], char *envp[]) { if(ioctlsocket(fd, FIONBIO, &arg) != 0) { fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno)); } -#else - struct sockaddr_un addr; - struct stat statbuf; - char *lastslash = strrchr(controlsocketname, '/'); - if(lastslash != NULL) { - /* control socket is not in cwd; stat its parent */ - *lastslash = 0; - result = stat(controlsocketname, &statbuf); - *lastslash = '/'; - } else - result = stat(".", &statbuf); - - if(result < 0) { - fprintf(stderr, "Unable to check control socket directory permissions: %s\n", strerror(errno)); - return 1; - } - - if(statbuf.st_uid != 0 || (statbuf.st_mode & S_IXOTH) != 0 || (statbuf.st_gid != 0 && (statbuf.st_mode & S_IXGRP)) != 0) { - fprintf(stderr, "Insecure permissions on control socket directory\n"); - return 1; - } - - if(strlen(controlsocketname) >= sizeof addr.sun_path) { - fprintf(stderr, "Control socket filename too long!\n"); - return 1; - } - - fd = socket(PF_UNIX, SOCK_STREAM, 0); - if(fd < 0) { - fprintf(stderr, "Cannot create UNIX socket: %s\n", strerror(errno)); - return 1; - } - - memset(&addr, 0, sizeof addr); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, controlsocketname, sizeof addr.sun_path - 1); -#endif if(connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) { - fprintf(stderr, "Cannot connect to %s: %s\n", controlsocketname, sockstrerror(sockerrno)); + fprintf(stderr, "Cannot connect to %s: %s\n", controlcookiename, sockstrerror(sockerrno)); return 1; } - if(fullread(fd, &greeting, sizeof greeting) == -1) { + char line[4096]; + char data[4096]; + int code, version, req; + + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %s %d", &code, data, &version) != 3 || code != 0) { fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno)); return 1; } - if(greeting.version != TINC_CTL_VERSION_CURRENT) { - fprintf(stderr, "Version mismatch: server %d, client %d\n", - greeting.version, TINC_CTL_VERSION_CURRENT); + sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT); + + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) { + fprintf(stderr, "Could not fully establish control socket connection\n"); return 1; } if(!strcasecmp(argv[optind], "pid")) { - printf("%d\n", greeting.pid); + printf("%d\n", pid); return 0; } if(!strcasecmp(argv[optind], "stop")) { - return send_ctl_request_cooked(fd, REQ_STOP, NULL, 0) != -1; + sendline(fd, "%d %d", CONTROL, REQ_STOP); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_STOP || result) { + fprintf(stderr, "Could not stop tinc daemon\n"); + return 1; + } + return 0; } if(!strcasecmp(argv[optind], "reload")) { - return send_ctl_request_cooked(fd, REQ_RELOAD, NULL, 0) != -1; + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RELOAD || result) { + fprintf(stderr, "Could not reload tinc daemon\n"); + return 1; + } + return 0; } - + if(!strcasecmp(argv[optind], "restart")) { - return send_ctl_request_cooked(fd, REQ_RESTART, NULL, 0) != -1; + sendline(fd, "%d %d", CONTROL, REQ_RESTART); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RESTART || result) { + fprintf(stderr, "Could not restart tinc daemon\n"); + return 1; + } + return 0; + } + + if(!strcasecmp(argv[optind], "retry")) { + sendline(fd, "%d %d", CONTROL, REQ_RETRY); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RETRY || result) { + fprintf(stderr, "Could not retry outgoing connections\n"); + return 1; + } + return 0; } if(!strcasecmp(argv[optind], "dump")) { @@ -563,53 +513,84 @@ int main(int argc, char *argv[], char *envp[]) { return 1; } - if(!strcasecmp(argv[optind+1], "nodes")) { - return send_ctl_request_cooked(fd, REQ_DUMP_NODES, NULL, 0) != -1; - } - - if(!strcasecmp(argv[optind+1], "edges")) { - return send_ctl_request_cooked(fd, REQ_DUMP_EDGES, NULL, 0) != -1; - } - - if(!strcasecmp(argv[optind+1], "subnets")) { - return send_ctl_request_cooked(fd, REQ_DUMP_SUBNETS, NULL, 0) != -1; + bool do_graph = false; + int dumps = 1; + + if(!strcasecmp(argv[optind+1], "nodes")) + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + else if(!strcasecmp(argv[optind+1], "edges")) + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + else if(!strcasecmp(argv[optind+1], "subnets")) + sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS); + else if(!strcasecmp(argv[optind+1], "connections")) + sendline(fd, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS); + else if(!strcasecmp(argv[optind+1], "graph")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + do_graph = true; + dumps = 2; + printf("digraph {\n"); + } else { + fprintf(stderr, "Unknown dump type '%s'.\n", argv[optind+1]); + usage(true); + return 1; } - if(!strcasecmp(argv[optind+1], "connections")) { - return send_ctl_request_cooked(fd, REQ_DUMP_CONNECTIONS, NULL, 0) != -1; - } + while(recvline(fd, line, sizeof line)) { + char node1[4096], node2[4096]; + int n = sscanf(line, "%d %d %s to %s", &code, &req, &node1, &node2); + if(n == 2) { + if(do_graph && req == REQ_DUMP_NODES) + continue; + else { + if(do_graph) + printf("}\n"); + return 0; + } + } + if(n < 2) + break; - if(!strcasecmp(argv[optind+1], "graph")) { - return send_ctl_request_cooked(fd, REQ_DUMP_GRAPH, NULL, 0) != -1; + if(!do_graph) + printf("%s\n", line + 5); + else { + if(req == REQ_DUMP_NODES) + printf(" %s [label = \"%s\"];\n", node1, node1); + else + printf(" %s -> %s;\n", node1, node2); + } } - fprintf(stderr, "Unknown dump type '%s'.\n", argv[optind+1]); - usage(true); + fprintf(stderr, "Error receiving dump\n"); return 1; } if(!strcasecmp(argv[optind], "purge")) { - return send_ctl_request_cooked(fd, REQ_PURGE, NULL, 0) != -1; + sendline(fd, "%d %d", CONTROL, REQ_PURGE); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_PURGE || result) { + fprintf(stderr, "Could not purge tinc daemon\n"); + return 1; + } + return 0; } if(!strcasecmp(argv[optind], "debug")) { - int debuglevel; + int debuglevel, origlevel; if(argc != optind + 2) { fprintf(stderr, "Invalid arguments.\n"); return 1; } debuglevel = atoi(argv[optind+1]); - return send_ctl_request_cooked(fd, REQ_SET_DEBUG, &debuglevel, - sizeof debuglevel) != -1; - } - if(!strcasecmp(argv[optind], "retry")) { - return send_ctl_request_cooked(fd, REQ_RETRY, NULL, 0) != -1; - } + sendline(fd, "%d %d %d", CONTROL, REQ_SET_DEBUG, debuglevel); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_SET_DEBUG) { + fprintf(stderr, "Could not purge tinc daemon\n"); + return 1; + } - if(!strcasecmp(argv[optind], "reload")) { - return send_ctl_request_cooked(fd, REQ_RELOAD, NULL, 0) != -1; + fprintf(stderr, "Old level %d, new level %d\n", origlevel, debuglevel); + return 0; } fprintf(stderr, "Unknown command `%s'.\n", argv[optind]); diff --git a/src/tincd.c b/src/tincd.c index 1761dc21..f6cda972 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -78,8 +78,8 @@ static const char *switchuser = NULL; bool use_logfile = false; char *identname = NULL; /* program name for syslog */ -char *controlsocketname = NULL; /* control socket location */ char *logfilename = NULL; /* log file location */ +char *controlcookiename = NULL; char **g_argv; /* a copy of the cmdline arguments */ static int status; @@ -96,7 +96,7 @@ static struct option const long_options[] = { {"chroot", no_argument, NULL, 'R'}, {"user", required_argument, NULL, 'U'}, {"logfile", optional_argument, NULL, 4}, - {"controlsocket", required_argument, NULL, 5}, + {"controlcookie", required_argument, NULL, 5}, {NULL, 0, NULL, 0} }; @@ -117,7 +117,7 @@ static void usage(bool status) { " -n, --net=NETNAME Connect to net NETNAME.\n" " -L, --mlock Lock tinc into main memory.\n" " --logfile[=FILENAME] Write log entries to a logfile.\n" - " --controlsocket=FILENAME Open control socket at FILENAME.\n" + " --controlcookie=FILENAME Write control socket cookie to FILENAME.\n" " --bypass-security Disables meta protocol security, for debugging.\n" " -R, --chroot chroot to NET dir at startup.\n" " -U, --user=USER setuid to given USER at startup.\n" " --help Display this help and exit.\n" @@ -190,7 +190,7 @@ static bool parse_options(int argc, char **argv) { break; case 5: /* open control socket here */ - controlsocketname = xstrdup(optarg); + controlcookiename = xstrdup(optarg); break; case '?': @@ -231,6 +231,8 @@ static void make_names(void) { else xasprintf(&confbase, "%s", installdir); } + if(!controlcookiename) + xasprintf(&controlcookiename, "%s/cookie", confbase); } RegCloseKey(key); if(*installdir) @@ -238,9 +240,6 @@ static void make_names(void) { } #endif - if(!controlsocketname) - xasprintf(&controlsocketname, "%s/run/%s.control/socket", LOCALSTATEDIR, identname); - if(!logfilename) xasprintf(&logfilename, LOCALSTATEDIR "/log/%s.log", identname); @@ -258,7 +257,7 @@ static void make_names(void) { static void free_names() { if (identname) free(identname); if (netname) free(netname); - if (controlsocketname) free(controlsocketname); + if (controlcookiename) free(controlcookiename); if (logfilename) free(logfilename); if (confbase) free(confbase); } @@ -359,9 +358,6 @@ int main(int argc, char **argv) { return 1; } - if(!init_control()) - return 1; - g_argv = argv; init_configuration(&config_tree); @@ -410,6 +406,9 @@ int main2(int argc, char **argv) { if(!setup_network()) goto end; + if(!init_control()) + return 1; + /* Initiate all outgoing connections. */ try_outgoing_connections(); @@ -449,9 +448,7 @@ int main2(int argc, char **argv) { end: logger(LOG_NOTICE, "Terminating"); -#ifndef HAVE_MINGW exit_control(); -#endif crypto_exit(); -- 2.39.2