From d81908d6a22948cf21875a9b80764cd0bb6c60a5 Mon Sep 17 00:00:00 2001 From: Hamish Coleman Date: Sun, 17 Apr 2022 00:19:08 +0100 Subject: [PATCH] Move as much as simply possible from edge_management into shared code --- CMakeLists.txt | 1 + Makefile.in | 3 + doc/ManagementAPI.md | 12 +- src/edge_management.c | 342 +++++------------------------------------- src/management.c | 278 ++++++++++++++++++++++++++++++++++ src/management.h | 100 ++++++++++++ src/strbuf.h | 24 +++ 7 files changed, 450 insertions(+), 310 deletions(-) create mode 100644 src/management.c create mode 100644 src/management.h create mode 100644 src/strbuf.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 80677a5..b8dc6b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,7 @@ add_library(n2n STATIC src/aes.c src/speck.c src/random_numbers.c + src/management.c src/pearson.c src/header_encryption.c src/tuntap_freebsd.c diff --git a/Makefile.in b/Makefile.in index e5a9a1a..d1062ab 100644 --- a/Makefile.in +++ b/Makefile.in @@ -100,8 +100,11 @@ LINT_CCODE=\ src/edge_utils_win32.c \ src/example_edge_embed_quick_edge_init.c \ src/header_encryption.c \ + src/management.c \ + src/management.h \ src/sn_management.c \ src/sn_selection.c \ + src/strbuf.h \ src/transform_cc20.c \ src/transform_null.c \ src/tuntap_freebsd.c \ diff --git a/doc/ManagementAPI.md b/doc/ManagementAPI.md index 3f179f4..b59a553 100644 --- a/doc/ManagementAPI.md +++ b/doc/ManagementAPI.md @@ -1,6 +1,8 @@ # Management API -This document is focused on the machine readable API interfaces. +This document is focused on the machine readable API interfaces. It is a +generic document describing the protocol framing, but not the specific +messages and replies. Both the edge and the supernode provide a management interface UDP port. These interfaces have some documentation on their non machine readable @@ -30,8 +32,7 @@ where this flexibility is easy to provide. Since the API is over UDP, the replies are structured so that each part of the reply is clearly tagged as belonging to one request and there is a clear begin and end marker for the reply. This is expected to support the -future possibilities of pipelined and overlapping transactions as well as -pub/sub asynchronous event channels. +future possibilities of pipelined and overlapping transactions. The replies will also handle some small amount of re-ordering of the packets, but that is not an specific goal of the protocol. @@ -105,6 +106,11 @@ on a given socket. One possible client implementation is a number between 0 and 999, incremented for each request and wrapping around to zero when it is greater than 999. +The client is also expected to use the tag to determine the format of the +data in the reply - since the client requested that data, it also knows what +should be in the matching reply. This is especially important with +asynchronous pub/sub events. + #### Message Flags This subfield is a set of bit flags that are hex-encoded and describe any diff --git a/src/edge_management.c b/src/edge_management.c index d9b6120..e5fb0a0 100644 --- a/src/edge_management.c +++ b/src/edge_management.c @@ -20,97 +20,7 @@ #include "edge_utils_win32.h" #include "strbuf.h" - -enum n2n_mgmt_type { - N2N_MGMT_UNKNOWN = 0, - N2N_MGMT_READ = 1, - N2N_MGMT_WRITE = 2, - N2N_MGMT_SUB = 3, -}; - -/* - * Everything needed to reply to a request - */ -typedef struct mgmt_req { - n2n_edge_t *eee; - enum n2n_mgmt_type type; - char tag[10]; - struct sockaddr_in sender_sock; -} mgmt_req_t; - -/* - * Read/Write handlers are defined in this structure - */ -#define FLAG_WROK 1 -typedef struct mgmt_handler { - int flags; - char *cmd; - char *help; - void (*func)(mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv); -} mgmt_handler_t; - -/* - * Event topic names are defined in this structure - */ -typedef struct mgmt_events { - enum n2n_event_topic topic; - char *cmd; - char *help; -} mgmt_events_t; - -// Lookup the index of matching argv0 in a cmd list -// store index in "Result", or -1 for not found -#define lookup_handler(Result, list, argv0) do { \ - int nr_max = sizeof(list) / sizeof(list[0]); \ - for( Result=0; Result < nr_max; Result++ ) { \ - if(0 == strcmp(list[Result].cmd, argv0)) { \ - break; \ - } \ - } \ - if( Result >= nr_max ) { \ - Result = -1; \ - } \ -} while(0) - -ssize_t send_reply (mgmt_req_t *req, strbuf_t *buf, size_t msg_len) { - // TODO: better error handling (counters?) - return sendto(req->eee->udp_mgmt_sock, buf->str, msg_len, 0, - (struct sockaddr *) &req->sender_sock, sizeof(struct sockaddr_in)); -} - -size_t gen_json_1str (strbuf_t *buf, char *tag, char *_type, char *key, char *val) { - return snprintf(buf->str, buf->size, - "{" - "\"_tag\":\"%s\"," - "\"_type\":\"%s\"," - "\"%s\":\"%s\"}\n", - tag, - _type, - key, - val); -} - -size_t gen_json_1uint (strbuf_t *buf, char *tag, char *_type, char *key, unsigned int val) { - return snprintf(buf->str, buf->size, - "{" - "\"_tag\":\"%s\"," - "\"_type\":\"%s\"," - "\"%s\":%u}\n", - tag, - _type, - key, - val); -} - -static void send_json_1str (mgmt_req_t *req, strbuf_t *buf, char *_type, char *key, char *val) { - size_t msg_len = gen_json_1str(buf, req->tag, _type, key, val); - send_reply(req, buf, msg_len); -} - -static void send_json_1uint (mgmt_req_t *req, strbuf_t *buf, char *_type, char *key, unsigned int val) { - size_t msg_len = gen_json_1uint(buf, req->tag, _type, key, val); - send_reply(req, buf, msg_len); -} +#include "management.h" size_t event_debug (strbuf_t *buf, char *tag, int data0, void *data1) { traceEvent(TRACE_DEBUG, "Unexpected call to event_debug"); @@ -147,31 +57,11 @@ size_t event_peer (strbuf_t *buf, char *tag, int data0, void *data1) { sock_to_cstr(sockbuf, &(peer->sock))); } -static void mgmt_error (mgmt_req_t *req, strbuf_t *buf, char *msg) { - send_json_1str(req, buf, "error", "error", msg); + + } -static void mgmt_stop (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { - - if(req->type==N2N_MGMT_WRITE) { - *req->eee->keep_running = 0; - } - - send_json_1uint(req, buf, "row", "keep_running", *req->eee->keep_running); -} - -static void mgmt_verbose (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { - - if(req->type==N2N_MGMT_WRITE) { - if(argv) { - setTraceLevel(strtoul(argv, NULL, 0)); - } - } - - send_json_1uint(req, buf, "row", "traceLevel", getTraceLevel()); -} - -static void mgmt_communities (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { +static void mgmt_communities (mgmt_req_t *req, strbuf_t *buf) { if(req->eee->conf.header_encryption != HEADER_ENCRYPTION_NONE) { mgmt_error(req, buf, "noaccess"); @@ -181,7 +71,7 @@ static void mgmt_communities (mgmt_req_t *req, strbuf_t *buf, char *argv0, char send_json_1str(req, buf, "row", "community", (char *)req->eee->conf.community_name); } -static void mgmt_supernodes (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { +static void mgmt_supernodes (mgmt_req_t *req, strbuf_t *buf) { size_t msg_len; struct peer_info *peer, *tmpPeer; macstr_t mac_buf; @@ -258,7 +148,7 @@ static void mgmt_edges_row (mgmt_req_t *req, strbuf_t *buf, struct peer_info *pe send_reply(req, buf, msg_len); } -static void mgmt_edges (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { +static void mgmt_edges (mgmt_req_t *req, strbuf_t *buf) { struct peer_info *peer, *tmpPeer; // dump nodes with forwarding through supernodes @@ -272,7 +162,7 @@ static void mgmt_edges (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) } } -static void mgmt_timestamps (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { +static void mgmt_timestamps (mgmt_req_t *req, strbuf_t *buf) { size_t msg_len; msg_len = snprintf(buf->str, buf->size, @@ -290,7 +180,7 @@ static void mgmt_timestamps (mgmt_req_t *req, strbuf_t *buf, char *argv0, char * send_reply(req, buf, msg_len); } -static void mgmt_packetstats (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { +static void mgmt_packetstats (mgmt_req_t *req, strbuf_t *buf) { size_t msg_len; msg_len = snprintf(buf->str, buf->size, @@ -346,20 +236,15 @@ static void mgmt_packetstats (mgmt_req_t *req, strbuf_t *buf, char *argv0, char send_reply(req, buf, msg_len); } -static void mgmt_post_test (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { +static void mgmt_post_test (mgmt_req_t *req, strbuf_t *buf) { send_json_1str(req, buf, "row", "sending", "test"); - mgmt_event_post(N2N_EVENT_TEST, -1, argv); -} - -static void mgmt_unimplemented (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { - - mgmt_error(req, buf, "unimplemented"); + mgmt_event_post(N2N_EVENT_TEST, -1, req->argv); } // Forward define so we can include this in the mgmt_handlers[] table -static void mgmt_help (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv); -static void mgmt_help_events (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv); +static void mgmt_help (mgmt_req_t *req, strbuf_t *buf); +static void mgmt_help_events (mgmt_req_t *req, strbuf_t *buf); static const mgmt_handler_t mgmt_handlers[] = { { .cmd = "reload_communities", .flags = FLAG_WROK, .help = "Reserved for supernode", .func = mgmt_unimplemented}, @@ -384,7 +269,8 @@ static mgmt_req_t mgmt_event_subscribers[] = { }; /* Map topic number to function */ -static const size_t (*mgmt_events[])(strbuf_t *buf, char *tag, int data0, void *data1) = { +// TODO: want this to be const +static mgmt_event_handler_t *mgmt_events[] = { [N2N_EVENT_DEBUG] = event_debug, [N2N_EVENT_TEST] = event_test, [N2N_EVENT_PEER] = event_peer, @@ -400,81 +286,25 @@ static const mgmt_events_t mgmt_event_names[] = { void mgmt_event_post (enum n2n_event_topic topic, int data0, void *data1) { mgmt_req_t *debug = &mgmt_event_subscribers[N2N_EVENT_DEBUG]; mgmt_req_t *sub = &mgmt_event_subscribers[topic]; + mgmt_event_handler_t *fn = mgmt_events[topic]; - traceEvent(TRACE_DEBUG, "post topic=%i data0=%i", topic, data0); - - if( sub->type != N2N_MGMT_SUB && debug->type != N2N_MGMT_SUB) { - // If neither of this topic or the debug topic have a subscriber - // then we dont need to do any work - return; - } - - char buf_space[100]; - strbuf_t *buf; - STRBUF_INIT(buf, buf_space); - - char *tag; - if(sub->type == N2N_MGMT_SUB) { - tag = sub->tag; - } else { - tag = debug->tag; - } - - size_t msg_len = mgmt_events[topic](buf, tag, data0, data1); - - if(sub->type == N2N_MGMT_SUB) { - send_reply(sub, buf, msg_len); - } - if(debug->type == N2N_MGMT_SUB) { - send_reply(debug, buf, msg_len); - } + mgmt_event_post2 (topic, data0, data1, debug, sub, fn); } -static void mgmt_help_events (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { - size_t msg_len; - +static void mgmt_help_events (mgmt_req_t *req, strbuf_t *buf) { int i; int nr_handlers = sizeof(mgmt_event_names) / sizeof(mgmt_events_t); for( i=0; i < nr_handlers; i++ ) { int topic = mgmt_event_names[i].topic; mgmt_req_t *sub = &mgmt_event_subscribers[topic]; - char host[40]; - char serv[6]; - if((sub->type != N2N_MGMT_SUB) || - getnameinfo((struct sockaddr *)&sub->sender_sock, sizeof(sub->sender_sock), - host, sizeof(host), - serv, sizeof(serv), - NI_NUMERICHOST|NI_NUMERICSERV) != 0) { - host[0] = '?'; - host[1] = 0; - serv[0] = '?'; - serv[1] = 0; - } - - // TODO: handle a topic with no subscribers more cleanly - - msg_len = snprintf(buf->str, buf->size, - "{" - "\"_tag\":\"%s\"," - "\"_type\":\"row\"," - "\"topic\":\"%s\"," - "\"tag\":\"%s\"," - "\"sockaddr\":\"%s:%s\"," - "\"help\":\"%s\"}\n", - req->tag, - mgmt_event_names[i].cmd, - sub->tag, - host, serv, - mgmt_event_names[i].help); - - send_reply(req, buf, msg_len); + mgmt_help_events_row(req, buf, sub, mgmt_event_names[i].cmd, mgmt_event_names[i].help); } } -static void mgmt_help (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) { - size_t msg_len; - +// TODO: want to keep the mgmt_handlers defintion const static, otherwise +// this whole function could be shared +static void mgmt_help (mgmt_req_t *req, strbuf_t *buf) { /* * Even though this command is readonly, we deliberately do not check * the type - allowing help replies to both read and write requests @@ -483,60 +313,14 @@ static void mgmt_help (mgmt_req_t *req, strbuf_t *buf, char *argv0, char *argv) int i; int nr_handlers = sizeof(mgmt_handlers) / sizeof(mgmt_handler_t); for( i=0; i < nr_handlers; i++ ) { - msg_len = snprintf(buf->str, buf->size, - "{" - "\"_tag\":\"%s\"," - "\"_type\":\"row\"," - "\"cmd\":\"%s\"," - "\"help\":\"%s\"}\n", - req->tag, - mgmt_handlers[i].cmd, - mgmt_handlers[i].help); - - send_reply(req, buf, msg_len); + mgmt_help_row(req, buf, mgmt_handlers[i].cmd, mgmt_handlers[i].help); } } -/* - * Check if the user is authorised for this command. - * - this should be more configurable! - * - for the moment we use some simple heuristics: - * Reads are not dangerous, so they are simply allowed - * Writes are possibly dangerous, so they need a fake password - */ -static int mgmt_auth (mgmt_req_t *req, char *auth, char *argv0, char *argv) { - - if(auth) { - /* If we have an auth key, it must match */ - if(req->eee->conf.mgmt_password_hash == pearson_hash_64((uint8_t*)auth, strlen(auth))) { - return 1; - } - return 0; - } - /* if we dont have an auth key, we can still read */ - if(req->type == N2N_MGMT_READ) { - return 1; - } - - return 0; -} - static void handleMgmtJson (mgmt_req_t *req, char *udp_buf, const int recvlen) { strbuf_t *buf; char cmdlinebuf[80]; - char *typechar; - char *options; - char *argv0; - char *argv; - char *flagstr; - int flags; - char *auth; - - /* Initialise the tag field until we extract it from the cmdline */ - req->tag[0] = '-'; - req->tag[1] = '1'; - req->tag[2] = '\0'; /* save a copy of the commandline before we reuse the udp_buf */ strncpy(cmdlinebuf, udp_buf, sizeof(cmdlinebuf)-1); @@ -547,71 +331,11 @@ static void handleMgmtJson (mgmt_req_t *req, char *udp_buf, const int recvlen) { /* we reuse the buffer already on the stack for all our strings */ STRBUF_INIT(buf, udp_buf); - typechar = strtok(cmdlinebuf, " \r\n"); - if(!typechar) { - /* should not happen */ - mgmt_error(req, buf, "notype"); - return; - } - if(*typechar == 'r') { - req->type=N2N_MGMT_READ; - } else if(*typechar == 'w') { - req->type=N2N_MGMT_WRITE; - } else if(*typechar == 's') { - req->type=N2N_MGMT_SUB; - } else { - mgmt_error(req, buf, "badtype"); - return; - } - - /* Extract the tag to use in all reply packets */ - options = strtok(NULL, " \r\n"); - if(!options) { - mgmt_error(req, buf, "nooptions"); - return; - } - - argv0 = strtok(NULL, " \r\n"); - if(!argv0) { - mgmt_error(req, buf, "nocmd"); - return; - } - - /* - * The entire rest of the line is the argv. We apply no processing - * or arg separation so that the cmd can use it however it needs. - */ - argv = strtok(NULL, "\r\n"); - - /* - * There might be an auth token mixed in with the tag - */ - char *tagp = strtok(options, ":"); - strncpy(req->tag, tagp, sizeof(req->tag)-1); - req->tag[sizeof(req->tag)-1] = '\0'; - - flagstr = strtok(NULL, ":"); - if(flagstr) { - flags = strtoul(flagstr, NULL, 16); - } else { - flags = 0; - } - - /* Only 1 flag bit defined at the moment - "auth option present" */ - if(flags & 1) { - auth = strtok(NULL, ":"); - } else { - auth = NULL; - } - - if(!mgmt_auth(req, auth, argv0, argv)) { - mgmt_error(req, buf, "badauth"); - return; - } + mgmt_req_init2(req, buf, (char *)&cmdlinebuf); if(req->type == N2N_MGMT_SUB) { int handler; - lookup_handler(handler, mgmt_event_names, argv0); + lookup_handler(handler, mgmt_event_names, req->argv0); if(handler == -1) { mgmt_error(req, buf, "unknowntopic"); return; @@ -620,18 +344,18 @@ static void handleMgmtJson (mgmt_req_t *req, char *udp_buf, const int recvlen) { int topic = mgmt_event_names[handler].topic; if(mgmt_event_subscribers[topic].type == N2N_MGMT_SUB) { send_json_1str(&mgmt_event_subscribers[topic], buf, - "unsubscribed", "topic", argv0); - send_json_1str(req, buf, "replacing", "topic", argv0); + "unsubscribed", "topic", req->argv0); + send_json_1str(req, buf, "replacing", "topic", req->argv0); } memcpy(&mgmt_event_subscribers[topic], req, sizeof(*req)); - send_json_1str(req, buf, "subscribe", "topic", argv0); + send_json_1str(req, buf, "subscribe", "topic", req->argv0); return; } int handler; - lookup_handler(handler, mgmt_handlers, argv0); + lookup_handler(handler, mgmt_handlers, req->argv0); if(handler == -1) { mgmt_error(req, buf, "unknowncmd"); return; @@ -648,11 +372,11 @@ static void handleMgmtJson (mgmt_req_t *req, char *udp_buf, const int recvlen) { * that make our JSON invalid. * - do we care? */ - send_json_1str(req, buf, "begin", "cmd", argv0); + send_json_1str(req, buf, "begin", "cmd", req->argv0); - mgmt_handlers[handler].func(req, buf, argv0, argv); + mgmt_handlers[handler].func(req, buf); - send_json_1str(req, buf, "end", "cmd", argv0); + send_json_1str(req, buf, "end", "cmd", req->argv0); return; } @@ -680,7 +404,11 @@ void readFromMgmtSocket (n2n_edge_t *eee) { uint32_t num = 0; selection_criterion_str_t sel_buf; + req.sss = NULL; req.eee = eee; + req.mgmt_sock = eee->udp_mgmt_sock; + req.keep_running = eee->keep_running; + req.mgmt_password_hash = eee->conf.mgmt_password_hash; now = time(NULL); i = sizeof(req.sender_sock); diff --git a/src/management.c b/src/management.c new file mode 100644 index 0000000..eaafc7f --- /dev/null +++ b/src/management.c @@ -0,0 +1,278 @@ +/* + * Common routines shared between the management interfaces + * + */ + +#include +#include +#include + +// TODO: move logging defs in their own header and include that +void setTraceLevel (int level); +int getTraceLevel (); + +#include +#include "management.h" + +#include "n2n.h" // for traceEvent and friends + +ssize_t send_reply (mgmt_req_t *req, strbuf_t *buf, size_t msg_len) { + // TODO: better error handling (counters?) + return sendto(req->mgmt_sock, buf->str, msg_len, 0, + (struct sockaddr *) &req->sender_sock, sizeof(struct sockaddr_in)); +} + +size_t gen_json_1str (strbuf_t *buf, char *tag, char *_type, char *key, char *val) { + return snprintf(buf->str, buf->size, + "{" + "\"_tag\":\"%s\"," + "\"_type\":\"%s\"," + "\"%s\":\"%s\"}\n", + tag, + _type, + key, + val); +} + +size_t gen_json_1uint (strbuf_t *buf, char *tag, char *_type, char *key, unsigned int val) { + return snprintf(buf->str, buf->size, + "{" + "\"_tag\":\"%s\"," + "\"_type\":\"%s\"," + "\"%s\":%u}\n", + tag, + _type, + key, + val); +} + +void send_json_1str (mgmt_req_t *req, strbuf_t *buf, char *_type, char *key, char *val) { + size_t msg_len = gen_json_1str(buf, req->tag, _type, key, val); + send_reply(req, buf, msg_len); +} + +void send_json_1uint (mgmt_req_t *req, strbuf_t *buf, char *_type, char *key, unsigned int val) { + size_t msg_len = gen_json_1uint(buf, req->tag, _type, key, val); + send_reply(req, buf, msg_len); +} + +void mgmt_error (mgmt_req_t *req, strbuf_t *buf, char *msg) { + send_json_1str(req, buf, "error", "error", msg); +} + +void mgmt_stop (mgmt_req_t *req, strbuf_t *buf) { + + if(req->type==N2N_MGMT_WRITE) { + *req->keep_running = 0; + } + + send_json_1uint(req, buf, "row", "keep_running", *req->keep_running); +} + +void mgmt_verbose (mgmt_req_t *req, strbuf_t *buf) { + + if(req->type==N2N_MGMT_WRITE) { + if(req->argv) { + setTraceLevel(strtoul(req->argv, NULL, 0)); + } + } + + send_json_1uint(req, buf, "row", "traceLevel", getTraceLevel()); +} + +void mgmt_unimplemented (mgmt_req_t *req, strbuf_t *buf) { + + mgmt_error(req, buf, "unimplemented"); +} + +void mgmt_event_post2 (enum n2n_event_topic topic, int data0, void *data1, mgmt_req_t *debug, mgmt_req_t *sub, mgmt_event_handler_t fn) { + traceEvent(TRACE_DEBUG, "post topic=%i data0=%i", topic, data0); + + if( sub->type != N2N_MGMT_SUB && debug->type != N2N_MGMT_SUB) { + // If neither of this topic or the debug topic have a subscriber + // then we dont need to do any work + return; + } + + char buf_space[100]; + strbuf_t *buf; + STRBUF_INIT(buf, buf_space); + + char *tag; + if(sub->type == N2N_MGMT_SUB) { + tag = sub->tag; + } else { + tag = debug->tag; + } + + size_t msg_len = fn(buf, tag, data0, data1); + + if(sub->type == N2N_MGMT_SUB) { + send_reply(sub, buf, msg_len); + } + if(debug->type == N2N_MGMT_SUB) { + send_reply(debug, buf, msg_len); + } + // TODO: + // - ideally, we would detect that the far end has gone away and + // set the ->type back to N2N_MGMT_UNKNOWN, but we are not using + // a connected socket, so that is difficult + // - failing that, we should require the client to send an unsubscribe + // and provide a manual unsubscribe +} + +void mgmt_help_row (mgmt_req_t *req, strbuf_t *buf, char *cmd, char *help) { + size_t msg_len; + + msg_len = snprintf(buf->str, buf->size, + "{" + "\"_tag\":\"%s\"," + "\"_type\":\"row\"," + "\"cmd\":\"%s\"," + "\"help\":\"%s\"}\n", + req->tag, + cmd, + help); + + send_reply(req, buf, msg_len); +} + +void mgmt_help_events_row (mgmt_req_t *req, strbuf_t *buf, mgmt_req_t *sub, char *cmd, char *help) { + size_t msg_len; + char host[40]; + char serv[6]; + + if((sub->type != N2N_MGMT_SUB) || + getnameinfo((struct sockaddr *)&sub->sender_sock, sizeof(sub->sender_sock), + host, sizeof(host), + serv, sizeof(serv), + NI_NUMERICHOST|NI_NUMERICSERV) != 0) { + host[0] = '?'; + host[1] = 0; + serv[0] = '?'; + serv[1] = 0; + } + + // TODO: handle a topic with no subscribers more cleanly + + msg_len = snprintf(buf->str, buf->size, + "{" + "\"_tag\":\"%s\"," + "\"_type\":\"row\"," + "\"topic\":\"%s\"," + "\"tag\":\"%s\"," + "\"sockaddr\":\"%s:%s\"," + "\"help\":\"%s\"}\n", + req->tag, + cmd, + sub->tag, + host, serv, + help); + + send_reply(req, buf, msg_len); +} + +// TODO: work out a method to keep the mgmt_handlers defintion const static, +// and then import the shared mgmt_help () definition to this file + +/* + * Check if the user is authorised for this command. + * - this should be more configurable! + * - for the moment we use some simple heuristics: + * Reads are not dangerous, so they are simply allowed + * Writes are possibly dangerous, so they need a fake password + */ +int mgmt_auth (mgmt_req_t *req, char *auth) { + + if(auth) { + /* If we have an auth key, it must match */ + if(req->mgmt_password_hash == pearson_hash_64((uint8_t*)auth, strlen(auth))) { + return 1; + } + return 0; + } + /* if we dont have an auth key, we can still read */ + if(req->type == N2N_MGMT_READ) { + return 1; + } + + return 0; +} + +/* + * Handle the common and shred parts of the mgmt_req_t initialisation + */ +void mgmt_req_init2 (mgmt_req_t *req, strbuf_t *buf, char *cmdline) { + char *typechar; + char *options; + char *flagstr; + int flags; + char *auth; + + /* Initialise the tag field until we extract it from the cmdline */ + req->tag[0] = '-'; + req->tag[1] = '1'; + req->tag[2] = '\0'; + + typechar = strtok(cmdline, " \r\n"); + if(!typechar) { + /* should not happen */ + mgmt_error(req, buf, "notype"); + return; + } + if(*typechar == 'r') { + req->type=N2N_MGMT_READ; + } else if(*typechar == 'w') { + req->type=N2N_MGMT_WRITE; + } else if(*typechar == 's') { + req->type=N2N_MGMT_SUB; + } else { + mgmt_error(req, buf, "badtype"); + return; + } + + /* Extract the tag to use in all reply packets */ + options = strtok(NULL, " \r\n"); + if(!options) { + mgmt_error(req, buf, "nooptions"); + return; + } + + req->argv0 = strtok(NULL, " \r\n"); + if(!req->argv0) { + mgmt_error(req, buf, "nocmd"); + return; + } + + /* + * The entire rest of the line is the argv. We apply no processing + * or arg separation so that the cmd can use it however it needs. + */ + req->argv = strtok(NULL, "\r\n"); + + /* + * There might be an auth token mixed in with the tag + */ + char *tagp = strtok(options, ":"); + strncpy(req->tag, tagp, sizeof(req->tag)-1); + req->tag[sizeof(req->tag)-1] = '\0'; + + flagstr = strtok(NULL, ":"); + if(flagstr) { + flags = strtoul(flagstr, NULL, 16); + } else { + flags = 0; + } + + /* Only 1 flag bit defined at the moment - "auth option present" */ + if(flags & 1) { + auth = strtok(NULL, ":"); + } else { + auth = NULL; + } + + if(!mgmt_auth(req, auth)) { + mgmt_error(req, buf, "badauth"); + return; + } +} diff --git a/src/management.h b/src/management.h new file mode 100644 index 0000000..80a829d --- /dev/null +++ b/src/management.h @@ -0,0 +1,100 @@ +/* + * Internal interface definitions for the management interfaces + * + * This header is not part of the public library API and is thus not in + * the public include folder + */ + +#ifndef MANAGEMENT_H +#define MANAGEMENT_H 1 + +#include // For the n2n_edge_t and n2n_sn_t defs + +#include "strbuf.h" + +enum n2n_mgmt_type { + N2N_MGMT_UNKNOWN = 0, + N2N_MGMT_READ = 1, + N2N_MGMT_WRITE = 2, + N2N_MGMT_SUB = 3, +}; + +/* + * Everything needed to reply to a request + * + * TODO: + * - one day, we might be able to merge the sss and eee members + * - once eee and sss are merged, some fields should migrate back into it: + * - mgmt_sock + * - keep_running + * - mgmt_password_hash + */ +typedef struct mgmt_req { + n2n_sn_t *sss; + n2n_edge_t *eee; + int mgmt_sock; // socket replies come from + int *keep_running; + uint64_t mgmt_password_hash; + enum n2n_mgmt_type type; + char *argv0; + char *argv; + char tag[10]; + struct sockaddr_in sender_sock; +} mgmt_req_t; + +/* + * Read/Write handlers are defined in this structure + * TODO: DRY + */ +#define FLAG_WROK 1 +typedef struct mgmt_handler { + int flags; + char *cmd; + char *help; + void (*func)(mgmt_req_t *req, strbuf_t *buf); +} mgmt_handler_t; + +/* + * Event topic names are defined in this structure + */ +typedef struct mgmt_events { + enum n2n_event_topic topic; + char *cmd; + char *help; +} mgmt_events_t; + +typedef size_t (mgmt_event_handler_t)(strbuf_t *buf, char *tag, int data0, void *data1); + +// Lookup the index of matching argv0 in a cmd list +// store index in "Result", or -1 for not found +#define lookup_handler(Result, list, argv0) do { \ + int nr_max = sizeof(list) / sizeof(list[0]); \ + for( Result=0; Result < nr_max; Result++ ) { \ + if(0 == strcmp(list[Result].cmd, argv0)) { \ + break; \ + } \ + } \ + if( Result >= nr_max ) { \ + Result = -1; \ + } \ +} while(0) + +ssize_t send_reply (mgmt_req_t *req, strbuf_t *buf, size_t msg_len); +size_t gen_json_1str (strbuf_t *buf, char *tag, char *_type, char *key, char *val); +size_t gen_json_1uint (strbuf_t *buf, char *tag, char *_type, char *key, unsigned int val); +void send_json_1str (mgmt_req_t *req, strbuf_t *buf, char *_type, char *key, char *val); +void send_json_1uint (mgmt_req_t *req, strbuf_t *buf, char *_type, char *key, unsigned int val); + +void mgmt_error (mgmt_req_t *req, strbuf_t *buf, char *msg); + +void mgmt_stop (mgmt_req_t *req, strbuf_t *buf); +void mgmt_verbose (mgmt_req_t *req, strbuf_t *buf); +void mgmt_unimplemented (mgmt_req_t *req, strbuf_t *buf); + +void mgmt_event_post2 (enum n2n_event_topic topic, int data0, void *data1, mgmt_req_t *debug, mgmt_req_t *sub, mgmt_event_handler_t fn); +void mgmt_help_row (mgmt_req_t *req, strbuf_t *buf, char *cmd, char *help); +void mgmt_help_events_row (mgmt_req_t *req, strbuf_t *buf, mgmt_req_t *sub, char *cmd, char *help); +int mgmt_auth (mgmt_req_t *req, char *auth); +void mgmt_req_init2 (mgmt_req_t *req, strbuf_t *buf, char *cmdline); + +#endif diff --git a/src/strbuf.h b/src/strbuf.h new file mode 100644 index 0000000..1bf2c4b --- /dev/null +++ b/src/strbuf.h @@ -0,0 +1,24 @@ +/* + * Internal interface definitions for the strbuf abstrction + * + * This header is not part of the public library API and is thus not in + * the public include folder + */ + +#ifndef STRBUF_H +#define STRBUF_H 1 + +typedef struct strbuf { + size_t size; + char str[]; +} strbuf_t; + +// Initialise the strbuf pointer buf to point at the storage area p +// p must be a known sized object +#define STRBUF_INIT(buf,p) do { \ + buf = (void *)p; \ + buf->size = sizeof(*p) - sizeof(size_t); \ +} while(0) + + +#endif