Drop localisation and checkpoint tracing in files not covered by the merge.
[tinc] / src / control.c
index 2c5d4be..7385214 100644 (file)
@@ -25,6 +25,7 @@
 #include "conf.h"
 #include "control.h"
 #include "control_common.h"
+#include "graph.h"
 #include "logger.h"
 #include "xalloc.h"
 
@@ -35,20 +36,21 @@ extern char *controlsocketname;
 
 static void handle_control_data(struct bufferevent *event, void *data) {
        tinc_ctl_request_t req;
-       size_t size;
        tinc_ctl_request_t res;
        struct evbuffer *res_data = NULL;
+       void *req_data;
 
-       if(EVBUFFER_LENGTH(event->input) < sizeof(tinc_ctl_request_t))
+       if(EVBUFFER_LENGTH(event->input) < sizeof req)
                return;
 
        /* Copy the structure to ensure alignment */
-       memcpy(&req, EVBUFFER_DATA(event->input), sizeof(tinc_ctl_request_t));
+       memcpy(&req, EVBUFFER_DATA(event->input), sizeof req);
 
        if(EVBUFFER_LENGTH(event->input) < req.length)
                return;
+       req_data = EVBUFFER_DATA(event->input) + sizeof req;
 
-       if(req.length < sizeof(tinc_ctl_request_t))
+       if(req.length < sizeof req)
                goto failure;
 
        memset(&res, 0, sizeof res);
@@ -56,54 +58,85 @@ static void handle_control_data(struct bufferevent *event, void *data) {
        res.id = req.id;
 
        res_data = evbuffer_new();
-       if (res_data == NULL) {
+       if(res_data == NULL) {
                res.res_errno = ENOMEM;
                goto respond;
        }
 
        if(req.type == REQ_STOP) {
-               logger(LOG_NOTICE, _("Got '%s' command"), "stop");
+               logger(LOG_NOTICE, "Got '%s' command", "stop");
                event_loopexit(NULL);
                goto respond;
        }
 
        if(req.type == REQ_DUMP_NODES) {
-               logger(LOG_NOTICE, _("Got '%s' command"), "dump nodes");
+               logger(LOG_NOTICE, "Got '%s' command", "dump nodes");
                res.res_errno = dump_nodes(res_data);
                goto respond;
        }
 
        if(req.type == REQ_DUMP_EDGES) {
-               logger(LOG_NOTICE, _("Got '%s' command"), "dump edges");
+               logger(LOG_NOTICE, "Got '%s' command", "dump edges");
                res.res_errno = dump_edges(res_data);
                goto respond;
        }
 
        if(req.type == REQ_DUMP_SUBNETS) {
-               logger(LOG_NOTICE, _("Got '%s' command"), "dump subnets");
+               logger(LOG_NOTICE, "Got '%s' command", "dump subnets");
                res.res_errno = dump_subnets(res_data);
                goto respond;
        }
 
        if(req.type == REQ_DUMP_CONNECTIONS) {
-               logger(LOG_NOTICE, _("Got '%s' command"), "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");
+               logger(LOG_NOTICE, "Got '%s' command", "dump graph");
                res.res_errno = dump_graph(res_data);
                goto respond;
        }
 
        if(req.type == REQ_PURGE) {
-               logger(LOG_NOTICE, _("Got '%s' command"), "purge");
+               logger(LOG_NOTICE, "Got '%s' command", "purge");
                purge();
                goto respond;
        }
 
-       logger(LOG_DEBUG, _("Malformed control command received"));
+       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;
+               }
+               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:
@@ -120,7 +153,7 @@ respond:
        return;
 
 failure:
-       logger(LOG_INFO, _("Closing control socket on error"));
+       logger(LOG_INFO, "Closing control socket on error");
        evbuffer_free(res_data);
        close(event->ev_read.ev_fd);
        splay_delete(control_socket_tree, event);
@@ -128,9 +161,9 @@ failure:
 
 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"));
+               logger(LOG_DEBUG, "Control socket connection closed by peer");
        else
-               logger(LOG_DEBUG, _("Error while reading from control socket: %s"), strerror(errno));
+               logger(LOG_DEBUG, "Error while reading from control socket: %s", strerror(errno));
 
        close(event->ev_read.ev_fd);
        splay_delete(control_socket_tree, event);
@@ -144,23 +177,24 @@ static void handle_new_control_socket(int fd, short events, void *data) {
        newfd = accept(fd, NULL, NULL);
 
        if(newfd < 0) {
-               logger(LOG_ERR, _("Accepting a new connection failed: %s"), strerror(errno));
+               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));
+               logger(LOG_ERR, "Could not create bufferevent for new control connection: %s", strerror(errno));
                close(newfd);
                return;
        }
 
        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"),
+                          "Cannot send greeting for new control connection: %s",
                           strerror(errno));
                bufferevent_free(ev);
                close(newfd);
@@ -170,7 +204,7 @@ static void handle_new_control_socket(int fd, short events, void *data) {
        bufferevent_enable(ev, EV_READ);
        splay_insert(control_socket_tree, ev);
 
-       logger(LOG_DEBUG, _("Control socket connection accepted"));
+       logger(LOG_DEBUG, "Control socket connection accepted");
 }
 
 static int control_compare(const struct event *a, const struct event *b) {
@@ -180,10 +214,11 @@ static int control_compare(const struct event *a, const struct event *b) {
 bool init_control() {
        int result;
        struct sockaddr_un addr;
+       char *lastslash;
 
        if(strlen(controlsocketname) >= sizeof addr.sun_path) {
-               logger(LOG_ERR, _("Control socket filename too long!"));
-               return false;
+               logger(LOG_ERR, "Control socket filename too long!");
+               goto bail;
        }
 
        memset(&addr, 0, sizeof addr);
@@ -193,47 +228,81 @@ bool init_control() {
        control_socket = socket(PF_UNIX, SOCK_STREAM, 0);
 
        if(control_socket < 0) {
-               logger(LOG_ERR, _("Creating UNIX socket failed: %s"), strerror(errno));
-               return false;
+               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;
        }
 
-       //unlink(controlsocketname);
        result = bind(control_socket, (struct sockaddr *)&addr, sizeof addr);
-       
+
        if(result < 0 && errno == EADDRINUSE) {
                result = connect(control_socket, (struct sockaddr *)&addr, sizeof addr);
                if(result < 0) {
-                       logger(LOG_WARNING, _("Removing old control socket."));
+                       logger(LOG_WARNING, "Removing old control socket.");
                        unlink(controlsocketname);
                        result = bind(control_socket, (struct sockaddr *)&addr, sizeof addr);
                } else {
-                       close(control_socket);
                        if(netname)
-                               logger(LOG_ERR, _("Another tincd is already running for net `%s'."), netname);
+                               logger(LOG_ERR, "Another tincd is already running for net `%s'.", netname);
                        else
-                               logger(LOG_ERR, _("Another tincd is already running."));
-                       return false;
+                               logger(LOG_ERR, "Another tincd is already running.");
+                       goto bail;
                }
        }
 
        if(result < 0) {
-               logger(LOG_ERR, _("Can't bind to %s: %s\n"), controlsocketname, strerror(errno));
-               close(control_socket);
-               return false;
+               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\n"), controlsocketname, strerror(errno));
-               close(control_socket);
-               return false;
+               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) {
+               close(control_socket);
+               control_socket = -1;
+       }
+       return false;
 }
 
 void exit_control() {