bpf_skb_utils.h: add missing include to fix build against newer kernel headers
[project/unetd.git] / cli.c
diff --git a/cli.c b/cli.c
index 371522097c42ccb4747e025506814ace4ca406e3..e3654f60bc7e5231a5e6258bc2b3d451fda30cb5 100644 (file)
--- a/cli.c
+++ b/cli.c
@@ -5,19 +5,36 @@
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
 #include <fcntl.h>
+#include <errno.h>
 #include <libubox/utils.h>
+#include <libubox/uloop.h>
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
 #include "edsign.h"
 #include "ed25519.h"
 #include "curve25519.h"
 #include "auth-data.h"
+#include "pex-msg.h"
 
+static uint8_t peerkey[EDSIGN_PUBLIC_KEY_SIZE];
 static uint8_t pubkey[EDSIGN_PUBLIC_KEY_SIZE];
 static uint8_t seckey[EDSIGN_PUBLIC_KEY_SIZE];
+static void *net_data;
+static size_t net_data_len;
+static uint64_t net_data_version;
+static struct blob_attr *net_data_hosts;
+static uint64_t req_id;
+static struct blob_buf b;
 static FILE *out_file;
+static bool quiet;
+static bool sync_done;
 static enum {
        CMD_UNKNOWN,
        CMD_GENERATE,
@@ -25,8 +42,17 @@ static enum {
        CMD_HOST_PUBKEY,
        CMD_VERIFY,
        CMD_SIGN,
+       CMD_DOWNLOAD,
+       CMD_UPLOAD,
 } cmd;
 
+#define INFO(...)                                      \
+       do {                                            \
+               if (quiet)                              \
+                       break;                          \
+               fprintf(stderr, ##__VA_ARGS__);         \
+       } while (0)
+
 static void print_key(const uint8_t *key)
 {
        char keystr[B64_ENCODE_LEN(EDSIGN_PUBLIC_KEY_SIZE)];
@@ -43,18 +69,243 @@ static int usage(const char *progname)
                "Commands:\n"
                "       -S                      Sign file\n"
                "       -V                      Verify file\n"
-               "       -P                      Get pulic signing key from secret key\n"
-               "       -H                      Get pulic host key from secret key\n"
+               "       -P                      Get public signing key from secret key\n"
+               "       -H                      Get public host key from secret key\n"
                "       -G                      Generate new private key\n"
+               "       -D <host>[:<port>]      Download network data from unetd\n"
+               "       -U <host>[:<port>]      Upload network data to unetd\n"
                "\n"
                "Options:\n"
+               "       -q:                     Quiet mode - suppress error/info messages\n"
                "       -o <file>:              Set output file to <file> (defaults to stdout)\n"
                "       -k <keyfile>|-:         Set public key from file or stdin\n"
                "       -K <keyfile>|-:         Set secret key from file or stdin\n"
+               "       -h <keyfile>|-          Set peer private key from file or stdin\n"
+               "                               (for network data down-/upload)\n"
                "\n", progname);
        return 1;
 }
 
+static void pex_timeout(struct uloop_timeout *timeout)
+{
+       uloop_end();
+}
+
+static void
+pex_recv_update_response(const uint8_t *data, size_t len, enum pex_opcode op)
+{
+       int net_data_len = 0;
+       void *net_data;
+
+       net_data = pex_msg_update_response_recv(data, len, op, &net_data_len, NULL);
+       if (net_data_len < 0)
+               goto out;
+
+       if (!net_data)
+               return;
+
+       if (cmd == CMD_DOWNLOAD) {
+               fwrite(net_data, net_data_len, 1, out_file);
+               sync_done = true;
+       }
+
+       free(net_data);
+
+out:
+       if (cmd == CMD_DOWNLOAD)
+               uloop_end();
+}
+
+static bool
+pex_get_pubkey(uint8_t *pubkey, const uint8_t *id)
+{
+       static const struct blobmsg_policy policy = { "key", BLOBMSG_TYPE_STRING };
+       struct blob_attr *cur, *key;
+       int rem;
+
+       blobmsg_for_each_attr(cur, net_data_hosts, rem) {
+               const char *keystr;
+
+               blobmsg_parse(&policy, 1, &key, blobmsg_data(cur), blobmsg_len(cur));
+
+               if (!key)
+                       continue;
+
+               keystr = blobmsg_get_string(key);
+               if (b64_decode(keystr, pubkey, CURVE25519_KEY_SIZE) != CURVE25519_KEY_SIZE)
+                       continue;
+
+               if (!memcmp(pubkey, id, PEX_ID_LEN))
+                       return true;
+       }
+
+       return false;
+}
+
+static void
+pex_handle_update_request(struct sockaddr_in6 *addr, const uint8_t *id, void *data, size_t len)
+{
+       struct pex_msg_update_send_ctx ctx = {};
+       static uint8_t empty_key[EDSIGN_PUBLIC_KEY_SIZE] = {};
+       uint8_t peerpubkey[EDSIGN_PUBLIC_KEY_SIZE];
+       bool done = false;
+
+       if (!pex_get_pubkey(peerpubkey, id)) {
+               INFO("Could not find public key\n");
+               return;
+       }
+
+       pex_msg_update_response_init(&ctx, empty_key, pubkey,
+                                    peerpubkey, true, data, net_data, net_data_len);
+       while (!done) {
+               __pex_msg_send(-1, NULL, NULL, 0);
+               done = !pex_msg_update_response_continue(&ctx);
+       }
+       sync_done = true;
+       uloop_end();
+}
+
+static void pex_recv(void *msg, size_t msg_len, struct sockaddr_in6 *addr)
+{
+       struct pex_hdr *hdr;
+       struct pex_ext_hdr *ehdr;
+       uint64_t *msg_req_id;
+       void *data;
+
+       hdr = pex_rx_accept(msg, msg_len, true);
+       if (!hdr)
+               return;
+
+       ehdr = (void *)(hdr + 1);
+       data = (void *)(ehdr + 1);
+       msg_req_id = data;
+
+       if (hdr->version != 0)
+               return;
+
+       if (memcmp(ehdr->auth_id, pubkey, sizeof(ehdr->auth_id)) != 0)
+               return;
+
+       *(uint64_t *)hdr->id ^= pex_network_hash(pubkey, ehdr->nonce);
+
+       switch (hdr->opcode) {
+       case PEX_MSG_UPDATE_REQUEST:
+               if (cmd != CMD_UPLOAD)
+                       break;
+
+               pex_handle_update_request(addr, hdr->id, data, hdr->len);
+               break;
+       case PEX_MSG_UPDATE_RESPONSE:
+       case PEX_MSG_UPDATE_RESPONSE_DATA:
+       case PEX_MSG_UPDATE_RESPONSE_NO_DATA:
+               if (hdr->len < sizeof(*msg_req_id) || *msg_req_id != req_id)
+                       break;
+
+               if (cmd == CMD_DOWNLOAD &&
+                   hdr->opcode == PEX_MSG_UPDATE_RESPONSE_NO_DATA) {
+                       INFO("No network data available\n");
+                       uloop_end();
+               }
+
+               if (cmd == CMD_UPLOAD &&
+                   hdr->opcode != PEX_MSG_UPDATE_RESPONSE_NO_DATA) {
+                       INFO("Server has newer network data\n");
+                       uloop_end();
+               }
+
+               pex_recv_update_response(data, hdr->len, hdr->opcode);
+               break;
+       }
+}
+
+static int load_network_data(const char *file)
+{
+       static const struct blobmsg_policy policy = { "hosts", BLOBMSG_TYPE_TABLE };
+       struct unet_auth_hdr *hdr;
+       struct unet_auth_data *data;
+       const char *json;
+
+       net_data_len = UNETD_NET_DATA_SIZE_MAX;
+       net_data = unet_read_file(file, &net_data_len);
+       if (!net_data) {
+               INFO("failed to read input file %s\n", file);
+               return 1;
+       }
+
+       if (unet_auth_data_validate(NULL, net_data, net_data_len, &net_data_version, &json) < 0) {
+               INFO("input data validation failed\n");
+               return 1;
+       }
+
+       hdr = net_data;
+       data = (struct unet_auth_data *)(hdr + 1);
+       memcpy(pubkey, data->pubkey, sizeof(pubkey));
+
+       blob_buf_init(&b, 0);
+       blobmsg_add_json_from_string(&b, json);
+
+       blobmsg_parse(&policy, 1, &net_data_hosts, blobmsg_data(b.head), blobmsg_len(b.head));
+       if (!net_data_hosts) {
+               INFO("network data is missing the hosts attribute\n");
+               return 1;
+       }
+
+       return 0;
+}
+
+
+static int cmd_sync(const char *endpoint, int argc, char **argv)
+{
+       uint8_t peerpubkey[EDSIGN_PUBLIC_KEY_SIZE];
+       struct uloop_timeout timeout = {
+               .cb = pex_timeout
+       };
+       struct pex_update_request *req;
+       union network_endpoint ep = {};
+       int len;
+
+       if (cmd == CMD_UPLOAD) {
+               if (argc < 1) {
+                       INFO("missing file argument\n");
+                       return 1;
+               }
+
+               if (load_network_data(argv[0]))
+                       return 1;
+       }
+
+       if (network_get_endpoint(&ep, AF_UNSPEC, endpoint, UNETD_GLOBAL_PEX_PORT, 0) < 0) {
+               INFO("Invalid hostname/port %s\n", endpoint);
+               return 1;
+       }
+
+       len = ep.sa.sa_family == AF_INET6 ? sizeof(ep.in6) : sizeof(ep.in);
+
+       uloop_init();
+
+       if (pex_open(&ep, len, pex_recv, false) < 0)
+               return 1;
+
+       uloop_timeout_set(&timeout, 5000);
+
+       curve25519_generate_public(peerpubkey, peerkey);
+       req = pex_msg_update_request_init(peerpubkey, peerkey, pubkey, &ep,
+                                         net_data_version, true);
+       if (!req)
+               return 1;
+
+       req_id = req->req_id;
+       if (__pex_msg_send(-1, NULL, NULL, 0) < 0) {
+               if (!quiet)
+                       perror("send");
+               return 1;
+       }
+
+       uloop_run();
+
+       return !sync_done;
+}
+
 static int cmd_sign(int argc, char **argv)
 {
        struct unet_auth_hdr hdr = {
@@ -67,18 +318,19 @@ static int cmd_sign(int argc, char **argv)
        FILE *f;
 
        if (argc != 1) {
-               fprintf(stderr, "Missing filename\n");
+               INFO("Missing filename\n");
                return 1;
        }
 
        if (gettimeofday(&tv, NULL)) {
-               perror("gettimeofday");
+               if (!quiet)
+                       perror("gettimeofday");
                return 1;
        }
 
        if (stat(argv[0], &st) ||
            (f = fopen(argv[0], "r")) == NULL) {
-               fprintf(stderr, "Input file not found\n");
+               INFO("Input file not found\n");
                return 1;
        }
 
@@ -88,11 +340,11 @@ static int cmd_sign(int argc, char **argv)
        fclose(f);
 
        if (len != st.st_size) {
-               fprintf(stderr, "Error reading from input file\n");
+               INFO("Error reading from input file\n");
                return 1;
        }
 
-       len += sizeof(*data);
+       len += sizeof(*data) + 1;
 
        memcpy(data->pubkey, pubkey, sizeof(pubkey));
        edsign_sign(hdr.signature, pubkey, seckey, (const void *)data, len);
@@ -115,18 +367,18 @@ static int cmd_verify(int argc, char **argv)
        int ret = 1;
 
        if (argc != 1) {
-               fprintf(stderr, "Missing filename\n");
+               INFO("Missing filename\n");
                return 1;
        }
 
        if (stat(argv[0], &st) ||
            (f = fopen(argv[0], "r")) == NULL) {
-               fprintf(stderr, "Input file not found\n");
+               INFO("Input file not found\n");
                return 1;
        }
 
        if (st.st_size <= sizeof(*hdr) + sizeof(*data)) {
-               fprintf(stderr, "Input file too small\n");
+               INFO("Input file too small\n");
                fclose(f);
                return 1;
        }
@@ -136,20 +388,20 @@ static int cmd_verify(int argc, char **argv)
        fclose(f);
 
        if (len != st.st_size) {
-               fprintf(stderr, "Error reading from input file\n");
+               INFO("Error reading from input file\n");
                return 1;
        }
 
-       ret = unet_auth_data_validate(pubkey, hdr, len, NULL);
+       ret = unet_auth_data_validate(pubkey, hdr, len, NULL, NULL);
        switch (ret) {
        case -1:
-               fprintf(stderr, "Invalid input data\n");
+               INFO("Invalid input data\n");
                break;
        case -2:
-               fprintf(stderr, "Public key does not match\n");
+               INFO("Public key does not match\n");
                break;
        case -3:
-               fprintf(stderr, "Signature verification failed\n");
+               INFO("Signature verification failed\n");
                break;
        }
 
@@ -179,7 +431,7 @@ static int cmd_generate(int argc, char **argv)
 
        f = fopen("/dev/urandom", "r");
        if (!f) {
-               fprintf(stderr, "Can't open /dev/urandom\n");
+               INFO("Can't open /dev/urandom\n");
                return 1;
        }
 
@@ -187,7 +439,7 @@ static int cmd_generate(int argc, char **argv)
        fclose(f);
 
        if (ret != 1) {
-               fprintf(stderr, "Can't read data from /dev/urandom\n");
+               INFO("Can't read data from /dev/urandom\n");
                return 1;
        }
 
@@ -209,7 +461,7 @@ static bool parse_key(uint8_t *dest, const char *str)
                f = fopen(str, "r");
 
        if (!f) {
-               fprintf(stderr, "Can't open key file for reading\n");
+               INFO("Can't open key file for reading\n");
                return false;
        }
 
@@ -220,16 +472,27 @@ static bool parse_key(uint8_t *dest, const char *str)
        keystr[len] = 0;
 
        if (b64_decode(keystr, dest, EDSIGN_PUBLIC_KEY_SIZE) != EDSIGN_PUBLIC_KEY_SIZE) {
-               fprintf(stderr, "Failed to parse key data\n");
+               INFO("Failed to parse key data\n");
                return false;
        }
 
        return true;
 }
 
+static bool cmd_needs_peerkey(void)
+{
+       switch (cmd) {
+       case CMD_DOWNLOAD:
+               return true;
+       default:
+               return false;
+       }
+}
+
 static bool cmd_needs_pubkey(void)
 {
        switch (cmd) {
+       case CMD_DOWNLOAD:
        case CMD_VERIFY:
                return true;
        default:
@@ -249,18 +512,61 @@ static bool cmd_needs_key(void)
        }
 }
 
+static bool cmd_needs_outfile(void)
+{
+       switch (cmd) {
+       case CMD_SIGN:
+       case CMD_PUBKEY:
+       case CMD_GENERATE:
+       case CMD_DOWNLOAD:
+               return true;
+       default:
+               return false;
+       }
+}
+
 int main(int argc, char **argv)
 {
        const char *progname = argv[0];
        const char *out_filename = NULL;
+       const char *cmd_arg = NULL;
        bool has_key = false, has_pubkey = false;
+       bool has_peerkey = false;
        int ret, ch;
 
-       while ((ch = getopt(argc, argv, "o:k:K:GHPSV")) != -1) {
+       while ((ch = getopt(argc, argv, "h:k:K:o:qD:GHPSU:V")) != -1) {
                switch (ch) {
+               case 'D':
+               case 'U':
+               case 'G':
+               case 'H':
+               case 'S':
+               case 'P':
+               case 'V':
+                       if (cmd != CMD_UNKNOWN)
+                               return usage(progname);
+                       break;
+               default:
+                       break;
+               }
+
+               switch (ch) {
+               case 'q':
+                       quiet = true;
+                       break;
                case 'o':
                        out_filename = optarg;
                        break;
+               case 'h':
+                       if (has_peerkey)
+                               return usage(progname);
+
+                       if (!parse_key(peerkey, optarg)) {
+                               return 1;
+                       }
+
+                       has_peerkey = true;
+                       break;
                case 'k':
                        if (has_pubkey)
                                return usage(progname);
@@ -284,34 +590,27 @@ int main(int argc, char **argv)
                        edsign_sec_to_pub(pubkey, seckey);
                        has_pubkey = true;
                        break;
+               case 'U':
+                       cmd = CMD_UPLOAD;
+                       cmd_arg = optarg;
+                       break;
+               case 'D':
+                       cmd = CMD_DOWNLOAD;
+                       cmd_arg = optarg;
+                       break;
                case 'G':
-                       if (cmd != CMD_UNKNOWN)
-                               return usage(progname);
-
                        cmd = CMD_GENERATE;
                        break;
                case 'S':
-                       if (cmd != CMD_UNKNOWN)
-                               return usage(progname);
-
                        cmd = CMD_SIGN;
                        break;
                case 'P':
-                       if (cmd != CMD_UNKNOWN)
-                               return usage(progname);
-
                        cmd = CMD_PUBKEY;
                        break;
                case 'H':
-                       if (cmd != CMD_UNKNOWN)
-                               return usage(progname);
-
                        cmd = CMD_HOST_PUBKEY;
                        break;
                case 'V':
-                       if (cmd != CMD_UNKNOWN)
-                               return usage(progname);
-
                        cmd = CMD_VERIFY;
                        break;
                default:
@@ -319,23 +618,28 @@ int main(int argc, char **argv)
                }
        }
 
+       if (!has_peerkey && cmd_needs_peerkey()) {
+               INFO("Missing -h <key> argument\n");
+               return 1;
+       }
+
        if (!has_key && cmd_needs_key()) {
-               fprintf(stderr, "Missing -K <key> argument\n");
+               INFO("Missing -K <key> argument\n");
                return 1;
        }
 
        if (!has_pubkey && cmd_needs_pubkey()) {
-               fprintf(stderr, "Missing -k <key> argument\n");
+               INFO("Missing -k <key> argument\n");
                return 1;
        }
 
        argc -= optind;
        argv += optind;
 
-       if (out_filename) {
+       if (out_filename && cmd_needs_outfile()) {
                out_file = fopen(out_filename, "w");
                if (!out_file) {
-                       fprintf(stderr, "Failed to open output file\n");
+                       INFO("Failed to open output file\n");
                        return 1;
                }
        } else {
@@ -344,6 +648,10 @@ int main(int argc, char **argv)
 
        ret = -1;
        switch (cmd) {
+       case CMD_UPLOAD:
+       case CMD_DOWNLOAD:
+               ret = cmd_sync(cmd_arg, argc, argv);
+               break;
        case CMD_GENERATE:
                ret = cmd_generate(argc, argv);
                break;
@@ -364,6 +672,11 @@ int main(int argc, char **argv)
                break;
        }
 
+       if (net_data)
+               free(net_data);
+
+       blob_buf_free(&b);
+
        if (out_file != stdout) {
                fclose(out_file);
                if (ret)