#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. */
        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) {
 
                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"
 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__ */
 
 */
 
 #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);
 }
 
 
 extern bool init_control();
 extern void exit_control();
+extern char controlcookie[];
 
 #endif
 
 #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,
 
 #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
 
 #include "system.h"
 
 #include "splay_tree.h"
+#include "control_common.h"
 #include "edge.h"
 #include "logger.h"
 #include "netutl.h"
        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;
                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);
 }
 
 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__ */
 
        }
 }
 
-/* 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();
 
 extern void graph(void);
 extern void mst_kruskal(void);
 extern void sssp_bfs(void);
-extern int dump_graph(struct evbuffer *);
 
 #endif /* __TINC_GRAPH_H__ */
 
 
 #include "system.h"
 
+#include "control_common.h"
 #include "splay_tree.h"
 #include "logger.h"
 #include "net.h"
        }
 }
 
-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);
 }
 
 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__ */
 
                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 */
                "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;
 
        ADD_EDGE, DEL_EDGE,
        KEY_CHANGED, REQ_KEY, ANS_KEY,
        PACKET,
+       CONTROL,
        LAST                                            /* Guardian for the highest request number */
 } request_t;
 
 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__ */
 
 #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"
                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)) {
 
 #include "system.h"
 
 #include "splay_tree.h"
+#include "control_common.h"
 #include "device.h"
 #include "logger.h"
 #include "net.h"
 
 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;
        }
 
                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;
                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);
 }
 
 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__ */
 
 /* 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;
 
                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"
                                break;
 
                        case 5:                                 /* open control socket here */
-                               controlsocketname = xstrdup(optarg);
+                               controlcookiename = xstrdup(optarg);
                                break;
 
                        case '?':
 static bool keygen(int bits) {
        rsa_t key;
        FILE *f;
-       char *name = NULL;
        char *filename;
 
        fprintf(stderr, "Generating %d bits keys:\n", bits);
                                        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)
        }
 }
 
-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];
 
         * 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) {
        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")) {
                        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]);
 
 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;
        {"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}
 };
 
                                "  -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"
                                break;
 
                        case 5:                                 /* open control socket here */
-                               controlsocketname = xstrdup(optarg);
+                               controlcookiename = xstrdup(optarg);
                                break;
 
                        case '?':
                                else
                                        xasprintf(&confbase, "%s", installdir);
                        }
+                       if(!controlcookiename)
+                               xasprintf(&controlcookiename, "%s/cookie", confbase);
                }
                RegCloseKey(key);
                if(*installdir)
        }
 #endif
 
-       if(!controlsocketname)
-               xasprintf(&controlsocketname, "%s/run/%s.control/socket", LOCALSTATEDIR, identname);
-
        if(!logfilename)
                xasprintf(&logfilename, LOCALSTATEDIR "/log/%s.log", identname);
 
 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);
 }
                return 1;
        }
 
-       if(!init_control())
-               return 1;
-
        g_argv = argv;
 
        init_configuration(&config_tree);
        if(!setup_network())
                goto end;
 
+       if(!init_control())
+               return 1;
+
        /* Initiate all outgoing connections. */
 
        try_outgoing_connections();
 end:
        logger(LOG_NOTICE, "Terminating");
 
-#ifndef HAVE_MINGW
        exit_control();
-#endif
 
        crypto_exit();