diff --git a/cli.h b/cli.h index ca7e7d1d..39236004 100644 --- a/cli.h +++ b/cli.h @@ -90,15 +90,4 @@ int cli_interval_ms(const char *arg); int cli_uint(const char *arg); int cli_optional_did(const char *text); -int cli_putchar(struct cli_context *context, char c); -int cli_puts(struct cli_context *context, const char *str); -void cli_printf(struct cli_context *context, const char *fmt, ...) __attribute__ (( format(printf,2,3) )); -int cli_delim(struct cli_context *context, const char *opt); -void cli_columns(struct cli_context *context, int columns, const char *names[]); -void cli_row_count(struct cli_context *context, int rows); -void cli_field_name(struct cli_context *context, const char *name, const char *delim); -void cli_put_long(struct cli_context *context, int64_t value, const char *delim); -void cli_put_string(struct cli_context *context, const char *value, const char *delim); -void cli_put_hexvalue(struct cli_context *context, const unsigned char *value, int length, const char *delim); - #endif // __SERVAL_DNA__CLI_H diff --git a/commandline.c b/commandline.c index 4e2d08d4..3e64efaf 100644 --- a/commandline.c +++ b/commandline.c @@ -17,65 +17,14 @@ along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/* - Portions Copyright (C) 2013 Petter Reinholdtsen - Some rights reserved - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_JNI_H -#include -#endif -#include #include "serval.h" #include "conf.h" -#include "rhizome.h" -#include "strbuf.h" -#include "strbuf_helpers.h" -#include "str.h" -#include "os.h" #include "mdp_client.h" #include "cli.h" -#include "overlay_address.h" -#include "overlay_buffer.h" #include "keyring.h" #include "dataformats.h" #include "commandline.h" +#include "server.h" DEFINE_CMD(commandline_usage,CLIFLAG_PERMISSIVE_CONFIG, "Display command usage.", @@ -565,65 +514,6 @@ void cli_flush(struct cli_context *UNUSED(context)) fflush(stdout); } -static void cli_put_manifest(struct cli_context *context, const rhizome_manifest *m) -{ - assert(m->filesize != RHIZOME_SIZE_UNSET); - cli_field_name(context, "manifestid", ":"); // TODO rename to "bundleid" or "bid" - cli_put_string(context, alloca_tohex_rhizome_bid_t(m->cryptoSignPublic), "\n"); - cli_field_name(context, "version", ":"); - cli_put_long(context, m->version, "\n"); - cli_field_name(context, "filesize", ":"); - cli_put_long(context, m->filesize, "\n"); - if (m->filesize != 0) { - cli_field_name(context, "filehash", ":"); - cli_put_string(context, alloca_tohex_rhizome_filehash_t(m->filehash), "\n"); - } - if (m->has_bundle_key) { - cli_field_name(context, "BK", ":"); - cli_put_string(context, alloca_tohex_rhizome_bk_t(m->bundle_key), "\n"); - } - if (m->has_date) { - cli_field_name(context, "date", ":"); - cli_put_long(context, m->date, "\n"); - } - switch (m->payloadEncryption) { - case PAYLOAD_CRYPT_UNKNOWN: - break; - case PAYLOAD_CLEAR: - cli_field_name(context, "crypt", ":"); - cli_put_long(context, 0, "\n"); - break; - case PAYLOAD_ENCRYPTED: - cli_field_name(context, "crypt", ":"); - cli_put_long(context, 1, "\n"); - break; - } - if (m->service) { - cli_field_name(context, "service", ":"); - cli_put_string(context, m->service, "\n"); - } - if (m->name) { - cli_field_name(context, "name", ":"); - cli_put_string(context, m->name, "\n"); - } - cli_field_name(context, ".readonly", ":"); - cli_put_long(context, m->haveSecret ? 0 : 1, "\n"); - if (m->haveSecret) { - char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1]; - rhizome_bytes_to_hex_upper(m->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES); - cli_field_name(context, ".secret", ":"); - cli_put_string(context, secret, "\n"); - } - if (m->authorship == AUTHOR_AUTHENTIC) { - cli_field_name(context, ".author", ":"); - cli_put_string(context, alloca_tohex_sid_t(m->author), "\n"); - } - cli_field_name(context, ".rowid", ":"); - cli_put_long(context, m->rowid, "\n"); - cli_field_name(context, ".inserttime", ":"); - cli_put_long(context, m->inserttime, "\n"); -} - DEFINE_CMD(app_echo,CLIFLAG_PERMISSIVE_CONFIG, "Output the supplied string.", "echo","[-e]","[--]","..."); @@ -832,2286 +722,6 @@ static int app_dna_lookup(const struct cli_parsed *parsed, struct cli_context *c return 0; } -DEFINE_CMD(app_server_start, 0, - "Start daemon with instance path from SERVALINSTANCE_PATH environment variable.", - "start" KEYRING_PIN_OPTIONS, "[foreground|exec ]"); -static int app_server_start(const struct cli_parsed *parsed, struct cli_context *context) -{ - IN(); - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - /* Process optional arguments */ - int pid=-1; - int cpid=-1; - const char *execpath; - if (cli_arg(parsed, "exec", &execpath, cli_absolute_path, NULL) == -1) - RETURN(-1); - int foregroundP = cli_arg(parsed, "foreground", NULL, NULL, NULL) == 0; -#ifdef HAVE_JNI_H - if (context && context->jni_env && execpath == NULL) - RETURN(WHY("Must supply \"exec \" arguments when invoked via JNI")); -#endif - /* Create the instance directory if it does not yet exist */ - if (create_serval_instance_dir() == -1) - RETURN(-1); - /* Now that we know our instance path, we can ask for the default set of - network interfaces that we will take interest in. */ - if (config.interfaces.ac == 0) - NOWHENCE(WARN("No network interfaces configured (empty 'interfaces' config option)")); - if (pid == -1) - pid = server_pid(); - if (pid < 0) - RETURN(-1); - int ret = -1; - // If the pidfile identifies this process, it probably means we are re-spawning after a SEGV, so - // go ahead and do the fork/exec. - if (pid > 0 && pid != getpid()) { - WARNF("Server already running (pid=%d)", pid); - ret = 10; - } else { - if (foregroundP) - INFOF("Foreground server process %s", execpath ? execpath : "without exec"); - else - INFOF("Starting background server %s", execpath ? execpath : "without exec"); - /* Start the Serval process. All server settings will be read by the server process from the - instance directory when it starts up. */ - // Open the keyring and ensure it contains at least one unlocked identity. - keyring = keyring_open_instance_cli(parsed); - if (!keyring) - RETURN(WHY("Could not open keyring file")); - if (keyring_seed(keyring) == -1) { - WHY("Could not seed keyring"); - goto exit; - } - if (foregroundP) { - ret = server(); - goto exit; - } - const char *dir = getenv("SERVALD_SERVER_CHDIR"); - if (!dir) - dir = config.server.chdir; - switch (cpid = fork()) { - case -1: - /* Main process. Fork failed. There is no child process. */ - WHY_perror("fork"); - goto exit; - case 0: { - /* Child process. Fork then exit, to disconnect daemon from parent process, so that - when daemon exits it does not live on as a zombie. N.B. On Android, do not return from - within this process; that will unroll the JNI call stack and cause havoc -- call _exit() - instead (not exit(), because we want to avoid any Java atexit(3) callbacks as well). If - _exit() is used on non-Android systems, then source code coverage does not get reported, - because it relies on an atexit() callback to write the accumulated counters into .gcda - files. */ -#ifdef ANDROID -# define EXIT_CHILD(n) _exit(n) -#else -# define EXIT_CHILD(n) exit(n) -#endif - // Ensure that all stdio streams are flushed before forking, so that if a child calls - // exit(), it will not result in any buffered output being written twice to the file - // descriptor. - fflush(stdout); - fflush(stderr); - switch (fork()) { - case -1: - EXIT_CHILD(WHY_perror("fork")); - case 0: { - /* Grandchild process. Close logfile (so that it gets re-opened again on demand, with - our own file pointer), disable logging to stderr (about to get redirected to - /dev/null), disconnect from current directory, disconnect standard I/O streams, and - start a new process session so that if we are being started by an adb shell session - on an Android device, then we don't receive a SIGHUP when the adb shell process ends. - */ - close_log_file(); - disable_log_stderr(); - int fd; - if ((fd = open("/dev/null", O_RDWR, 0)) == -1) - EXIT_CHILD(WHY_perror("open(\"/dev/null\")")); - if (setsid() == -1) - EXIT_CHILD(WHY_perror("setsid")); - if (chdir(dir) == -1) - EXIT_CHILD(WHYF_perror("chdir(%s)", alloca_str_toprint(dir))); - if (dup2(fd, 0) == -1) - EXIT_CHILD(WHYF_perror("dup2(%d,0)", fd)); - if (dup2(fd, 1) == -1) - EXIT_CHILD(WHYF_perror("dup2(%d,1)", fd)); - if (dup2(fd, 2) == -1) - EXIT_CHILD(WHYF_perror("dup2(%d,2)", fd)); - if (fd > 2) - (void)close(fd); - /* The execpath option is provided so that a JNI call to "start" can be made which - creates a new server daemon process with the correct argv[0]. Otherwise, the servald - process appears as a process with argv[0] = "org.servalproject". */ - if (execpath) { - /* Need the cast on Solaris because it defines NULL as 0L and gcc doesn't see it as a - sentinal. */ - execl(execpath, execpath, "start", "foreground", (void *)NULL); - WHYF_perror("execl(%s,\"start\",\"foreground\")", alloca_str_toprint(execpath)); - EXIT_CHILD(-1); - } - EXIT_CHILD(server()); - // NOT REACHED - } - } - // TODO wait for server_write_pid() to signal more directly? - EXIT_CHILD(0); // Main process is waitpid()-ing for this. -#undef EXIT_CHILD - } - } - /* Main process. Wait for the child process to fork the grandchild and exit. */ - waitpid(cpid, NULL, 0); - /* Allow a few seconds for the grandchild process to report for duty. */ - time_ms_t timeout = gettime_ms() + 5000; - do { - sleep_ms(200); // 5 Hz - } while ((pid = server_pid()) == 0 && gettime_ms() < timeout); - if (pid == -1) - goto exit; - if (pid == 0) { - WHY("Server process did not start"); - goto exit; - } - ret = 0; - } - const char *ipath = instance_path(); - if (ipath) { - cli_field_name(context, "instancepath", ":"); - cli_put_string(context, ipath, "\n"); - } - cli_field_name(context, "pidfile", ":"); - cli_put_string(context, server_pidfile_path(), "\n"); - cli_field_name(context, "pid", ":"); - cli_put_long(context, pid, "\n"); - char buff[256]; - if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){ - cli_field_name(context, "http_port", ":"); - cli_put_string(context, buff, "\n"); - } - if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){ - cli_field_name(context, "mdp_inet_port", ":"); - cli_put_string(context, buff, "\n"); - } - cli_flush(context); - /* Sleep before returning if env var is set. This is used in testing, to simulate the situation - on Android phones where the "start" command is invoked via the JNI interface and the calling - process does not die. - */ - const char *post_sleep = getenv("SERVALD_START_POST_SLEEP"); - if (post_sleep) { - time_ms_t milliseconds = atoi(post_sleep); - INFOF("Sleeping for %"PRId64" milliseconds", (int64_t) milliseconds); - sleep_ms(milliseconds); - } -exit: - keyring_free(keyring); - keyring = NULL; - RETURN(ret); - OUT(); -} - -DEFINE_CMD(app_server_stop,CLIFLAG_PERMISSIVE_CONFIG, - "Stop a running daemon with instance path from SERVALINSTANCE_PATH environment variable.", - "stop"); -static int app_server_stop(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - int pid, tries, running; - time_ms_t timeout; - const char *ipath = instance_path(); - if (ipath) { - cli_field_name(context, "instancepath", ":"); - cli_put_string(context, ipath, "\n"); - } - cli_field_name(context, "pidfile", ":"); - cli_put_string(context, server_pidfile_path(), "\n"); - pid = server_pid(); - /* Not running, nothing to stop */ - if (pid <= 0) - return 1; - INFOF("Stopping server (pid=%d)", pid); - /* Set the stop file and signal the process */ - cli_field_name(context, "pid", ":"); - cli_put_long(context, pid, "\n"); - tries = 0; - running = pid; - while (running == pid) { - if (tries >= 5) { - WHYF("Servald pid=%d (pidfile=%s) did not stop after %d SIGHUP signals", - pid, server_pidfile_path(), tries); - return 253; - } - ++tries; - if (kill(pid, SIGHUP) == -1) { - // ESRCH means process is gone, possibly we are racing with another stop, or servald just died - // voluntarily. We DO NOT call serverCleanUp() in this case (once used to!) because that - // would race with a starting server process. - if (errno == ESRCH) - break; - WHY_perror("kill"); - WHYF("Error sending SIGHUP to Servald pid=%d (pidfile %s)", pid, server_pidfile_path()); - return 252; - } - /* Allow a few seconds for the process to die. */ - timeout = gettime_ms() + 2000; - do - sleep_ms(200); // 5 Hz - while ((running = server_pid()) == pid && gettime_ms() < timeout); - } - cli_field_name(context, "tries", ":"); - cli_put_long(context, tries, "\n"); - return 0; -} - -DEFINE_CMD(app_server_status,CLIFLAG_PERMISSIVE_CONFIG, - "Display information about running daemon.", - "status"); -int app_server_status(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - int pid = server_pid(); - const char *ipath = instance_path(); - if (ipath) { - cli_field_name(context, "instancepath", ":"); - cli_put_string(context, ipath, "\n"); - } - cli_field_name(context, "pidfile", ":"); - cli_put_string(context, server_pidfile_path(), "\n"); - cli_field_name(context, "status", ":"); - cli_put_string(context, pid > 0 ? "running" : "stopped", "\n"); - if (pid > 0) { - cli_field_name(context, "pid", ":"); - cli_put_long(context, pid, "\n"); - char buff[256]; - if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){ - cli_field_name(context, "http_port", ":"); - cli_put_string(context, buff, "\n"); - } - if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){ - cli_field_name(context, "mdp_inet_port", ":"); - cli_put_string(context, buff, "\n"); - } - } - return pid > 0 ? 0 : 1; -} - -// returns -1 on error, -2 on timeout, packet length on success. -static ssize_t mdp_poll_recv(int mdp_sock, time_ms_t deadline, struct mdp_header *rev_header, unsigned char *payload, size_t buffer_size) -{ - time_ms_t now = gettime_ms(); - if (now > deadline) - return -2; - int p = mdp_poll(mdp_sock, deadline - now); - if (p == -1) - return WHY_perror("mdp_poll"); - if (p == 0) - return -2; - ssize_t len = mdp_recv(mdp_sock, rev_header, payload, buffer_size); - if (len == -1) - return -1; - if (rev_header->flags & MDP_FLAG_ERROR) - return WHY("Operation failed, check the daemon log for more information"); - return len; -} - -DEFINE_CMD(app_mdp_ping, 0, - "Attempts to ping specified node via Mesh Datagram Protocol (MDP).", - "mdp","ping","[--interval=]","[--timeout=]","[--wait-for-duplicates]", - "|broadcast","[]"); -static int app_mdp_ping(const struct cli_parsed *parsed, struct cli_context *context) -{ - int mdp_sockfd; - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *sidhex, *count, *opt_timeout, *opt_interval; - int opt_wait_for_duplicates = 0 == cli_arg(parsed, "--wait-for-duplicates", NULL, NULL, NULL); - if ( cli_arg(parsed, "--timeout", &opt_timeout, cli_interval_ms, "1") == -1 - || cli_arg(parsed, "--interval", &opt_interval, cli_interval_ms, "1") == -1 - || cli_arg(parsed, "SID", &sidhex, str_is_subscriber_id, "broadcast") == -1 - || cli_arg(parsed, "count", &count, cli_uint, "0") == -1) - return -1; - - /* Get SID that we want to ping. - TODO - allow lookup of SID prefixes and telephone numbers - (that would require MDP lookup of phone numbers, which doesn't yet occur) */ - sid_t ping_sid; - if (str_to_sid_t(&ping_sid, sidhex) == -1) - return WHY("str_to_sid_t() failed"); - - // assume we wont hear any responses - int ret=1; - unsigned icount = atoi(count); - int64_t timeout_ms = 1000; - str_to_uint64_interval_ms(opt_timeout, &timeout_ms, NULL); - if (timeout_ms == 0) - timeout_ms = 60 * 60000; // 1 hour... - int64_t interval_ms = 1000; - str_to_uint64_interval_ms(opt_interval, &interval_ms, NULL); - if (interval_ms == 0) - interval_ms = 1000; - - /* First sequence number in the echo frames */ - uint32_t firstSeq = random(); - uint32_t sequence_number = firstSeq; - - int broadcast = is_sid_t_broadcast(ping_sid); - - /* Bind to MDP socket and await confirmation */ - if ((mdp_sockfd = mdp_socket()) < 0) - return WHY("Cannot create MDP socket"); - - set_nonblock(mdp_sockfd); - struct mdp_header mdp_header; - bzero(&mdp_header, sizeof(mdp_header)); - - mdp_header.local.sid = BIND_PRIMARY; - mdp_header.remote.sid = ping_sid; - mdp_header.remote.port = MDP_PORT_ECHO; - mdp_header.qos = OQ_MESH_MANAGEMENT; - mdp_header.ttl = PAYLOAD_TTL_DEFAULT; - mdp_header.flags = MDP_FLAG_BIND; - if (broadcast) - mdp_header.flags |= MDP_FLAG_NO_CRYPT; - - /* TODO Eventually we should try to resolve SID to phone number and vice versa */ - cli_printf(context, "MDP PING %s: 12 data bytes", alloca_tohex_sid_t(ping_sid)); - cli_delim(context, "\n"); - cli_flush(context); - - unsigned tx_count = 0; - unsigned missing_pong_count = 0; - unsigned rx_count = 0; - unsigned rx_dupcount = 0; - unsigned rx_igncount = 0; - time_ms_t rx_mintime_ms = -1; - time_ms_t rx_maxtime_ms = -1; - time_ms_t rx_tottime_ms = 0; - struct packet_stat { - uint32_t sequence; - time_ms_t tx_time; - time_ms_t rx_time; - unsigned pong_count; - } stats[1024]; - bzero(stats, sizeof stats); - - if (broadcast) - WARN("broadcast ping packets will not be encrypted"); - - sigIntFlag = 0; - signal(SIGINT, sigIntHandler); - - while (!sigIntFlag && (icount == 0 || tx_count < icount)) { - time_ms_t now = gettime_ms(); - - // send a ping packet - if (tx_count == 0 || !(mdp_header.flags & MDP_FLAG_BIND)) { - uint8_t payload[12]; - write_uint32(&payload[0], sequence_number); - write_uint64(&payload[4], now); - int r = mdp_send(mdp_sockfd, &mdp_header, payload, sizeof(payload)); - if (r != -1) { - if (config.debug.mdprequests) - DEBUGF("ping seq=%lu", (unsigned long)(sequence_number - firstSeq) + 1); - unsigned i = (unsigned long)(sequence_number - firstSeq) % NELS(stats); - assert(i == tx_count % NELS(stats)); - struct packet_stat *stat = &stats[i]; - if (stat->tx_time && stat->pong_count == 0) { - assert(missing_pong_count > 0); - --missing_pong_count; - } - stat->sequence = sequence_number; - stat->tx_time = now; - stat->pong_count = 0; - ++missing_pong_count; - ++sequence_number; - ++tx_count; - } - } - - // Now look for replies ("pongs") until one second has passed, and print any replies with - // appropriate information as required - int all_sent = icount && tx_count >= icount; - time_ms_t finish = now + (all_sent ? timeout_ms : interval_ms); - while (!sigIntFlag && now < finish && (!all_sent || opt_wait_for_duplicates || missing_pong_count)) { - time_ms_t poll_timeout_ms = finish - now; - if (mdp_poll(mdp_sockfd, poll_timeout_ms) <= 0) { - now = gettime_ms(); - continue; - } - - struct mdp_header mdp_recv_header; - uint8_t recv_payload[12]; - ssize_t len = mdp_recv(mdp_sockfd, &mdp_recv_header, recv_payload, sizeof(recv_payload)); - if (len == -1) - break; - if (mdp_recv_header.flags & MDP_FLAG_ERROR) { - WHY("error from daemon, please check the log for more information"); - continue; - } - if (mdp_recv_header.flags & MDP_FLAG_BIND){ - // received port binding confirmation - mdp_header.local = mdp_recv_header.local; - mdp_header.flags &= ~MDP_FLAG_BIND; - if (config.debug.mdprequests) - DEBUGF("bound to %s:%d", alloca_tohex_sid_t(mdp_header.local.sid), mdp_header.local.port); - continue; - } - if ((size_t)len < sizeof(recv_payload)){ - if (config.debug.mdprequests) - DEBUGF("ignoring short pong"); - continue; - } - uint32_t rxseq = read_uint32(&recv_payload[0]); - time_ms_t txtime = read_uint64(&recv_payload[4]); - int hop_count = 64 - mdp_recv_header.ttl; - now = gettime_ms(); - time_ms_t delay = now - txtime; - - struct packet_stat *stat = &stats[(unsigned long)(rxseq - firstSeq) % NELS(stats)]; - if (stat->sequence != rxseq || stat->tx_time != txtime) { - if (config.debug.mdprequests) - DEBUGF("ignoring spurious pong"); - ++rx_igncount; - stat = NULL; // old or corrupted reply (either sequence or txtime is wrong) - } else if (stat->pong_count++ == 0) { - assert(missing_pong_count > 0); - --missing_pong_count; - stat->rx_time = now; - rx_tottime_ms += delay; - ++rx_count; - if (rx_mintime_ms > delay || rx_mintime_ms == -1) - rx_mintime_ms = delay; - if (delay > rx_maxtime_ms) - rx_maxtime_ms = delay; - } else - ++rx_dupcount; - - cli_put_hexvalue(context, mdp_recv_header.remote.sid.binary, SID_SIZE, ": seq="); - cli_put_long(context, (unsigned long)(rxseq - firstSeq) + 1, " time="); - cli_put_long(context, delay, "ms hops="); - cli_put_long(context, hop_count, ""); - cli_put_string(context, (mdp_recv_header.flags & MDP_FLAG_NO_CRYPT) ? "" : " ENCRYPTED", ""); - cli_put_string(context, (mdp_recv_header.flags & MDP_FLAG_NO_SIGN) ? "" : " SIGNED", "\n"); - cli_flush(context); - - ret=0; - } - } - - signal(SIGINT, SIG_DFL); - sigIntFlag = 0; - mdp_close(mdp_sockfd); - - { - float rx_stddev = 0; - float rx_mean = rx_tottime_ms * 1.0 / rx_count; - unsigned tx_samples = tx_count < NELS(stats) ? tx_count : NELS(stats); - unsigned rx_samples = 0; - unsigned i; - for (i = 0; i < tx_samples; ++i) { - struct packet_stat *stat = &stats[i]; - if (stat->pong_count) { - float dev = rx_mean - (stat->rx_time - stat->tx_time); - rx_stddev += dev * dev; - ++rx_samples; - } - } - rx_stddev /= rx_samples; - rx_stddev = sqrtf(rx_stddev); - - /* XXX Report final statistics before going */ - cli_printf(context, "--- %s ping statistics ---\n", alloca_tohex_sid_t(ping_sid)); - cli_printf(context, "%u packets transmitted, %u packets received (plus %u duplicates, %u ignored), %3.1f%% packet loss\n", - tx_count, - rx_count, - rx_dupcount, - rx_igncount, - tx_count ? (tx_count - rx_count) * 100.0 / tx_count : 0 - ); - if (rx_samples) - cli_printf(context, "round-trip min/avg/max/stddev = %"PRId64"/%.3f/%"PRId64"/%.3f ms (%u samples)\n", - rx_mintime_ms, rx_mean, rx_maxtime_ms, rx_stddev, rx_samples); - cli_delim(context, NULL); - cli_flush(context); - } - return ret; -} - -DEFINE_CMD(app_trace, 0, - "Trace through the network to the specified node via MDP.", - "mdp","trace",""); -static int app_trace(const struct cli_parsed *parsed, struct cli_context *context) -{ - int mdp_sockfd; - const char *sidhex; - if (cli_arg(parsed, "SID", &sidhex, str_is_subscriber_id, NULL) == -1) - return -1; - - sid_t srcsid; - sid_t dstsid; - if (str_to_sid_t(&dstsid, sidhex) == -1) - return WHY("str_to_sid_t() failed"); - - if ((mdp_sockfd = overlay_mdp_client_socket()) < 0) - return WHY("Cannot create MDP socket"); - mdp_port_t port=32768+(random()&32767); - if (overlay_mdp_getmyaddr(mdp_sockfd, 0, &srcsid)) { - overlay_mdp_client_close(mdp_sockfd); - return WHY("Could not get local address"); - } - if (overlay_mdp_bind(mdp_sockfd, &srcsid, port)) { - overlay_mdp_client_close(mdp_sockfd); - return WHY("Could not bind to MDP socket"); - } - - overlay_mdp_frame mdp; - bzero(&mdp, sizeof(mdp)); - - mdp.out.src.sid = srcsid; - mdp.out.dst.sid = srcsid; - mdp.out.src.port=port; - mdp.out.dst.port=MDP_PORT_TRACE; - mdp.packetTypeAndFlags=MDP_TX; - struct overlay_buffer *b = ob_static(mdp.out.payload, sizeof(mdp.out.payload)); - ob_append_byte(b, SID_SIZE); - ob_append_bytes(b, srcsid.binary, SID_SIZE); - ob_append_byte(b, SID_SIZE); - ob_append_bytes(b, dstsid.binary, SID_SIZE); - int ret; - if (ob_overrun(b)) - ret = WHY("overlay buffer overrun"); - else { - mdp.out.payload_length = ob_position(b); - cli_printf(context, "Tracing the network path from %s to %s", - alloca_tohex_sid_t(srcsid), alloca_tohex_sid_t(dstsid)); - cli_delim(context, "\n"); - cli_flush(context); - ret = overlay_mdp_send(mdp_sockfd, &mdp, MDP_AWAITREPLY, 5000); - if (ret) - WHYF("overlay_mdp_send returned %d", ret); - } - ob_free(b); - if (ret == 0) { - int offset=0; - { - // skip the first two sid's - int len = mdp.out.payload[offset++]; - offset+=len; - len = mdp.out.payload[offset++]; - offset+=len; - } - int i=0; - while(offsettext || it.node->nodc == 0) { - cli_put_string(context, it.node->fullkey,"="); - cli_put_string(context, it.node->text, "\n"); - } - cf_om_free_node(&root); - return 0; -} - -DEFINE_CMD(app_config_dump, CLIFLAG_PERMISSIVE_CONFIG, - "Dump configuration settings.", - "config","dump","[--full]"); -static int app_config_dump(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - int full = 0 == cli_arg(parsed, "--full", NULL, NULL, NULL); - if (create_serval_instance_dir() == -1) - return -1; - struct cf_om_node *root = NULL; - int ret = cf_fmt_config_main(&root, &config); - if (ret == CFERROR) { - cf_om_free_node(&root); - return -1; - } - struct cf_om_iterator it; - for (cf_om_iter_start(&it, root); it.node; cf_om_iter_next(&it)) { - if (it.node->text && (full || it.node->line_number)) { - cli_put_string(context, it.node->fullkey, "="); - cli_put_string(context, it.node->text, "\n"); - } - } - cf_om_free_node(&root); - return ret == CFOK ? 0 : 1; -} - -static int mdp_client_sync_config(time_ms_t timeout) -{ - /* Bind to MDP socket and await confirmation */ - struct mdp_header mdp_header = { - .remote.port = MDP_SYNC_CONFIG, - }; - int mdpsock = mdp_socket(); - if (mdpsock == -1) - return WHY("cannot create MDP socket"); - set_nonblock(mdpsock); - int r = mdp_send(mdpsock, &mdp_header, NULL, 0); - if (r == -1) - return -1; - time_ms_t deadline = gettime_ms() + timeout; // TODO add --timeout option - struct mdp_header rev_header; - do { - ssize_t len = mdp_poll_recv(mdpsock, deadline, &rev_header, NULL, 0); - if (len == -1) - return -1; - if (len == -2) { - WHYF("timeout while synchronising daemon configuration"); - return -1; - } - } while (!(rev_header.flags & MDP_FLAG_CLOSE)); - return 0; -} - -DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG, - "Set and del specified configuration variables.", - "config","set","","","..."); -DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG, - "Del and set specified configuration variables.", - "config","del","","..."); -DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG, - "Synchronise with the daemon's configuration.", - "config","sync","..."); -static int app_config_set(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - if (create_serval_instance_dir() == -1) - return -1; - // - // This fixes a subtle bug in when upgrading the Batphone app: the servald.conf file does - // not get upgraded. The bug goes like this: - // 1. new Batphone APK is installed, but prior servald.conf is not overwritten because it - // comes in serval.zip; - // 2. new Batphone is started, which calls JNI "stop" command, which reads the old servald.conf - // into memory buffer; - // 3. new Batphone unpacks serval.zip, overwriting servald.conf with new version; - // 4. new Batphone calls JNI "config set rhizome.enable 1", which sets the "rhizome.enable" - // config option in the existing memory buffer and overwrites servald.conf; - // Bingo, the old version of servald.conf is what remains. This kludge intervenes in step 4, by - // reading the new servald.conf into the memory buffer before applying the "rhizome.enable" set - // value and overwriting. - if (cf_om_reload() == -1) - return -1; - // - const char *var[parsed->argc - 1]; - const char *val[parsed->argc - 1]; - unsigned nvar = 0; - unsigned i; - for (i = 1; i < parsed->argc; ++i) { - const char *arg = parsed->args[i]; - int iv = -1; - if (strcmp(arg, "set") == 0) { - if (i + 2 > parsed->argc) - return WHYF("malformed command at args[%d]: 'set' not followed by two arguments", i); - var[nvar] = parsed->args[iv = ++i]; - val[nvar] = parsed->args[++i]; - } else if (strcmp(arg, "del") == 0) { - if (i + 1 > parsed->argc) - return WHYF("malformed command at args[%d]: 'del' not followed by one argument", i); - var[nvar] = parsed->args[iv = ++i]; - val[nvar] = NULL; - } else if (strcmp(arg, "sync") == 0) - var[nvar] = val[nvar] = NULL; - else - return WHYF("malformed command at args[%d]: unsupported action '%s'", i, arg); - if (var[nvar] && !is_configvarname(var[nvar])) - return WHYF("malformed command at args[%d]: '%s' is not a valid config option name", iv, var[nvar]); - ++nvar; - } - int changed = 0; - for (i = 0; i < nvar; ++i) { - if (var[i]) { - if (cf_om_set(&cf_om_root, var[i], val[i]) == -1) - return -1; - if (val[i]) - INFOF("config set %s %s", var[i], alloca_str_toprint(val[i])); - else - INFOF("config del %s", var[i]); - changed = 1; - } else { - if (changed) { - if (cf_om_save() == -1) - return -1; - if (cf_reload() == -1) // logs an error if the new config is bad - return 2; - changed = 0; - } - int pid = server_pid(); - if (pid) { - INFO("config sync"); - // TODO make timeout configurable with --timeout option. - if (mdp_client_sync_config(10000) == -1) - return 3; - } else - INFO("config sync -- skipped, server not running"); - } - } - if (changed) { - if (cf_om_save() == -1) - return -1; - if (cf_reload() == -1) // logs an error if the new config is bad - return 2; - } - return 0; -} - -DEFINE_CMD(app_config_get, CLIFLAG_PERMISSIVE_CONFIG, - "Get specified configuration variable.", - "config","get","[]"); -static int app_config_get(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *var; - if (cli_arg(parsed, "variable", &var, is_configvarpattern, NULL) == -1) - return -1; - if (create_serval_instance_dir() == -1) - return -1; - if (cf_om_reload() == -1) - return -1; - if (var && is_configvarname(var)) { - const char *value = cf_om_get(cf_om_root, var); - if (value) { - cli_field_name(context, var, "="); - cli_put_string(context, value, "\n"); - } - } else { - struct cf_om_iterator it; - for (cf_om_iter_start(&it, cf_om_root); it.node; cf_om_iter_next(&it)) { - if (var && cf_om_match(var, it.node) <= 0) - continue; - if (it.node->text) { - cli_field_name(context, it.node->fullkey, "="); - cli_put_string(context, it.node->text, "\n"); - } - } - } - return 0; -} - -DEFINE_CMD(app_config_paths, CLIFLAG_PERMISSIVE_CONFIG, - "Dump file and directory paths.", - "config", "paths"); -static int app_config_paths(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - if (cf_om_reload() == -1) - return -1; - char path[1024]; - if (FORMF_SERVAL_ETC_PATH(path, NULL)) { - cli_field_name(context, "SERVAL_ETC_PATH", ":"); - cli_put_string(context, path, "\n"); - } - if (FORMF_SERVAL_RUN_PATH(path, NULL)) { - cli_field_name(context, "SERVAL_RUN_PATH", ":"); - cli_put_string(context, path, "\n"); - } - if (FORMF_SERVAL_CACHE_PATH(path, NULL)) { - cli_field_name(context, "SERVAL_CACHE_PATH", ":"); - cli_put_string(context, path, "\n"); - } - strbuf sb = strbuf_local(path, sizeof path); - strbuf_system_log_path(sb); - if (!strbuf_overrun(sb)) { - cli_field_name(context, "SYSTEM_LOG_PATH", ":"); - cli_put_string(context, path, "\n"); - } - strbuf_reset(sb); - strbuf_serval_log_path(sb); - if (!strbuf_overrun(sb)) { - cli_field_name(context, "SERVAL_LOG_PATH", ":"); - cli_put_string(context, path, "\n"); - } - if (FORMF_SERVAL_TMP_PATH(path, NULL)) { - cli_field_name(context, "SERVAL_TMP_PATH", ":"); - cli_put_string(context, path, "\n"); - } - if (FORMF_SERVALD_PROC_PATH(path, NULL)) { - cli_field_name(context, "SERVALD_PROC_PATH", ":"); - cli_put_string(context, path, "\n"); - } - if (FORMF_RHIZOME_STORE_PATH(path, NULL)) { - cli_field_name(context, "RHIZOME_STORE_PATH", ":"); - cli_put_string(context, path, "\n"); - } - return 0; -} - -DEFINE_CMD(app_rhizome_hash_file, 0, - "Compute the Rhizome hash of a file", - "rhizome","hash","file",""); -static int app_rhizome_hash_file(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - /* compute hash of file. We do this without a manifest, so it will necessarily - return the hash of the file unencrypted. */ - const char *filepath; - cli_arg(parsed, "filepath", &filepath, NULL, ""); - rhizome_filehash_t hash; - uint64_t size; - if (rhizome_hash_file(NULL, filepath, &hash, &size) == -1) - return -1; - cli_put_string(context, size ? alloca_tohex_rhizome_filehash_t(hash) : "", "\n"); - return 0; -} - -DEFINE_CMD(app_rhizome_add_file, 0, - "Add a file to Rhizome and optionally write its manifest to the given path", - "rhizome","add","file" KEYRING_PIN_OPTIONS,"[--force-new]","","","[]","[]"); -DEFINE_CMD(app_rhizome_add_file, 0, - "Append content to a journal bundle", - "rhizome", "journal", "append" KEYRING_PIN_OPTIONS, "", "", "", "[]"); -static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *filepath, *manifestpath, *manifestid, *authorSidHex, *bskhex; - - int force_new = 0 == cli_arg(parsed, "--force-new", NULL, NULL, NULL); - cli_arg(parsed, "filepath", &filepath, NULL, ""); - if (cli_arg(parsed, "author_sid", &authorSidHex, cli_optional_sid, "") == -1) - return -1; - cli_arg(parsed, "manifestpath", &manifestpath, NULL, ""); - cli_arg(parsed, "manifestid", &manifestid, NULL, ""); - if (cli_arg(parsed, "bsk", &bskhex, cli_optional_bundle_key, NULL) == -1) - return -1; - - sid_t authorSid; - if (authorSidHex[0] && str_to_sid_t(&authorSid, authorSidHex) == -1) - return WHYF("invalid author_sid: %s", authorSidHex); - rhizome_bk_t bsk; - - // treat empty string the same as null - if (bskhex && !*bskhex) - bskhex=NULL; - - if (bskhex && str_to_rhizome_bk_t(&bsk, bskhex) == -1) - return WHYF("invalid bsk: \"%s\"", bskhex); - - int journal = strcasecmp(parsed->args[1], "journal")==0; - - if (create_serval_instance_dir() == -1) - return -1; - - if (!(keyring = keyring_open_instance_cli(parsed))) - return -1; - - if (rhizome_opendb() == -1){ - keyring_free(keyring); - keyring = NULL; - return -1; - } - - /* Create a new manifest that will represent the file. If a manifest file was supplied, then read - * it, otherwise create a blank manifest. */ - rhizome_manifest *m = rhizome_new_manifest(); - if (!m){ - keyring_free(keyring); - keyring = NULL; - return WHY("Manifest struct could not be allocated -- not added to rhizome"); - } - if (manifestpath && *manifestpath && access(manifestpath, R_OK) == 0) { - if (config.debug.rhizome) - DEBUGF("reading manifest from %s", manifestpath); - /* Don't verify the manifest, because it will fail if it is incomplete. - This is okay, because we fill in any missing bits and sanity check before - trying to write it out. However, we do insist that whatever we load is - parsed okay and not malformed. */ - if (rhizome_read_manifest_from_file(m, manifestpath) || m->malformed) { - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHY("Manifest file could not be loaded -- not added to rhizome"); - } - } else if (manifestid && *manifestid) { - if (config.debug.rhizome) - DEBUGF("Reading manifest from database"); - rhizome_bid_t bid; - if (str_to_rhizome_bid_t(&bid, manifestid) == -1) { - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHYF("Invalid bundle ID: %s", alloca_str_toprint(manifestid)); - } - if (rhizome_retrieve_manifest(&bid, m)){ - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHY("Existing manifest could not be loaded -- not added to rhizome"); - } - } else { - if (config.debug.rhizome) - DEBUGF("Creating new manifest"); - if (journal) { - rhizome_manifest_set_filesize(m, 0); - rhizome_manifest_set_tail(m, 0); - } - } - - if (journal && !m->is_journal){ - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHY("Existing manifest is not a journal"); - } - if (!journal && m->is_journal) { - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return WHY("Existing manifest is a journal"); - } - - if (bskhex) - rhizome_apply_bundle_secret(m, &bsk); - if (m->service == NULL) - rhizome_manifest_set_service(m, RHIZOME_SERVICE_FILE); - if (rhizome_fill_manifest(m, filepath, *authorSidHex ? &authorSid : NULL)) { - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return -1; - } - - enum rhizome_bundle_status status = RHIZOME_BUNDLE_STATUS_NEW; - enum rhizome_payload_status pstatus; - if (journal){ - pstatus = rhizome_append_journal_file(m, 0, filepath); - } else { - pstatus = rhizome_stat_payload_file(m, filepath); - assert(m->filesize != RHIZOME_SIZE_UNSET); - if (pstatus == RHIZOME_PAYLOAD_STATUS_NEW) { - assert(m->filesize > 0); - pstatus = rhizome_store_payload_file(m, filepath); - } - } - switch (pstatus) { - case RHIZOME_PAYLOAD_STATUS_EMPTY: - case RHIZOME_PAYLOAD_STATUS_STORED: - case RHIZOME_PAYLOAD_STATUS_NEW: - break; - case RHIZOME_PAYLOAD_STATUS_TOO_BIG: - case RHIZOME_PAYLOAD_STATUS_EVICTED: - status = RHIZOME_BUNDLE_STATUS_NO_ROOM; - INFO("Insufficient space to store payload"); - break; - case RHIZOME_PAYLOAD_STATUS_ERROR: - status = RHIZOME_BUNDLE_STATUS_ERROR; - break; - case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: - case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: - status = RHIZOME_BUNDLE_STATUS_INCONSISTENT; - break; - case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: - status = RHIZOME_BUNDLE_STATUS_READONLY; - break; - default: - FATALF("pstatus = %d", pstatus); - } - rhizome_manifest *mout = NULL; - if (status == RHIZOME_BUNDLE_STATUS_NEW) { - if (!rhizome_manifest_validate(m) || m->malformed) - status = RHIZOME_BUNDLE_STATUS_INVALID; - else { - status = rhizome_manifest_finalise(m, &mout, !force_new); - if (mout && mout != m && !rhizome_manifest_validate(mout)) { - WHYF("Stored manifest id=%s is invalid -- overwriting", alloca_tohex_rhizome_bid_t(mout->cryptoSignPublic)); - status = RHIZOME_BUNDLE_STATUS_NEW; - } - } - } - int status_valid = 0; - switch (status) { - case RHIZOME_BUNDLE_STATUS_NEW: - if (mout && mout != m) - rhizome_manifest_free(mout); - mout = m; - // fall through - case RHIZOME_BUNDLE_STATUS_SAME: - case RHIZOME_BUNDLE_STATUS_DUPLICATE: - case RHIZOME_BUNDLE_STATUS_OLD: - assert(mout != NULL); - cli_put_manifest(context, mout); - if ( manifestpath && *manifestpath - && rhizome_write_manifest_file(mout, manifestpath, 0) == -1 - ) - WHYF("Could not write manifest to %s", alloca_str_toprint(manifestpath)); - status_valid = 1; - break; - case RHIZOME_BUNDLE_STATUS_READONLY: - case RHIZOME_BUNDLE_STATUS_INCONSISTENT: - case RHIZOME_BUNDLE_STATUS_ERROR: - case RHIZOME_BUNDLE_STATUS_INVALID: - case RHIZOME_BUNDLE_STATUS_FAKE: - case RHIZOME_BUNDLE_STATUS_NO_ROOM: - status_valid = 1; - break; - // Do not use a default: label! With no default, if a new value is added to the enum, then the - // compiler will issue a warning on switch statements that do not cover all the values, which is - // a valuable tool for the developer. - } - if (!status_valid) - FATALF("status=%d", status); - if (mout && mout != m) - rhizome_manifest_free(mout); - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return status; -} - -DEFINE_CMD(app_rhizome_import_bundle, 0, - "Import a payload/manifest pair into Rhizome", - "rhizome","import","bundle","",""); -static int app_rhizome_import_bundle(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *filepath, *manifestpath; - cli_arg(parsed, "filepath", &filepath, NULL, ""); - cli_arg(parsed, "manifestpath", &manifestpath, NULL, ""); - if (rhizome_opendb() == -1) - return -1; - rhizome_manifest *m = rhizome_new_manifest(); - if (!m) - return WHY("Out of manifests."); - rhizome_manifest *m_out = NULL; - enum rhizome_bundle_status status = rhizome_bundle_import_files(m, &m_out, manifestpath, filepath); - switch (status) { - case RHIZOME_BUNDLE_STATUS_NEW: - case RHIZOME_BUNDLE_STATUS_SAME: - case RHIZOME_BUNDLE_STATUS_DUPLICATE: - case RHIZOME_BUNDLE_STATUS_OLD: - cli_put_manifest(context, m_out); - break; - case RHIZOME_BUNDLE_STATUS_ERROR: - case RHIZOME_BUNDLE_STATUS_INVALID: - case RHIZOME_BUNDLE_STATUS_INCONSISTENT: - break; - default: - FATALF("rhizome_bundle_import_files() returned %d", status); - } - if (m_out && m_out != m) - rhizome_manifest_free(m_out); - rhizome_manifest_free(m); - return status; -} - -DEFINE_CMD(app_rhizome_append_manifest, 0, - "Append a manifest to the end of the file it belongs to.", - "rhizome", "append", "manifest", "", ""); -static int app_rhizome_append_manifest(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *manifestpath, *filepath; - if ( cli_arg(parsed, "manifestpath", &manifestpath, NULL, "") == -1 - || cli_arg(parsed, "filepath", &filepath, NULL, "") == -1) - return -1; - rhizome_manifest *m = rhizome_new_manifest(); - if (!m) - return WHY("Out of manifests."); - int ret = -1; - if ( rhizome_read_manifest_from_file(m, manifestpath) != -1 - && rhizome_manifest_validate(m) - && rhizome_manifest_verify(m) - ) { - if (rhizome_write_manifest_file(m, filepath, 1) != -1) - ret = 0; - } - rhizome_manifest_free(m); - return ret; -} - -DEFINE_CMD(app_rhizome_delete, 0, - "Remove the manifest, or payload, or both for the given Bundle ID from the Rhizome store", - "rhizome","delete","manifest|payload|bundle",""); -DEFINE_CMD(app_rhizome_delete, 0, - "Remove the file with the given hash from the Rhizome store", - "rhizome","delete","|file",""); -static int app_rhizome_delete(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *manifestid, *fileid; - if (cli_arg(parsed, "manifestid", &manifestid, cli_manifestid, NULL) == -1) - return -1; - if (cli_arg(parsed, "fileid", &fileid, cli_fileid, NULL) == -1) - return -1; - /* Ensure the Rhizome database exists and is open */ - if (create_serval_instance_dir() == -1) - return -1; - if (rhizome_opendb() == -1) - return -1; - if (!(keyring = keyring_open_instance_cli(parsed))) - return -1; - int ret=0; - if (cli_arg(parsed, "file", NULL, NULL, NULL) == 0) { - if (!fileid){ - keyring_free(keyring); - keyring = NULL; - return WHY("missing argument"); - } - rhizome_filehash_t hash; - if (str_to_rhizome_filehash_t(&hash, fileid) == -1){ - keyring_free(keyring); - keyring = NULL; - return WHYF("invalid argument: %s", alloca_str_toprint(fileid)); - } - ret = rhizome_delete_file(&hash); - } else { - if (!manifestid){ - keyring_free(keyring); - keyring = NULL; - return WHY("missing argument"); - } - rhizome_bid_t bid; - if (str_to_rhizome_bid_t(&bid, manifestid) == -1){ - keyring_free(keyring); - keyring = NULL; - return WHY("Invalid manifest ID"); - } - if (cli_arg(parsed, "bundle", NULL, NULL, NULL) == 0) - ret = rhizome_delete_bundle(&bid); - else if (cli_arg(parsed, "manifest", NULL, NULL, NULL) == 0) - ret = rhizome_delete_manifest(&bid); - else if (cli_arg(parsed, "payload", NULL, NULL, NULL) == 0) - ret = rhizome_delete_payload(&bid); - else{ - keyring_free(keyring); - keyring = NULL; - return WHY("unrecognised command"); - } - } - keyring_free(keyring); - keyring = NULL; - return ret; -} - -DEFINE_CMD(app_rhizome_clean, 0, - "Remove stale and orphaned content from the Rhizome store", - "rhizome","clean","[verify]"); -static int app_rhizome_clean(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - int verify = cli_arg(parsed, "verify", NULL, NULL, NULL) == 0; - - /* Ensure the Rhizome database exists and is open */ - if (create_serval_instance_dir() == -1) - return -1; - if (rhizome_opendb() == -1) - return -1; - - if (verify) - verify_bundles(); - struct rhizome_cleanup_report report; - if (rhizome_cleanup(&report) == -1) - return -1; - cli_field_name(context, "deleted_stale_incoming_files", ":"); - cli_put_long(context, report.deleted_stale_incoming_files, "\n"); - cli_field_name(context, "deleted_orphan_files", ":"); - cli_put_long(context, report.deleted_orphan_files, "\n"); - cli_field_name(context, "deleted_orphan_fileblobs", ":"); - cli_put_long(context, report.deleted_orphan_fileblobs, "\n"); - cli_field_name(context, "deleted_orphan_manifests", ":"); - cli_put_long(context, report.deleted_orphan_manifests, "\n"); - return 0; -} - -DEFINE_CMD(app_rhizome_extract, 0, - "Export a manifest and payload file to the given paths, without decrypting.", - "rhizome","export","bundle" KEYRING_PIN_OPTIONS, - "","[]","[]"); -DEFINE_CMD(app_rhizome_extract, 0, - "Export a manifest from Rhizome and write it to the given path", - "rhizome","export","manifest" KEYRING_PIN_OPTIONS, - "","[]"); -DEFINE_CMD(app_rhizome_extract, 0, - "Extract and decrypt a manifest and file to the given paths.", - "rhizome","extract","bundle" KEYRING_PIN_OPTIONS, - "","[]","[]","[]"); -DEFINE_CMD(app_rhizome_extract, 0, - "Extract and decrypt a file from Rhizome and write it to the given path", - "rhizome","extract","file" KEYRING_PIN_OPTIONS, - "","[]","[]"); -static int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *manifestpath, *filepath, *manifestid, *bskhex; - if ( cli_arg(parsed, "manifestid", &manifestid, cli_manifestid, "") == -1 - || cli_arg(parsed, "manifestpath", &manifestpath, NULL, "") == -1 - || cli_arg(parsed, "filepath", &filepath, NULL, "") == -1 - || cli_arg(parsed, "bsk", &bskhex, cli_optional_bundle_key, NULL) == -1) - return -1; - - int extract = strcasecmp(parsed->args[1], "extract")==0; - - /* Ensure the Rhizome database exists and is open */ - if (create_serval_instance_dir() == -1) - return -1; - if (rhizome_opendb() == -1) - return -1; - - if (!(keyring = keyring_open_instance_cli(parsed))) - return -1; - - int ret=0; - - rhizome_bid_t bid; - if (str_to_rhizome_bid_t(&bid, manifestid) == -1){ - keyring_free(keyring); - keyring = NULL; - return WHY("Invalid manifest ID"); - } - - // treat empty string the same as null - if (bskhex && !*bskhex) - bskhex=NULL; - - rhizome_bk_t bsk; - if (bskhex && str_to_rhizome_bk_t(&bsk, bskhex) == -1){ - keyring_free(keyring); - keyring = NULL; - return WHYF("invalid bsk: \"%s\"", bskhex); - } - - rhizome_manifest *m = rhizome_new_manifest(); - if (m==NULL){ - keyring_free(keyring); - keyring = NULL; - return WHY("Out of manifests"); - } - ret = rhizome_retrieve_manifest(&bid, m); - if (ret==0){ - assert(m->finalised); - if (bskhex) - rhizome_apply_bundle_secret(m, &bsk); - rhizome_authenticate_author(m); - assert(m->authorship != AUTHOR_LOCAL); - cli_put_manifest(context, m); - } - - enum rhizome_payload_status pstatus = RHIZOME_PAYLOAD_STATUS_EMPTY; - if (ret==0 && m->filesize != 0 && filepath && *filepath){ - if (extract){ - // Save the file, implicitly decrypting if required. - pstatus = rhizome_extract_file(m, filepath); - if (pstatus != RHIZOME_PAYLOAD_STATUS_EMPTY && pstatus != RHIZOME_PAYLOAD_STATUS_STORED) - WHYF("rhizome_extract_file() returned %d", pstatus); - }else{ - // Save the file without attempting to decrypt - uint64_t length; - pstatus = rhizome_dump_file(&m->filehash, filepath, &length); - if (pstatus != RHIZOME_PAYLOAD_STATUS_EMPTY && pstatus != RHIZOME_PAYLOAD_STATUS_STORED) - WHYF("rhizome_dump_file() returned %d", pstatus); - } - } - - if (ret==0 && manifestpath && *manifestpath){ - if (strcmp(manifestpath, "-") == 0) { - // always extract a manifest to stdout, even if writing the file itself failed. - cli_field_name(context, "manifest", ":"); - cli_write(context, m->manifestdata, m->manifest_all_bytes); - cli_delim(context, "\n"); - } else { - int append = (strcmp(manifestpath, filepath)==0)?1:0; - // don't write out the manifest if we were asked to append it and writing the file failed. - if (!append || (pstatus == RHIZOME_PAYLOAD_STATUS_EMPTY || pstatus == RHIZOME_PAYLOAD_STATUS_STORED)) { - if (rhizome_write_manifest_file(m, manifestpath, append) == -1) - ret = -1; - } - } - } - switch (pstatus) { - case RHIZOME_PAYLOAD_STATUS_EMPTY: - case RHIZOME_PAYLOAD_STATUS_STORED: - break; - case RHIZOME_PAYLOAD_STATUS_NEW: - ret = 1; // payload not found - break; - case RHIZOME_PAYLOAD_STATUS_ERROR: - case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: - case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: - case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: - ret = -1; - break; - default: - FATALF("pstatus = %d", pstatus); - } - if (m) - rhizome_manifest_free(m); - keyring_free(keyring); - keyring = NULL; - return ret; -} - -DEFINE_CMD(app_rhizome_export_file, 0, - "Export a file from Rhizome and write it to the given path without attempting decryption", - "rhizome","export","file","","[]"); -static int app_rhizome_export_file(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *fileid, *filepath; - if ( cli_arg(parsed, "filepath", &filepath, NULL, "") == -1 - || cli_arg(parsed, "fileid", &fileid, cli_fileid, NULL) == -1) - return -1; - rhizome_filehash_t hash; - if (str_to_rhizome_filehash_t(&hash, fileid) == -1) - return WHYF("invalid argument: %s", alloca_str_toprint(fileid)); - if (create_serval_instance_dir() == -1) - return -1; - if (rhizome_opendb() == -1) - return -1; - if (!rhizome_exists(&hash)) - return 1; - uint64_t length; - enum rhizome_payload_status pstatus = rhizome_dump_file(&hash, filepath, &length); - switch (pstatus) { - case RHIZOME_PAYLOAD_STATUS_EMPTY: - case RHIZOME_PAYLOAD_STATUS_STORED: - break; - case RHIZOME_PAYLOAD_STATUS_NEW: - return 1; // payload not found - case RHIZOME_PAYLOAD_STATUS_ERROR: - case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: - case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: - case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: - return -1; - default: - FATALF("pstatus = %d", pstatus); - } - cli_field_name(context, "filehash", ":"); - cli_put_string(context, alloca_tohex_rhizome_filehash_t(hash), "\n"); - cli_field_name(context, "filesize", ":"); - cli_put_long(context, length, "\n"); - return 0; -} - -DEFINE_CMD(app_rhizome_list, 0, - "List all manifests and files in Rhizome", - "rhizome","list" KEYRING_PIN_OPTIONS, - "[]","[]","[]","[]","[]","[]"); -static int app_rhizome_list(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *service = NULL, *name = NULL, *sender_hex = NULL, *recipient_hex = NULL, *offset_ascii = NULL, *limit_ascii = NULL; - cli_arg(parsed, "service", &service, NULL, ""); - cli_arg(parsed, "name", &name, NULL, ""); - cli_arg(parsed, "sender_sid", &sender_hex, cli_optional_sid, ""); - cli_arg(parsed, "recipient_sid", &recipient_hex, cli_optional_sid, ""); - cli_arg(parsed, "offset", &offset_ascii, cli_uint, "0"); - cli_arg(parsed, "limit", &limit_ascii, cli_uint, "0"); - /* Create the instance directory if it does not yet exist */ - if (create_serval_instance_dir() == -1) - return -1; - if (!(keyring = keyring_open_instance_cli(parsed))) - return -1; - if (rhizome_opendb() == -1) { - keyring_free(keyring); - keyring = NULL; - return -1; - } - size_t rowlimit = atoi(limit_ascii); - size_t rowoffset = atoi(offset_ascii); - struct rhizome_list_cursor cursor; - bzero(&cursor, sizeof cursor); - cursor.service = service && service[0] ? service : NULL; - cursor.name = name && name[0] ? name : NULL; - if (sender_hex && sender_hex[0]) { - if (str_to_sid_t(&cursor.sender, sender_hex) == -1) - return WHYF("Invalid : %s", sender_hex); - cursor.is_sender_set = 1; - } - if (recipient_hex && recipient_hex[0]) { - if (str_to_sid_t(&cursor.recipient, recipient_hex) == -1) - return WHYF("Invalid filesize != RHIZOME_SIZE_UNSET); - rhizome_lookup_author(m); - cli_put_long(context, m->rowid, ":"); - cli_put_string(context, m->service, ":"); - cli_put_hexvalue(context, m->cryptoSignPublic.binary, sizeof m->cryptoSignPublic.binary, ":"); - cli_put_long(context, m->version, ":"); - cli_put_long(context, m->has_date ? m->date : 0, ":"); - cli_put_long(context, m->inserttime, ":"); - switch (m->authorship) { - case AUTHOR_LOCAL: - case AUTHOR_AUTHENTIC: - cli_put_hexvalue(context, m->author.binary, sizeof m->author.binary, ":"); - cli_put_long(context, 1, ":"); - break; - default: - cli_put_string(context, NULL, ":"); - cli_put_long(context, 0, ":"); - break; - } - cli_put_long(context, m->filesize, ":"); - cli_put_hexvalue(context, m->filesize ? m->filehash.binary : NULL, sizeof m->filehash.binary, ":"); - cli_put_hexvalue(context, m->has_sender ? m->sender.binary : NULL, sizeof m->sender.binary, ":"); - cli_put_hexvalue(context, m->has_recipient ? m->recipient.binary : NULL, sizeof m->recipient.binary, ":"); - cli_put_string(context, m->name, "\n"); - } - } - rhizome_list_release(&cursor); - keyring_free(keyring); - keyring = NULL; - if (n == -1) - return -1; - cli_row_count(context, rowcount); - return 0; -} - -DEFINE_CMD(app_keyring_create, 0, - "Create a new keyring file.", - "keyring","create"); -static int app_keyring_create(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - keyring_file *k = keyring_open_instance(); - if (!k) - return -1; - keyring_free(k); - return 0; -} - -DEFINE_CMD(app_keyring_dump, 0, - "Dump all keyring identities that can be accessed using the specified PINs", - "keyring","dump" KEYRING_PIN_OPTIONS,"[--secret]","[]"); -static int app_keyring_dump(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *path; - if (cli_arg(parsed, "file", &path, cli_path_regular, NULL) == -1) - return -1; - int include_secret = 0 == cli_arg(parsed, "--secret", NULL, NULL, NULL); - keyring_file *k = keyring_open_instance_cli(parsed); - if (!k) - return -1; - FILE *fp = path ? fopen(path, "w") : stdout; - if (fp == NULL) { - WHYF_perror("fopen(%s, \"w\")", alloca_str_toprint(path)); - keyring_free(k); - return -1; - } - int ret = keyring_dump(k, XPRINTF_STDIO(fp), include_secret); - if (fp != stdout && fclose(fp) == EOF) { - WHYF_perror("fclose(%s)", alloca_str_toprint(path)); - keyring_free(k); - return -1; - } - keyring_free(k); - return ret; -} - -DEFINE_CMD(app_keyring_load, 0, - "Load identities from the given dump text and insert them into the keyring using the specified entry PINs", - "keyring","load" KEYRING_PIN_OPTIONS,"","[]","[]..."); -static int app_keyring_load(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *path; - if (cli_arg(parsed, "file", &path, cli_path_regular, NULL) == -1) - return -1; - const char *kpin; - if (cli_arg(parsed, "keyring-pin", &kpin, NULL, "") == -1) - return -1; - unsigned pinc = 0; - unsigned i; - for (i = 0; i < parsed->labelc; ++i) - if (strn_str_cmp(parsed->labelv[i].label, parsed->labelv[i].len, "entry-pin") == 0) - ++pinc; - const char *pinv[pinc]; - unsigned pc = 0; - for (i = 0; i < parsed->labelc; ++i) - if (strn_str_cmp(parsed->labelv[i].label, parsed->labelv[i].len, "entry-pin") == 0) { - assert(pc < pinc); - pinv[pc++] = parsed->labelv[i].text; - } - keyring_file *k = keyring_open_instance_cli(parsed); - if (!k) - return -1; - FILE *fp = path && strcmp(path, "-") != 0 ? fopen(path, "r") : stdin; - if (fp == NULL) { - WHYF_perror("fopen(%s, \"r\")", alloca_str_toprint(path)); - keyring_free(k); - return -1; - } - if (keyring_load(k, kpin, pinc, pinv, fp) == -1) { - keyring_free(k); - return -1; - } - if (keyring_commit(k) == -1) { - keyring_free(k); - return WHY("Could not write new identity"); - } - keyring_free(k); - return 0; -} - -DEFINE_CMD(app_keyring_list, 0, - "List identities that can be accessed using the supplied PINs", - "keyring","list" KEYRING_PIN_OPTIONS); -static int app_keyring_list(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - keyring_file *k = keyring_open_instance_cli(parsed); - if (!k) - return -1; - - const char *names[]={ - "sid", - "did", - "name" - }; - cli_columns(context, 3, names); - size_t rowcount = 0; - - unsigned cn, in; - for (cn = 0; cn < k->context_count; ++cn) - for (in = 0; in < k->contexts[cn]->identity_count; ++in) { - const sid_t *sidp = NULL; - const char *did = NULL; - const char *name = NULL; - keyring_identity_extract(k->contexts[cn]->identities[in], &sidp, &did, &name); - if (sidp || did) { - cli_put_string(context, alloca_tohex_sid_t(*sidp), ":"); - cli_put_string(context, did, ":"); - cli_put_string(context, name, "\n"); - rowcount++; - } - } - keyring_free(k); - cli_row_count(context, rowcount); - return 0; -} - -static void cli_output_identity(struct cli_context *context, const keyring_identity *id) -{ - unsigned i; - for (i=0;ikeypair_count;i++){ - keypair *kp=id->keypairs[i]; - switch(kp->type){ - case KEYTYPE_CRYPTOBOX: - cli_field_name(context, "sid", ":"); - cli_put_string(context, alloca_tohex(kp->public_key, kp->public_key_len), "\n"); - break; - case KEYTYPE_DID: - { - char *str = (char*)kp->private_key; - int l = strlen(str); - if (l){ - cli_field_name(context, "did", ":"); - cli_put_string(context, str, "\n"); - } - str = (char*)kp->public_key; - l=strlen(str); - if (l){ - cli_field_name(context, "name", ":"); - cli_put_string(context, str, "\n"); - } - } - break; - case KEYTYPE_PUBLIC_TAG: - { - const char *name; - const unsigned char *value; - size_t length; - if (keyring_unpack_tag(kp->public_key, kp->public_key_len, &name, &value, &length)==0){ - cli_field_name(context, name, ":"); - cli_put_string(context, alloca_toprint_quoted(-1, value, length, NULL), "\n"); - } - } - break; - } - } -} - -DEFINE_CMD(app_keyring_add, 0, - "Create a new identity in the keyring protected by the supplied PIN (empty PIN if not given)", - "keyring","add" KEYRING_PIN_OPTIONS,"[]"); -static int app_keyring_add(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *pin; - cli_arg(parsed, "pin", &pin, NULL, ""); - keyring_file *k = keyring_open_instance_cli(parsed); - if (!k) - return -1; - keyring_enter_pin(k, pin); - assert(k->context_count > 0); - const keyring_identity *id = keyring_create_identity(k, k->contexts[k->context_count - 1], pin); - if (id == NULL) { - keyring_free(k); - return WHY("Could not create new identity"); - } - const sid_t *sidp = NULL; - const char *did = ""; - const char *name = ""; - keyring_identity_extract(id, &sidp, &did, &name); - if (!sidp) { - keyring_free(k); - return WHY("New identity has no SID"); - } - if (keyring_commit(k) == -1) { - keyring_free(k); - return WHY("Could not write new identity"); - } - cli_output_identity(context, id); - keyring_free(k); - return 0; -} - -DEFINE_CMD(app_keyring_set_did, 0, - "Set the DID for the specified SID (must supply PIN to unlock the SID record in the keyring)", - "keyring", "set","did" KEYRING_PIN_OPTIONS,"","",""); -static int app_keyring_set_did(const struct cli_parsed *parsed, struct cli_context *context) -{ - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - const char *sidhex, *did, *name; - - if (cli_arg(parsed, "sid", &sidhex, str_is_subscriber_id, "") == -1 || - cli_arg(parsed, "did", &did, cli_optional_did, "") == -1 || - cli_arg(parsed, "name", &name, NULL, "") == -1) - return -1; - - if (strlen(name)>63) - return WHY("Name too long (31 char max)"); - - sid_t sid; - if (str_to_sid_t(&sid, sidhex) == -1){ - keyring_free(keyring); - keyring = NULL; - return WHY("str_to_sid_t() failed"); - } - - if (!(keyring = keyring_open_instance_cli(parsed))) - return -1; - - unsigned cn=0, in=0, kp=0; - int r=0; - if (!keyring_find_sid(keyring, &cn, &in, &kp, &sid)) - r=WHY("No matching SID"); - else{ - if (keyring_set_did(keyring->contexts[cn]->identities[in], did, name)) - r=WHY("Could not set DID"); - else{ - if (keyring_commit(keyring)) - r=WHY("Could not write updated keyring record"); - else{ - cli_output_identity(context, keyring->contexts[cn]->identities[in]); - } - } - } - - keyring_free(keyring); - keyring = NULL; - return r; -} - -DEFINE_CMD(app_keyring_set_tag, 0, - "Set a named tag for the specified SID (must supply PIN to unlock the SID record in the keyring)", - "keyring", "set","tag" KEYRING_PIN_OPTIONS,"","",""); -static int app_keyring_set_tag(const struct cli_parsed *parsed, struct cli_context *context) -{ - const char *sidhex, *tag, *value; - if (cli_arg(parsed, "sid", &sidhex, str_is_subscriber_id, "") == -1 || - cli_arg(parsed, "tag", &tag, NULL, "") == -1 || - cli_arg(parsed, "value", &value, NULL, "") == -1 ) - return -1; - - if (!(keyring = keyring_open_instance_cli(parsed))) - return -1; - - sid_t sid; - if (str_to_sid_t(&sid, sidhex) == -1) - return WHY("str_to_sid_t() failed"); - - unsigned cn=0, in=0, kp=0; - int r=0; - if (!keyring_find_sid(keyring, &cn, &in, &kp, &sid)) - r=WHY("No matching SID"); - else{ - int length = strlen(value); - if (keyring_set_public_tag(keyring->contexts[cn]->identities[in], tag, (const unsigned char*)value, length)) - r=WHY("Could not set tag value"); - else{ - if (keyring_commit(keyring)) - r=WHY("Could not write updated keyring record"); - else{ - cli_output_identity(context, keyring->contexts[cn]->identities[in]); - } - } - } - - keyring_free(keyring); - keyring = NULL; - return r; -} - -static int handle_pins(const struct cli_parsed *parsed, struct cli_context *UNUSED(context), int revoke) -{ - const char *pin, *sid_hex; - if (cli_arg(parsed, "entry-pin", &pin, NULL, "") == -1 || - cli_arg(parsed, "sid", &sid_hex, str_is_subscriber_id, "") == -1) - return -1; - - int ret=1; - struct mdp_header header={ - .remote.port=MDP_IDENTITY, - }; - int mdp_sock = mdp_socket(); - set_nonblock(mdp_sock); - - unsigned char request_payload[1200]; - struct mdp_identity_request *request = (struct mdp_identity_request *)request_payload; - - if (revoke){ - request->action=ACTION_LOCK; - }else{ - request->action=ACTION_UNLOCK; - } - size_t len = sizeof(struct mdp_identity_request); - if (pin && *pin) { - request->type=TYPE_PIN; - size_t pin_siz = strlen(pin) + 1; - if (pin_siz + len > sizeof(request_payload)) - return WHY("Supplied pin is too long"); - bcopy(pin, &request_payload[len], pin_siz); - len += pin_siz; - }else if(sid_hex && *sid_hex){ - request->type=TYPE_SID; - sid_t sid; - if (str_to_sid_t(&sid, sid_hex) == -1) - return WHY("str_to_sid_t() failed"); - bcopy(sid.binary, &request_payload[len], sizeof(sid)); - len += sizeof(sid); - } - - if (mdp_send(mdp_sock, &header, request_payload, len) == -1) - goto end; - - time_ms_t timeout=gettime_ms()+500; - while(1){ - struct mdp_header rev_header; - unsigned char response_payload[1600]; - ssize_t len = mdp_poll_recv(mdp_sock, timeout, &rev_header, response_payload, sizeof(response_payload)); - if (len==-1) - break; - if (len==-2){ - WHYF("Timeout while waiting for response"); - break; - } - if (rev_header.flags & MDP_FLAG_CLOSE){ - ret=0; - break; - } - } -end: - mdp_close(mdp_sock); - return ret; -} - -DEFINE_CMD(app_revoke_pin, 0, - "Unload any identities protected by this pin and drop all routes to them", - "id", "relinquish", "pin", ""); -DEFINE_CMD(app_revoke_pin, 0, - "Unload a specific identity and drop all routes to it", - "id", "relinquish", "sid", ""); -int app_revoke_pin(const struct cli_parsed *parsed, struct cli_context *context) -{ - return handle_pins(parsed, context, 1); -} - -DEFINE_CMD(app_id_pin, 0, - "Unlock any pin protected identities and enable routing packets to them", - "id", "enter", "pin", ""); -static int app_id_pin(const struct cli_parsed *parsed, struct cli_context *context) -{ - return handle_pins(parsed, context, 0); -} - -DEFINE_CMD(app_id_list, 0, - "Search unlocked identities based on an optional tag and value", - "id", "list", "[]", "[]"); -static int app_id_list(const struct cli_parsed *parsed, struct cli_context *context) -{ - const char *tag, *value; - if (cli_arg(parsed, "tag", &tag, NULL, "") == -1 || - cli_arg(parsed, "value", &value, NULL, "") == -1 ) - return -1; - - int ret=-1; - struct mdp_header header={ - .remote.port=MDP_SEARCH_IDS, - }; - int mdp_sock = mdp_socket(); - set_nonblock(mdp_sock); - - unsigned char request_payload[1200]; - size_t len=0; - - if (tag && *tag){ - size_t value_len=0; - if (value && *value) - value_len = strlen(value); - len = sizeof(request_payload); - if (keyring_pack_tag(request_payload, &len, tag, (unsigned char*)value, value_len)) - goto end; - } - - if (mdp_send(mdp_sock, &header, request_payload, len) == -1) - goto end; - - const char *names[]={ - "sid" - }; - cli_columns(context, 1, names); - size_t rowcount=0; - - time_ms_t timeout=gettime_ms()+500; - while(1){ - struct mdp_header rev_header; - unsigned char response_payload[1600]; - ssize_t len = mdp_poll_recv(mdp_sock, timeout, &rev_header, response_payload, sizeof(response_payload)); - if (len==-1) - break; - if (len==-2){ - WHYF("Timeout while waiting for response"); - break; - } - - if (len>=SID_SIZE){ - rowcount++; - sid_t *id = (sid_t*)response_payload; - cli_put_hexvalue(context, id->binary, sizeof(sid_t), "\n"); - // TODO receive and decode other details about this identity - } - - if (rev_header.flags & MDP_FLAG_CLOSE){ - ret=0; - break; - } - } - cli_row_count(context, rowcount); -end: - mdp_close(mdp_sock); - return ret; -} - -DEFINE_CMD(app_id_self, 0, - "Return identity(s) as URIs of own node, or of known routable peers, or all known peers", - "id","self|peers|allpeers"); -static int app_id_self(const struct cli_parsed *parsed, struct cli_context *context) -{ - int mdp_sockfd; - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - /* List my own identities */ - overlay_mdp_frame a; - bzero(&a, sizeof(overlay_mdp_frame)); - int result; - - a.packetTypeAndFlags=MDP_GETADDRS; - const char *arg = parsed->labelc ? parsed->labelv[0].text : ""; - if (!strcasecmp(arg,"self")) - a.addrlist.mode = MDP_ADDRLIST_MODE_SELF; /* get own identities */ - else if (!strcasecmp(arg,"allpeers")) - a.addrlist.mode = MDP_ADDRLIST_MODE_ALL_PEERS; /* get all known peers */ - else if (!strcasecmp(arg,"peers")) - a.addrlist.mode = MDP_ADDRLIST_MODE_ROUTABLE_PEERS; /* get routable (reachable) peers */ - else - return WHYF("unsupported arg '%s'", arg); - a.addrlist.first_sid=0; - - if ((mdp_sockfd = overlay_mdp_client_socket()) < 0) - return WHY("Cannot create MDP socket"); - - const char *names[]={ - "sid" - }; - cli_columns(context, 1, names); - size_t rowcount=0; - - do{ - result=overlay_mdp_send(mdp_sockfd, &a, MDP_AWAITREPLY, 5000); - if (result) { - if (a.packetTypeAndFlags==MDP_ERROR){ - WHYF(" MDP Server error #%d: '%s'", - a.error.error,a.error.message); - } else - WHYF("Could not get list of local MDP addresses"); - overlay_mdp_client_close(mdp_sockfd); - return WHY("Failed to get local address list"); - } - if ((a.packetTypeAndFlags&MDP_TYPE_MASK)!=MDP_ADDRLIST) { - overlay_mdp_client_close(mdp_sockfd); - return WHY("MDP Server returned something other than an address list"); - } - unsigned i; - for(i=0;i1.00) count/=2; - } - - - cli_printf(context, "Benchmarking CryptoSign signature verification:\n"); - { - - unsigned char sign_pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES]; - unsigned char sign_sk[crypto_sign_edwards25519sha512batch_SECRETKEYBYTES]; - if (crypto_sign_edwards25519sha512batch_keypair(sign_pk,sign_sk)) - return WHY("crypto_sign_curve25519xsalsa20poly1305_keypair() failed.\n"); - - unsigned char plainTextIn[1024]; - unsigned char cipherText[1024]; - unsigned char plainTextOut[1024]; - unsigned long long cipherLen=0; - unsigned long long plainLenOut; - bzero(plainTextIn,1024); - bzero(cipherText,1024); - snprintf((char *)&plainTextIn[0],1024,"%s","No casaba melons allowed in the lab."); - int plainLenIn=64; - - time_ms_t start = gettime_ms(); - for(i=0;i<10;i++) { - int r=crypto_sign_edwards25519sha512batch(cipherText,&cipherLen, - plainTextIn,plainLenIn, - sign_sk); - if (r) - return WHY("crypto_sign_edwards25519sha512batch() failed.\n"); - } - - time_ms_t end=gettime_ms(); - cli_printf(context, "mean signature generation time = %.2fms\n", - (end-start)*1.0/i); - start = gettime_ms(); - - for(i=0;i<10;i++) { - bzero(&plainTextOut,1024); plainLenOut=0; - int r=crypto_sign_edwards25519sha512batch_open(plainTextOut,&plainLenOut, - &cipherText[0],cipherLen, - sign_pk); - if (r) - return WHYF("crypto_sign_edwards25519sha512batch_open() failed (r=%d, i=%d).\n", - r,i); - } - end = gettime_ms(); - cli_printf(context, "mean signature verification time = %.2fms\n", - (end-start)*1.0/i); - } - - /* We can't do public signing with a crypto_box key, but we should be able to - do shared-secret generation using crypto_sign keys. */ - { - cli_printf(context, "Testing supercop-20120525 Ed25519 CryptoSign implementation:\n"); - - unsigned char sign1_pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES]; - unsigned char sign1_sk[crypto_sign_edwards25519sha512batch_SECRETKEYBYTES]; - if (crypto_sign_edwards25519sha512batch_keypair(sign1_pk,sign1_sk)) - return WHY("crypto_sign_edwards25519sha512batch_keypair() failed.\n"); - - /* Try calculating public key from secret key */ - unsigned char pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES]; - - /* New Ed25519 implementation has public key as 2nd half of private key. */ - bcopy(&sign1_sk[32],pk,32); - - if (memcmp(pk, sign1_pk, crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES)) { - WHY("Could not calculate public key from private key.\n"); - dump("calculated",&pk,sizeof(pk)); - dump("original",&sign1_pk,sizeof(sign1_pk)); - } else - cli_printf(context, "Can calculate public key from private key.\n"); - - /* Now use a pre-tested keypair and make sure that we can sign and verify with - it, and that the signatures are as expected. */ - - unsigned char key[64]={ - 0xf6,0x70,0x6b,0x8a,0x4e,0x1e,0x4b,0x01, - 0x11,0x56,0x85,0xac,0x63,0x46,0x67,0x5f, - 0xc1,0x44,0xcf,0xdf,0x98,0x5c,0x2b,0x8b, - 0x18,0xff,0x70,0x9c,0x12,0x71,0x48,0xb9, - - 0x32,0x2a,0x88,0xba,0x9c,0xdd,0xed,0x35, - 0x8f,0x01,0x18,0xf7,0x60,0x1b,0xfb,0x80, - 0xaf,0xce,0x74,0xe0,0x85,0x39,0xac,0x13, - 0x15,0xf6,0x79,0xaa,0x68,0xef,0x5d,0xc6}; - - unsigned char plainTextIn[1024]; - unsigned char plainTextOut[1024]; - unsigned char cipherText[1024]; - unsigned long long cipherLen=0; - unsigned long long plainLenOut; - bzero(plainTextIn,1024); - bzero(cipherText,1024); - snprintf((char *)&plainTextIn[0],1024,"%s","No casaba melons allowed in the lab."); - int plainLenIn=64; - - int r=crypto_sign_edwards25519sha512batch(cipherText,&cipherLen, - plainTextIn,plainLenIn, - key); - if (r) - return WHY("crypto_sign_edwards25519sha512batch() failed.\n"); - - dump("signature",cipherText,cipherLen); - - unsigned char casabamelons[128]={ - 0xa4,0xea,0xd0,0x7f,0x11,0x65,0x28,0x3f,0x90,0x45,0x87,0xbf,0xe5,0xb9,0x15,0x2a,0x9a,0x2d,0x99,0x35,0x0d,0x0e,0x7b,0xb0,0xcd,0x15,0x2e,0xe8,0xeb,0xb3,0xc2,0xb1,0x13,0x8e,0xe3,0x82,0x55,0x6c,0x6e,0x34,0x44,0xe4,0xbc,0xa3,0xd5,0xe0,0x7a,0x6a,0x67,0x61,0xda,0x79,0x67,0xb6,0x1c,0x2e,0x48,0xc7,0x28,0x5b,0xd8,0xd0,0x54,0x0c,0x4e,0x6f,0x20,0x63,0x61,0x73,0x61,0x62,0x61,0x20,0x6d,0x65,0x6c,0x6f,0x6e,0x73,0x20,0x61,0x6c,0x6c,0x6f,0x77,0x65,0x64,0x20,0x69,0x6e,0x20,0x74,0x68,0x65,0x20,0x6c,0x61,0x62,0x2e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 - }; - - if (cipherLen!=128||memcmp(casabamelons, cipherText, 128)) { - WHY("Computed signature for stored key+message does not match expected value.\n"); - dump("expected signature",casabamelons,sizeof(casabamelons)); - } - - bzero(&plainTextOut,1024); plainLenOut=0; - r=crypto_sign_edwards25519sha512batch_open(plainTextOut,&plainLenOut, - &casabamelons[0],128, - /* the public key, which is the 2nd - half of the secret key. */ - &key[32]); - if (r) - WHY("Cannot open rearranged ref/ version of signature.\n"); - else - cli_printf(context, "Signature open fine.\n"); - - } - - return 0; -} - -DEFINE_CMD(app_route_print, 0, - "Print the routing table", - "route","print"); -static int app_route_print(const struct cli_parsed *parsed, struct cli_context *context) -{ - int mdp_sockfd; - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - - if ((mdp_sockfd = overlay_mdp_client_socket()) < 0) - return WHY("Cannot create MDP socket"); - - overlay_mdp_frame mdp; - bzero(&mdp,sizeof(mdp)); - - mdp.packetTypeAndFlags=MDP_ROUTING_TABLE; - overlay_mdp_send(mdp_sockfd, &mdp,0,0); - - const char *names[]={ - "Subscriber id", - "Routing flags", - "Interface", - "Next hop" - }; - cli_columns(context, 4, names); - size_t rowcount=0; - - while(overlay_mdp_client_poll(mdp_sockfd, 200)){ - overlay_mdp_frame rx; - int ttl; - if (overlay_mdp_recv(mdp_sockfd, &rx, 0, &ttl)) - continue; - - int ofs=0; - while(ofs + sizeof(struct overlay_route_record) <= rx.out.payload_length){ - struct overlay_route_record *p=(struct overlay_route_record *)&rx.out.payload[ofs]; - ofs+=sizeof(struct overlay_route_record); - - if (p->reachable==REACHABLE_NONE) - continue; - - cli_put_string(context, alloca_tohex_sid_t(p->sid), ":"); - char flags[32]; - strbuf b = strbuf_local(flags, sizeof flags); - - switch (p->reachable){ - case REACHABLE_SELF: - strbuf_puts(b, "SELF"); - break; - case REACHABLE_BROADCAST: - strbuf_puts(b, "BROADCAST"); - break; - case REACHABLE_UNICAST: - strbuf_puts(b, "UNICAST"); - break; - case REACHABLE_INDIRECT: - strbuf_puts(b, "INDIRECT"); - break; - default: - strbuf_sprintf(b, "%d", p->reachable); - } - cli_put_string(context, strbuf_str(b), ":"); - cli_put_string(context, p->interface_name, ":"); - cli_put_string(context, alloca_tohex_sid_t(p->neighbour), "\n"); - rowcount++; - } - } - overlay_mdp_client_close(mdp_sockfd); - cli_row_count(context, rowcount); - return 0; -} - DEFINE_CMD(app_reverse_lookup, 0, "Lookup the phone number (DID) and name of a given subscriber (SID)", "reverse", "lookup", "", "[]"); @@ -3220,92 +830,3 @@ static int app_reverse_lookup(const struct cli_parsed *parsed, struct cli_contex return 1; } -void context_switch_test(int); -DEFINE_CMD(app_mem_test, 0, - "Run memory speed test", - "test","memory"); -static int app_mem_test(const struct cli_parsed *UNUSED(parsed), struct cli_context *UNUSED(context)) -{ - size_t mem_size; - size_t addr; - uint64_t count; - - - // First test context switch speed - context_switch_test(1); - - for(mem_size=1024;mem_size<=(128*1024*1024);mem_size*=2) { - uint8_t *mem=malloc(mem_size); - if (!mem) { - fprintf(stderr,"Could not allocate %zdKB memory -- stopping test.\n",mem_size/1024); - return -1; - } - - // Fill memory with random stuff so that we don't have memory page-in - // delays when doing the reads - for(addr=0;addr]"); -static int app_network_scan(const struct cli_parsed *parsed, struct cli_context *context) -{ - int mdp_sockfd; - if (config.debug.verbose) - DEBUG_cli_parsed(parsed); - overlay_mdp_frame mdp; - bzero(&mdp,sizeof(mdp)); - - mdp.packetTypeAndFlags=MDP_SCAN; - - struct overlay_mdp_scan *scan = (struct overlay_mdp_scan *)&mdp.raw; - const char *address; - if (cli_arg(parsed, "address", &address, NULL, NULL) == -1) - return -1; - - if (address){ - if (!inet_aton(address, &scan->addr)) - return WHY("Unable to parse the address"); - }else - INFO("Scanning local networks"); - - if ((mdp_sockfd = overlay_mdp_client_socket()) < 0) - return WHY("Cannot create MDP socket"); - overlay_mdp_send(mdp_sockfd, &mdp, MDP_AWAITREPLY, 5000); - overlay_mdp_client_close(mdp_sockfd); - - if (mdp.packetTypeAndFlags!=MDP_ERROR) - return -1; - cli_put_string(context, mdp.error.message, "\n"); - return mdp.error.error; -} diff --git a/commandline.h b/commandline.h index 2f47783b..7abab7b7 100644 --- a/commandline.h +++ b/commandline.h @@ -1,3 +1,21 @@ +/* + Copyright (C) 2014 Serval Project Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + #ifndef __SERVAL_DNA__COMMANDLINE_H #define __SERVAL_DNA__COMMANDLINE_H @@ -9,6 +27,9 @@ #define _APPEND(X,Y) X ## Y #define _APPEND2(X,Y) _APPEND(X,Y) +struct cli_schema; +struct cli_context; + #define DEFINE_CMD(FUNC, FLAGS, HELP, WORD1, ...) \ static int FUNC(const struct cli_parsed *parsed, struct cli_context *context); \ struct cli_schema _APPEND2(FUNC, __LINE__) \ @@ -23,4 +44,17 @@ extern struct cli_schema __start_commands[]; extern struct cli_schema __stop_commands[]; #define CMD_COUNT (__stop_commands - __start_commands) + +void cli_flush(struct cli_context *context); +int cli_delim(struct cli_context *context, const char *opt); +int cli_puts(struct cli_context *context, const char *str); +void cli_printf(struct cli_context *context, const char *fmt, ...) __attribute__ (( format(printf,2,3) )); +void cli_columns(struct cli_context *context, int columns, const char *names[]); +void cli_row_count(struct cli_context *context, int rows); +void cli_field_name(struct cli_context *context, const char *name, const char *delim); +void cli_put_long(struct cli_context *context, int64_t value, const char *delim); +void cli_put_string(struct cli_context *context, const char *value, const char *delim); +void cli_put_hexvalue(struct cli_context *context, const unsigned char *value, int length, const char *delim); +int cli_write(struct cli_context *context, const unsigned char *buf, size_t len); + #endif \ No newline at end of file diff --git a/conf_cli.c b/conf_cli.c new file mode 100644 index 00000000..c5dbf216 --- /dev/null +++ b/conf_cli.c @@ -0,0 +1,279 @@ +/* +Serval configuration command-line functions +Copyright (C) 2014 Serval Project Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "conf.h" +#include "cli.h" +#include "commandline.h" +#include "strbuf.h" +#include "strbuf_helpers.h" +#include "instance.h" +#include "mdp_client.h" +#include "server.h" + +DEFINE_CMD(app_config_schema, CLIFLAG_PERMISSIVE_CONFIG, + "Display configuration schema.", + "config", "schema"); +static int app_config_schema(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + struct cf_om_node *root = NULL; + if (cf_sch_config_main(&root) == -1) { + cf_om_free_node(&root); + return -1; + } + struct cf_om_iterator it; + for (cf_om_iter_start(&it, root); it.node; cf_om_iter_next(&it)) + if (it.node->text || it.node->nodc == 0) { + cli_put_string(context, it.node->fullkey,"="); + cli_put_string(context, it.node->text, "\n"); + } + cf_om_free_node(&root); + return 0; +} + +DEFINE_CMD(app_config_dump, CLIFLAG_PERMISSIVE_CONFIG, + "Dump configuration settings.", + "config","dump","[--full]"); +static int app_config_dump(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + int full = 0 == cli_arg(parsed, "--full", NULL, NULL, NULL); + if (create_serval_instance_dir() == -1) + return -1; + struct cf_om_node *root = NULL; + int ret = cf_fmt_config_main(&root, &config); + if (ret == CFERROR) { + cf_om_free_node(&root); + return -1; + } + struct cf_om_iterator it; + for (cf_om_iter_start(&it, root); it.node; cf_om_iter_next(&it)) { + if (it.node->text && (full || it.node->line_number)) { + cli_put_string(context, it.node->fullkey, "="); + cli_put_string(context, it.node->text, "\n"); + } + } + cf_om_free_node(&root); + return ret == CFOK ? 0 : 1; +} + +static int mdp_client_sync_config(time_ms_t timeout) +{ + /* Bind to MDP socket and await confirmation */ + struct mdp_header mdp_header = { + .remote.port = MDP_SYNC_CONFIG, + }; + int mdpsock = mdp_socket(); + if (mdpsock == -1) + return WHY("cannot create MDP socket"); + set_nonblock(mdpsock); + int r = mdp_send(mdpsock, &mdp_header, NULL, 0); + if (r == -1) + return -1; + time_ms_t deadline = gettime_ms() + timeout; // TODO add --timeout option + struct mdp_header rev_header; + do { + ssize_t len = mdp_poll_recv(mdpsock, deadline, &rev_header, NULL, 0); + if (len == -1) + return -1; + if (len == -2) { + WHYF("timeout while synchronising daemon configuration"); + return -1; + } + } while (!(rev_header.flags & MDP_FLAG_CLOSE)); + return 0; +} + +DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG, + "Set and del specified configuration variables.", + "config","set","","","..."); +DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG, + "Del and set specified configuration variables.", + "config","del","","..."); +DEFINE_CMD(app_config_set, CLIFLAG_PERMISSIVE_CONFIG, + "Synchronise with the daemon's configuration.", + "config","sync","..."); +static int app_config_set(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + if (create_serval_instance_dir() == -1) + return -1; + // + // This fixes a subtle bug in when upgrading the Batphone app: the servald.conf file does + // not get upgraded. The bug goes like this: + // 1. new Batphone APK is installed, but prior servald.conf is not overwritten because it + // comes in serval.zip; + // 2. new Batphone is started, which calls JNI "stop" command, which reads the old servald.conf + // into memory buffer; + // 3. new Batphone unpacks serval.zip, overwriting servald.conf with new version; + // 4. new Batphone calls JNI "config set rhizome.enable 1", which sets the "rhizome.enable" + // config option in the existing memory buffer and overwrites servald.conf; + // Bingo, the old version of servald.conf is what remains. This kludge intervenes in step 4, by + // reading the new servald.conf into the memory buffer before applying the "rhizome.enable" set + // value and overwriting. + if (cf_om_reload() == -1) + return -1; + // + const char *var[parsed->argc - 1]; + const char *val[parsed->argc - 1]; + unsigned nvar = 0; + unsigned i; + for (i = 1; i < parsed->argc; ++i) { + const char *arg = parsed->args[i]; + int iv = -1; + if (strcmp(arg, "set") == 0) { + if (i + 2 > parsed->argc) + return WHYF("malformed command at args[%d]: 'set' not followed by two arguments", i); + var[nvar] = parsed->args[iv = ++i]; + val[nvar] = parsed->args[++i]; + } else if (strcmp(arg, "del") == 0) { + if (i + 1 > parsed->argc) + return WHYF("malformed command at args[%d]: 'del' not followed by one argument", i); + var[nvar] = parsed->args[iv = ++i]; + val[nvar] = NULL; + } else if (strcmp(arg, "sync") == 0) + var[nvar] = val[nvar] = NULL; + else + return WHYF("malformed command at args[%d]: unsupported action '%s'", i, arg); + if (var[nvar] && !is_configvarname(var[nvar])) + return WHYF("malformed command at args[%d]: '%s' is not a valid config option name", iv, var[nvar]); + ++nvar; + } + int changed = 0; + for (i = 0; i < nvar; ++i) { + if (var[i]) { + if (cf_om_set(&cf_om_root, var[i], val[i]) == -1) + return -1; + if (val[i]) + INFOF("config set %s %s", var[i], alloca_str_toprint(val[i])); + else + INFOF("config del %s", var[i]); + changed = 1; + } else { + if (changed) { + if (cf_om_save() == -1) + return -1; + if (cf_reload() == -1) // logs an error if the new config is bad + return 2; + changed = 0; + } + int pid = server_pid(); + if (pid) { + INFO("config sync"); + // TODO make timeout configurable with --timeout option. + if (mdp_client_sync_config(10000) == -1) + return 3; + } else + INFO("config sync -- skipped, server not running"); + } + } + if (changed) { + if (cf_om_save() == -1) + return -1; + if (cf_reload() == -1) // logs an error if the new config is bad + return 2; + } + return 0; +} + +DEFINE_CMD(app_config_get, CLIFLAG_PERMISSIVE_CONFIG, + "Get specified configuration variable.", + "config","get","[]"); +static int app_config_get(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *var; + if (cli_arg(parsed, "variable", &var, is_configvarpattern, NULL) == -1) + return -1; + if (create_serval_instance_dir() == -1) + return -1; + if (cf_om_reload() == -1) + return -1; + if (var && is_configvarname(var)) { + const char *value = cf_om_get(cf_om_root, var); + if (value) { + cli_field_name(context, var, "="); + cli_put_string(context, value, "\n"); + } + } else { + struct cf_om_iterator it; + for (cf_om_iter_start(&it, cf_om_root); it.node; cf_om_iter_next(&it)) { + if (var && cf_om_match(var, it.node) <= 0) + continue; + if (it.node->text) { + cli_field_name(context, it.node->fullkey, "="); + cli_put_string(context, it.node->text, "\n"); + } + } + } + return 0; +} + +DEFINE_CMD(app_config_paths, CLIFLAG_PERMISSIVE_CONFIG, + "Dump file and directory paths.", + "config", "paths"); +static int app_config_paths(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + if (cf_om_reload() == -1) + return -1; + char path[1024]; + if (FORMF_SERVAL_ETC_PATH(path, NULL)) { + cli_field_name(context, "SERVAL_ETC_PATH", ":"); + cli_put_string(context, path, "\n"); + } + if (FORMF_SERVAL_RUN_PATH(path, NULL)) { + cli_field_name(context, "SERVAL_RUN_PATH", ":"); + cli_put_string(context, path, "\n"); + } + if (FORMF_SERVAL_CACHE_PATH(path, NULL)) { + cli_field_name(context, "SERVAL_CACHE_PATH", ":"); + cli_put_string(context, path, "\n"); + } + strbuf sb = strbuf_local(path, sizeof path); + strbuf_system_log_path(sb); + if (!strbuf_overrun(sb)) { + cli_field_name(context, "SYSTEM_LOG_PATH", ":"); + cli_put_string(context, path, "\n"); + } + strbuf_reset(sb); + strbuf_serval_log_path(sb); + if (!strbuf_overrun(sb)) { + cli_field_name(context, "SERVAL_LOG_PATH", ":"); + cli_put_string(context, path, "\n"); + } + if (FORMF_SERVAL_TMP_PATH(path, NULL)) { + cli_field_name(context, "SERVAL_TMP_PATH", ":"); + cli_put_string(context, path, "\n"); + } + if (FORMF_SERVALD_PROC_PATH(path, NULL)) { + cli_field_name(context, "SERVALD_PROC_PATH", ":"); + cli_put_string(context, path, "\n"); + } + if (FORMF_RHIZOME_STORE_PATH(path, NULL)) { + cli_field_name(context, "RHIZOME_STORE_PATH", ":"); + cli_put_string(context, path, "\n"); + } + return 0; +} diff --git a/httpd.c b/httpd.c index 9524bf1e..db51e04c 100644 --- a/httpd.c +++ b/httpd.c @@ -25,6 +25,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "overlay_interface.h" #include "mem.h" #include "net.h" +#include "server.h" #define RHIZOME_SERVER_MAX_LIVE_REQUESTS 32 diff --git a/keyring.c b/keyring.c index 47f13b6a..5bc981fb 100644 --- a/keyring.c +++ b/keyring.c @@ -35,6 +35,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "str.h" #include "mem.h" #include "rotbuf.h" +#include "server.h" static void keyring_free_keypair(keypair *kp); static void keyring_free_context(keyring_context *c); diff --git a/keyring_cli.c b/keyring_cli.c new file mode 100644 index 00000000..6232ef74 --- /dev/null +++ b/keyring_cli.c @@ -0,0 +1,472 @@ +/* + Serval keyring command line functions + Copyright (C) 2014 Serval Project Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include "serval_types.h" +#include "dataformats.h" +#include "os.h" +#include "conf.h" +#include "mdp_client.h" +#include "cli.h" +#include "commandline.h" +#include "keyring.h" + +DEFINE_CMD(app_keyring_create, 0, + "Create a new keyring file.", + "keyring","create"); +static int app_keyring_create(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + keyring_file *k = keyring_open_instance(); + if (!k) + return -1; + keyring_free(k); + return 0; +} + +DEFINE_CMD(app_keyring_dump, 0, + "Dump all keyring identities that can be accessed using the specified PINs", + "keyring","dump" KEYRING_PIN_OPTIONS,"[--secret]","[]"); +static int app_keyring_dump(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *path; + if (cli_arg(parsed, "file", &path, cli_path_regular, NULL) == -1) + return -1; + int include_secret = 0 == cli_arg(parsed, "--secret", NULL, NULL, NULL); + keyring_file *k = keyring_open_instance_cli(parsed); + if (!k) + return -1; + FILE *fp = path ? fopen(path, "w") : stdout; + if (fp == NULL) { + WHYF_perror("fopen(%s, \"w\")", alloca_str_toprint(path)); + keyring_free(k); + return -1; + } + int ret = keyring_dump(k, XPRINTF_STDIO(fp), include_secret); + if (fp != stdout && fclose(fp) == EOF) { + WHYF_perror("fclose(%s)", alloca_str_toprint(path)); + keyring_free(k); + return -1; + } + keyring_free(k); + return ret; +} + +DEFINE_CMD(app_keyring_load, 0, + "Load identities from the given dump text and insert them into the keyring using the specified entry PINs", + "keyring","load" KEYRING_PIN_OPTIONS,"","[]","[]..."); +static int app_keyring_load(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *path; + if (cli_arg(parsed, "file", &path, cli_path_regular, NULL) == -1) + return -1; + const char *kpin; + if (cli_arg(parsed, "keyring-pin", &kpin, NULL, "") == -1) + return -1; + unsigned pinc = 0; + unsigned i; + for (i = 0; i < parsed->labelc; ++i) + if (strn_str_cmp(parsed->labelv[i].label, parsed->labelv[i].len, "entry-pin") == 0) + ++pinc; + const char *pinv[pinc]; + unsigned pc = 0; + for (i = 0; i < parsed->labelc; ++i) + if (strn_str_cmp(parsed->labelv[i].label, parsed->labelv[i].len, "entry-pin") == 0) { + assert(pc < pinc); + pinv[pc++] = parsed->labelv[i].text; + } + keyring_file *k = keyring_open_instance_cli(parsed); + if (!k) + return -1; + FILE *fp = path && strcmp(path, "-") != 0 ? fopen(path, "r") : stdin; + if (fp == NULL) { + WHYF_perror("fopen(%s, \"r\")", alloca_str_toprint(path)); + keyring_free(k); + return -1; + } + if (keyring_load(k, kpin, pinc, pinv, fp) == -1) { + keyring_free(k); + return -1; + } + if (keyring_commit(k) == -1) { + keyring_free(k); + return WHY("Could not write new identity"); + } + keyring_free(k); + return 0; +} + +DEFINE_CMD(app_keyring_list, 0, + "List identities that can be accessed using the supplied PINs", + "keyring","list" KEYRING_PIN_OPTIONS); +static int app_keyring_list(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + keyring_file *k = keyring_open_instance_cli(parsed); + if (!k) + return -1; + + const char *names[]={ + "sid", + "did", + "name" + }; + cli_columns(context, 3, names); + size_t rowcount = 0; + + unsigned cn, in; + for (cn = 0; cn < k->context_count; ++cn) + for (in = 0; in < k->contexts[cn]->identity_count; ++in) { + const sid_t *sidp = NULL; + const char *did = NULL; + const char *name = NULL; + keyring_identity_extract(k->contexts[cn]->identities[in], &sidp, &did, &name); + if (sidp || did) { + cli_put_string(context, alloca_tohex_sid_t(*sidp), ":"); + cli_put_string(context, did, ":"); + cli_put_string(context, name, "\n"); + rowcount++; + } + } + keyring_free(k); + cli_row_count(context, rowcount); + return 0; +} + +static void cli_output_identity(struct cli_context *context, const keyring_identity *id) +{ + unsigned i; + for (i=0;ikeypair_count;i++){ + keypair *kp=id->keypairs[i]; + switch(kp->type){ + case KEYTYPE_CRYPTOBOX: + cli_field_name(context, "sid", ":"); + cli_put_string(context, alloca_tohex(kp->public_key, kp->public_key_len), "\n"); + break; + case KEYTYPE_DID: + { + char *str = (char*)kp->private_key; + int l = strlen(str); + if (l){ + cli_field_name(context, "did", ":"); + cli_put_string(context, str, "\n"); + } + str = (char*)kp->public_key; + l=strlen(str); + if (l){ + cli_field_name(context, "name", ":"); + cli_put_string(context, str, "\n"); + } + } + break; + case KEYTYPE_PUBLIC_TAG: + { + const char *name; + const unsigned char *value; + size_t length; + if (keyring_unpack_tag(kp->public_key, kp->public_key_len, &name, &value, &length)==0){ + cli_field_name(context, name, ":"); + cli_put_string(context, alloca_toprint_quoted(-1, value, length, NULL), "\n"); + } + } + break; + } + } +} + +DEFINE_CMD(app_keyring_add, 0, + "Create a new identity in the keyring protected by the supplied PIN (empty PIN if not given)", + "keyring","add" KEYRING_PIN_OPTIONS,"[]"); +static int app_keyring_add(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *pin; + cli_arg(parsed, "pin", &pin, NULL, ""); + keyring_file *k = keyring_open_instance_cli(parsed); + if (!k) + return -1; + keyring_enter_pin(k, pin); + assert(k->context_count > 0); + const keyring_identity *id = keyring_create_identity(k, k->contexts[k->context_count - 1], pin); + if (id == NULL) { + keyring_free(k); + return WHY("Could not create new identity"); + } + const sid_t *sidp = NULL; + const char *did = ""; + const char *name = ""; + keyring_identity_extract(id, &sidp, &did, &name); + if (!sidp) { + keyring_free(k); + return WHY("New identity has no SID"); + } + if (keyring_commit(k) == -1) { + keyring_free(k); + return WHY("Could not write new identity"); + } + cli_output_identity(context, id); + keyring_free(k); + return 0; +} + +DEFINE_CMD(app_keyring_set_did, 0, + "Set the DID for the specified SID (must supply PIN to unlock the SID record in the keyring)", + "keyring", "set","did" KEYRING_PIN_OPTIONS,"","",""); +static int app_keyring_set_did(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *sidhex, *did, *name; + + if (cli_arg(parsed, "sid", &sidhex, str_is_subscriber_id, "") == -1 || + cli_arg(parsed, "did", &did, cli_optional_did, "") == -1 || + cli_arg(parsed, "name", &name, NULL, "") == -1) + return -1; + + if (strlen(name)>63) + return WHY("Name too long (31 char max)"); + + sid_t sid; + if (str_to_sid_t(&sid, sidhex) == -1){ + keyring_free(keyring); + keyring = NULL; + return WHY("str_to_sid_t() failed"); + } + + if (!(keyring = keyring_open_instance_cli(parsed))) + return -1; + + unsigned cn=0, in=0, kp=0; + int r=0; + if (!keyring_find_sid(keyring, &cn, &in, &kp, &sid)) + r=WHY("No matching SID"); + else{ + if (keyring_set_did(keyring->contexts[cn]->identities[in], did, name)) + r=WHY("Could not set DID"); + else{ + if (keyring_commit(keyring)) + r=WHY("Could not write updated keyring record"); + else{ + cli_output_identity(context, keyring->contexts[cn]->identities[in]); + } + } + } + + keyring_free(keyring); + keyring = NULL; + return r; +} + +DEFINE_CMD(app_keyring_set_tag, 0, + "Set a named tag for the specified SID (must supply PIN to unlock the SID record in the keyring)", + "keyring", "set","tag" KEYRING_PIN_OPTIONS,"","",""); +static int app_keyring_set_tag(const struct cli_parsed *parsed, struct cli_context *context) +{ + const char *sidhex, *tag, *value; + if (cli_arg(parsed, "sid", &sidhex, str_is_subscriber_id, "") == -1 || + cli_arg(parsed, "tag", &tag, NULL, "") == -1 || + cli_arg(parsed, "value", &value, NULL, "") == -1 ) + return -1; + + if (!(keyring = keyring_open_instance_cli(parsed))) + return -1; + + sid_t sid; + if (str_to_sid_t(&sid, sidhex) == -1) + return WHY("str_to_sid_t() failed"); + + unsigned cn=0, in=0, kp=0; + int r=0; + if (!keyring_find_sid(keyring, &cn, &in, &kp, &sid)) + r=WHY("No matching SID"); + else{ + int length = strlen(value); + if (keyring_set_public_tag(keyring->contexts[cn]->identities[in], tag, (const unsigned char*)value, length)) + r=WHY("Could not set tag value"); + else{ + if (keyring_commit(keyring)) + r=WHY("Could not write updated keyring record"); + else{ + cli_output_identity(context, keyring->contexts[cn]->identities[in]); + } + } + } + + keyring_free(keyring); + keyring = NULL; + return r; +} + +static int handle_pins(const struct cli_parsed *parsed, struct cli_context *UNUSED(context), int revoke) +{ + const char *pin, *sid_hex; + if (cli_arg(parsed, "entry-pin", &pin, NULL, "") == -1 || + cli_arg(parsed, "sid", &sid_hex, str_is_subscriber_id, "") == -1) + return -1; + + int ret=1; + struct mdp_header header={ + .remote.port=MDP_IDENTITY, + }; + int mdp_sock = mdp_socket(); + set_nonblock(mdp_sock); + + unsigned char request_payload[1200]; + struct mdp_identity_request *request = (struct mdp_identity_request *)request_payload; + + if (revoke){ + request->action=ACTION_LOCK; + }else{ + request->action=ACTION_UNLOCK; + } + size_t len = sizeof(struct mdp_identity_request); + if (pin && *pin) { + request->type=TYPE_PIN; + size_t pin_siz = strlen(pin) + 1; + if (pin_siz + len > sizeof(request_payload)) + return WHY("Supplied pin is too long"); + bcopy(pin, &request_payload[len], pin_siz); + len += pin_siz; + }else if(sid_hex && *sid_hex){ + request->type=TYPE_SID; + sid_t sid; + if (str_to_sid_t(&sid, sid_hex) == -1) + return WHY("str_to_sid_t() failed"); + bcopy(sid.binary, &request_payload[len], sizeof(sid)); + len += sizeof(sid); + } + + if (mdp_send(mdp_sock, &header, request_payload, len) == -1) + goto end; + + time_ms_t timeout=gettime_ms()+500; + while(1){ + struct mdp_header rev_header; + unsigned char response_payload[1600]; + ssize_t len = mdp_poll_recv(mdp_sock, timeout, &rev_header, response_payload, sizeof(response_payload)); + if (len==-1) + break; + if (len==-2){ + WHYF("Timeout while waiting for response"); + break; + } + if (rev_header.flags & MDP_FLAG_CLOSE){ + ret=0; + break; + } + } +end: + mdp_close(mdp_sock); + return ret; +} + +DEFINE_CMD(app_revoke_pin, 0, + "Unload any identities protected by this pin and drop all routes to them", + "id", "relinquish", "pin", ""); +DEFINE_CMD(app_revoke_pin, 0, + "Unload a specific identity and drop all routes to it", + "id", "relinquish", "sid", ""); +int app_revoke_pin(const struct cli_parsed *parsed, struct cli_context *context) +{ + return handle_pins(parsed, context, 1); +} + +DEFINE_CMD(app_id_pin, 0, + "Unlock any pin protected identities and enable routing packets to them", + "id", "enter", "pin", ""); +static int app_id_pin(const struct cli_parsed *parsed, struct cli_context *context) +{ + return handle_pins(parsed, context, 0); +} + +DEFINE_CMD(app_id_list, 0, + "Search unlocked identities based on an optional tag and value", + "id", "list", "[]", "[]"); +static int app_id_list(const struct cli_parsed *parsed, struct cli_context *context) +{ + const char *tag, *value; + if (cli_arg(parsed, "tag", &tag, NULL, "") == -1 || + cli_arg(parsed, "value", &value, NULL, "") == -1 ) + return -1; + + int ret=-1; + struct mdp_header header={ + .remote.port=MDP_SEARCH_IDS, + }; + int mdp_sock = mdp_socket(); + set_nonblock(mdp_sock); + + unsigned char request_payload[1200]; + size_t len=0; + + if (tag && *tag){ + size_t value_len=0; + if (value && *value) + value_len = strlen(value); + len = sizeof(request_payload); + if (keyring_pack_tag(request_payload, &len, tag, (unsigned char*)value, value_len)) + goto end; + } + + if (mdp_send(mdp_sock, &header, request_payload, len) == -1) + goto end; + + const char *names[]={ + "sid" + }; + cli_columns(context, 1, names); + size_t rowcount=0; + + time_ms_t timeout=gettime_ms()+500; + while(1){ + struct mdp_header rev_header; + unsigned char response_payload[1600]; + ssize_t len = mdp_poll_recv(mdp_sock, timeout, &rev_header, response_payload, sizeof(response_payload)); + if (len==-1) + break; + if (len==-2){ + WHYF("Timeout while waiting for response"); + break; + } + + if (len>=SID_SIZE){ + rowcount++; + sid_t *id = (sid_t*)response_payload; + cli_put_hexvalue(context, id->binary, sizeof(sid_t), "\n"); + // TODO receive and decode other details about this identity + } + + if (rev_header.flags & MDP_FLAG_CLOSE){ + ret=0; + break; + } + } + cli_row_count(context, rowcount); +end: + mdp_close(mdp_sock); + return ret; +} + diff --git a/mdp_client.c b/mdp_client.c index 141b8e96..b41de4a0 100644 --- a/mdp_client.c +++ b/mdp_client.c @@ -161,6 +161,25 @@ int _mdp_poll(struct __sourceloc UNUSED(__whence), int socket, time_ms_t timeout return overlay_mdp_client_poll(socket, timeout_ms); } +// returns -1 on error, -2 on timeout, packet length on success. +ssize_t mdp_poll_recv(int mdp_sock, time_ms_t deadline, struct mdp_header *rev_header, unsigned char *payload, size_t buffer_size) +{ + time_ms_t now = gettime_ms(); + if (now > deadline) + return -2; + int p = mdp_poll(mdp_sock, deadline - now); + if (p == -1) + return WHY_perror("mdp_poll"); + if (p == 0) + return -2; + ssize_t len = mdp_recv(mdp_sock, rev_header, payload, buffer_size); + if (len == -1) + return -1; + if (rev_header->flags & MDP_FLAG_ERROR) + return WHY("Operation failed, check the daemon log for more information"); + return len; +} + int overlay_mdp_send(int mdp_sockfd, overlay_mdp_frame *mdp, int flags, int timeout_ms) { if (mdp_sockfd == -1) diff --git a/mdp_client.h b/mdp_client.h index f0a21992..7023192a 100644 --- a/mdp_client.h +++ b/mdp_client.h @@ -144,6 +144,7 @@ int _mdp_close(struct __sourceloc, int socket); int _mdp_send(struct __sourceloc, int socket, const struct mdp_header *header, const uint8_t *payload, size_t len); ssize_t _mdp_recv(struct __sourceloc, int socket, struct mdp_header *header, uint8_t *payload, size_t max_len); int _mdp_poll(struct __sourceloc, int socket, time_ms_t timeout_ms); +ssize_t mdp_poll_recv(int mdp_sock, time_ms_t deadline, struct mdp_header *rev_header, unsigned char *payload, size_t buffer_size); #define mdp_socket() _mdp_socket(__WHENCE__) #define mdp_close(s) _mdp_close(__WHENCE__, (s)) #define mdp_send(s,h,p,l) _mdp_send(__WHENCE__, (s), (h), (p), (l)) diff --git a/mdp_filter.c b/mdp_filter.c index 837df772..8245cb13 100644 --- a/mdp_filter.c +++ b/mdp_filter.c @@ -27,6 +27,7 @@ #include "conf.h" #include "mem.h" #include "str.h" +#include "server.h" //#define DEBUG_MDP_FILTER_PARSING 1 diff --git a/network_cli.c b/network_cli.c new file mode 100644 index 00000000..4e3e2a33 --- /dev/null +++ b/network_cli.c @@ -0,0 +1,526 @@ +/* + Serval network command line functions + Copyright (C) 2014 Serval Project Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include "dataformats.h" +#include "mdp_client.h" +#include "conf.h" +#include "cli.h" +#include "commandline.h" +#include "sighandlers.h" + +DEFINE_CMD(app_mdp_ping, 0, + "Attempts to ping specified node via Mesh Datagram Protocol (MDP).", + "mdp","ping","[--interval=]","[--timeout=]","[--wait-for-duplicates]", + "|broadcast","[]"); +static int app_mdp_ping(const struct cli_parsed *parsed, struct cli_context *context) +{ + int mdp_sockfd; + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *sidhex, *count, *opt_timeout, *opt_interval; + int opt_wait_for_duplicates = 0 == cli_arg(parsed, "--wait-for-duplicates", NULL, NULL, NULL); + if ( cli_arg(parsed, "--timeout", &opt_timeout, cli_interval_ms, "1") == -1 + || cli_arg(parsed, "--interval", &opt_interval, cli_interval_ms, "1") == -1 + || cli_arg(parsed, "SID", &sidhex, str_is_subscriber_id, "broadcast") == -1 + || cli_arg(parsed, "count", &count, cli_uint, "0") == -1) + return -1; + + /* Get SID that we want to ping. + TODO - allow lookup of SID prefixes and telephone numbers + (that would require MDP lookup of phone numbers, which doesn't yet occur) */ + sid_t ping_sid; + if (str_to_sid_t(&ping_sid, sidhex) == -1) + return WHY("str_to_sid_t() failed"); + + // assume we wont hear any responses + int ret=1; + unsigned icount = atoi(count); + int64_t timeout_ms = 1000; + str_to_uint64_interval_ms(opt_timeout, &timeout_ms, NULL); + if (timeout_ms == 0) + timeout_ms = 60 * 60000; // 1 hour... + int64_t interval_ms = 1000; + str_to_uint64_interval_ms(opt_interval, &interval_ms, NULL); + if (interval_ms == 0) + interval_ms = 1000; + + /* First sequence number in the echo frames */ + uint32_t firstSeq = random(); + uint32_t sequence_number = firstSeq; + + int broadcast = is_sid_t_broadcast(ping_sid); + + /* Bind to MDP socket and await confirmation */ + if ((mdp_sockfd = mdp_socket()) < 0) + return WHY("Cannot create MDP socket"); + + set_nonblock(mdp_sockfd); + struct mdp_header mdp_header; + bzero(&mdp_header, sizeof(mdp_header)); + + mdp_header.local.sid = BIND_PRIMARY; + mdp_header.remote.sid = ping_sid; + mdp_header.remote.port = MDP_PORT_ECHO; + mdp_header.qos = OQ_MESH_MANAGEMENT; + mdp_header.ttl = PAYLOAD_TTL_DEFAULT; + mdp_header.flags = MDP_FLAG_BIND; + if (broadcast) + mdp_header.flags |= MDP_FLAG_NO_CRYPT; + + /* TODO Eventually we should try to resolve SID to phone number and vice versa */ + cli_printf(context, "MDP PING %s: 12 data bytes", alloca_tohex_sid_t(ping_sid)); + cli_delim(context, "\n"); + cli_flush(context); + + unsigned tx_count = 0; + unsigned missing_pong_count = 0; + unsigned rx_count = 0; + unsigned rx_dupcount = 0; + unsigned rx_igncount = 0; + time_ms_t rx_mintime_ms = -1; + time_ms_t rx_maxtime_ms = -1; + time_ms_t rx_tottime_ms = 0; + struct packet_stat { + uint32_t sequence; + time_ms_t tx_time; + time_ms_t rx_time; + unsigned pong_count; + } stats[1024]; + bzero(stats, sizeof stats); + + if (broadcast) + WARN("broadcast ping packets will not be encrypted"); + + sigIntFlag = 0; + signal(SIGINT, sigIntHandler); + + while (!sigIntFlag && (icount == 0 || tx_count < icount)) { + time_ms_t now = gettime_ms(); + + // send a ping packet + if (tx_count == 0 || !(mdp_header.flags & MDP_FLAG_BIND)) { + uint8_t payload[12]; + write_uint32(&payload[0], sequence_number); + write_uint64(&payload[4], now); + int r = mdp_send(mdp_sockfd, &mdp_header, payload, sizeof(payload)); + if (r != -1) { + if (config.debug.mdprequests) + DEBUGF("ping seq=%lu", (unsigned long)(sequence_number - firstSeq) + 1); + unsigned i = (unsigned long)(sequence_number - firstSeq) % NELS(stats); + assert(i == tx_count % NELS(stats)); + struct packet_stat *stat = &stats[i]; + if (stat->tx_time && stat->pong_count == 0) { + assert(missing_pong_count > 0); + --missing_pong_count; + } + stat->sequence = sequence_number; + stat->tx_time = now; + stat->pong_count = 0; + ++missing_pong_count; + ++sequence_number; + ++tx_count; + } + } + + // Now look for replies ("pongs") until one second has passed, and print any replies with + // appropriate information as required + int all_sent = icount && tx_count >= icount; + time_ms_t finish = now + (all_sent ? timeout_ms : interval_ms); + while (!sigIntFlag && now < finish && (!all_sent || opt_wait_for_duplicates || missing_pong_count)) { + time_ms_t poll_timeout_ms = finish - now; + if (mdp_poll(mdp_sockfd, poll_timeout_ms) <= 0) { + now = gettime_ms(); + continue; + } + + struct mdp_header mdp_recv_header; + uint8_t recv_payload[12]; + ssize_t len = mdp_recv(mdp_sockfd, &mdp_recv_header, recv_payload, sizeof(recv_payload)); + if (len == -1) + break; + if (mdp_recv_header.flags & MDP_FLAG_ERROR) { + WHY("error from daemon, please check the log for more information"); + continue; + } + if (mdp_recv_header.flags & MDP_FLAG_BIND){ + // received port binding confirmation + mdp_header.local = mdp_recv_header.local; + mdp_header.flags &= ~MDP_FLAG_BIND; + if (config.debug.mdprequests) + DEBUGF("bound to %s:%d", alloca_tohex_sid_t(mdp_header.local.sid), mdp_header.local.port); + continue; + } + if ((size_t)len < sizeof(recv_payload)){ + if (config.debug.mdprequests) + DEBUGF("ignoring short pong"); + continue; + } + uint32_t rxseq = read_uint32(&recv_payload[0]); + time_ms_t txtime = read_uint64(&recv_payload[4]); + int hop_count = 64 - mdp_recv_header.ttl; + now = gettime_ms(); + time_ms_t delay = now - txtime; + + struct packet_stat *stat = &stats[(unsigned long)(rxseq - firstSeq) % NELS(stats)]; + if (stat->sequence != rxseq || stat->tx_time != txtime) { + if (config.debug.mdprequests) + DEBUGF("ignoring spurious pong"); + ++rx_igncount; + stat = NULL; // old or corrupted reply (either sequence or txtime is wrong) + } else if (stat->pong_count++ == 0) { + assert(missing_pong_count > 0); + --missing_pong_count; + stat->rx_time = now; + rx_tottime_ms += delay; + ++rx_count; + if (rx_mintime_ms > delay || rx_mintime_ms == -1) + rx_mintime_ms = delay; + if (delay > rx_maxtime_ms) + rx_maxtime_ms = delay; + } else + ++rx_dupcount; + + cli_put_hexvalue(context, mdp_recv_header.remote.sid.binary, SID_SIZE, ": seq="); + cli_put_long(context, (unsigned long)(rxseq - firstSeq) + 1, " time="); + cli_put_long(context, delay, "ms hops="); + cli_put_long(context, hop_count, ""); + cli_put_string(context, (mdp_recv_header.flags & MDP_FLAG_NO_CRYPT) ? "" : " ENCRYPTED", ""); + cli_put_string(context, (mdp_recv_header.flags & MDP_FLAG_NO_SIGN) ? "" : " SIGNED", "\n"); + cli_flush(context); + + ret=0; + } + } + + signal(SIGINT, SIG_DFL); + sigIntFlag = 0; + mdp_close(mdp_sockfd); + + { + float rx_stddev = 0; + float rx_mean = rx_tottime_ms * 1.0 / rx_count; + unsigned tx_samples = tx_count < NELS(stats) ? tx_count : NELS(stats); + unsigned rx_samples = 0; + unsigned i; + for (i = 0; i < tx_samples; ++i) { + struct packet_stat *stat = &stats[i]; + if (stat->pong_count) { + float dev = rx_mean - (stat->rx_time - stat->tx_time); + rx_stddev += dev * dev; + ++rx_samples; + } + } + rx_stddev /= rx_samples; + rx_stddev = sqrtf(rx_stddev); + + /* XXX Report final statistics before going */ + cli_printf(context, "--- %s ping statistics ---\n", alloca_tohex_sid_t(ping_sid)); + cli_printf(context, "%u packets transmitted, %u packets received (plus %u duplicates, %u ignored), %3.1f%% packet loss\n", + tx_count, + rx_count, + rx_dupcount, + rx_igncount, + tx_count ? (tx_count - rx_count) * 100.0 / tx_count : 0 + ); + if (rx_samples) + cli_printf(context, "round-trip min/avg/max/stddev = %"PRId64"/%.3f/%"PRId64"/%.3f ms (%u samples)\n", + rx_mintime_ms, rx_mean, rx_maxtime_ms, rx_stddev, rx_samples); + cli_delim(context, NULL); + cli_flush(context); + } + return ret; +} + +DEFINE_CMD(app_trace, 0, + "Trace through the network to the specified node via MDP.", + "mdp","trace",""); +static int app_trace(const struct cli_parsed *parsed, struct cli_context *context) +{ + int mdp_sockfd; + const char *sidhex; + if (cli_arg(parsed, "SID", &sidhex, str_is_subscriber_id, NULL) == -1) + return -1; + + sid_t srcsid; + sid_t dstsid; + if (str_to_sid_t(&dstsid, sidhex) == -1) + return WHY("str_to_sid_t() failed"); + + if ((mdp_sockfd = overlay_mdp_client_socket()) < 0) + return WHY("Cannot create MDP socket"); + mdp_port_t port=32768+(random()&32767); + if (overlay_mdp_getmyaddr(mdp_sockfd, 0, &srcsid)) { + overlay_mdp_client_close(mdp_sockfd); + return WHY("Could not get local address"); + } + if (overlay_mdp_bind(mdp_sockfd, &srcsid, port)) { + overlay_mdp_client_close(mdp_sockfd); + return WHY("Could not bind to MDP socket"); + } + + overlay_mdp_frame mdp; + bzero(&mdp, sizeof(mdp)); + + mdp.out.src.sid = srcsid; + mdp.out.dst.sid = srcsid; + mdp.out.src.port=port; + mdp.out.dst.port=MDP_PORT_TRACE; + mdp.packetTypeAndFlags=MDP_TX; + struct overlay_buffer *b = ob_static(mdp.out.payload, sizeof(mdp.out.payload)); + ob_append_byte(b, SID_SIZE); + ob_append_bytes(b, srcsid.binary, SID_SIZE); + ob_append_byte(b, SID_SIZE); + ob_append_bytes(b, dstsid.binary, SID_SIZE); + int ret; + if (ob_overrun(b)) + ret = WHY("overlay buffer overrun"); + else { + mdp.out.payload_length = ob_position(b); + cli_printf(context, "Tracing the network path from %s to %s", + alloca_tohex_sid_t(srcsid), alloca_tohex_sid_t(dstsid)); + cli_delim(context, "\n"); + cli_flush(context); + ret = overlay_mdp_send(mdp_sockfd, &mdp, MDP_AWAITREPLY, 5000); + if (ret) + WHYF("overlay_mdp_send returned %d", ret); + } + ob_free(b); + if (ret == 0) { + int offset=0; + { + // skip the first two sid's + int len = mdp.out.payload[offset++]; + offset+=len; + len = mdp.out.payload[offset++]; + offset+=len; + } + int i=0; + while(offsetlabelc ? parsed->labelv[0].text : ""; + if (!strcasecmp(arg,"self")) + a.addrlist.mode = MDP_ADDRLIST_MODE_SELF; /* get own identities */ + else if (!strcasecmp(arg,"allpeers")) + a.addrlist.mode = MDP_ADDRLIST_MODE_ALL_PEERS; /* get all known peers */ + else if (!strcasecmp(arg,"peers")) + a.addrlist.mode = MDP_ADDRLIST_MODE_ROUTABLE_PEERS; /* get routable (reachable) peers */ + else + return WHYF("unsupported arg '%s'", arg); + a.addrlist.first_sid=0; + + if ((mdp_sockfd = overlay_mdp_client_socket()) < 0) + return WHY("Cannot create MDP socket"); + + const char *names[]={ + "sid" + }; + cli_columns(context, 1, names); + size_t rowcount=0; + + do{ + result=overlay_mdp_send(mdp_sockfd, &a, MDP_AWAITREPLY, 5000); + if (result) { + if (a.packetTypeAndFlags==MDP_ERROR){ + WHYF(" MDP Server error #%d: '%s'", + a.error.error,a.error.message); + } else + WHYF("Could not get list of local MDP addresses"); + overlay_mdp_client_close(mdp_sockfd); + return WHY("Failed to get local address list"); + } + if ((a.packetTypeAndFlags&MDP_TYPE_MASK)!=MDP_ADDRLIST) { + overlay_mdp_client_close(mdp_sockfd); + return WHY("MDP Server returned something other than an address list"); + } + unsigned i; + for(i=0;ireachable==REACHABLE_NONE) + continue; + + cli_put_string(context, alloca_tohex_sid_t(p->sid), ":"); + char flags[32]; + strbuf b = strbuf_local(flags, sizeof flags); + + switch (p->reachable){ + case REACHABLE_SELF: + strbuf_puts(b, "SELF"); + break; + case REACHABLE_BROADCAST: + strbuf_puts(b, "BROADCAST"); + break; + case REACHABLE_UNICAST: + strbuf_puts(b, "UNICAST"); + break; + case REACHABLE_INDIRECT: + strbuf_puts(b, "INDIRECT"); + break; + default: + strbuf_sprintf(b, "%d", p->reachable); + } + cli_put_string(context, strbuf_str(b), ":"); + cli_put_string(context, p->interface_name, ":"); + cli_put_string(context, alloca_tohex_sid_t(p->neighbour), "\n"); + rowcount++; + } + } + overlay_mdp_client_close(mdp_sockfd); + cli_row_count(context, rowcount); + return 0; +} + +DEFINE_CMD(app_network_scan, 0, + "Scan the network for serval peers. If no argument is supplied, all local addresses will be scanned.", + "scan","[
]"); +static int app_network_scan(const struct cli_parsed *parsed, struct cli_context *context) +{ + int mdp_sockfd; + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + overlay_mdp_frame mdp; + bzero(&mdp,sizeof(mdp)); + + mdp.packetTypeAndFlags=MDP_SCAN; + + struct overlay_mdp_scan *scan = (struct overlay_mdp_scan *)&mdp.raw; + const char *address; + if (cli_arg(parsed, "address", &address, NULL, NULL) == -1) + return -1; + + if (address){ + if (!inet_aton(address, &scan->addr)) + return WHY("Unable to parse the address"); + }else + INFO("Scanning local networks"); + + if ((mdp_sockfd = overlay_mdp_client_socket()) < 0) + return WHY("Cannot create MDP socket"); + overlay_mdp_send(mdp_sockfd, &mdp, MDP_AWAITREPLY, 5000); + overlay_mdp_client_close(mdp_sockfd); + + if (mdp.packetTypeAndFlags!=MDP_ERROR) + return -1; + cli_put_string(context, mdp.error.message, "\n"); + return mdp.error.error; +} diff --git a/nonce.c b/nonce.c deleted file mode 100644 index 358f499c..00000000 --- a/nonce.c +++ /dev/null @@ -1,76 +0,0 @@ -/* -Copyright (C) 2013 Paul Gardner-Stephen -Copyright (C) 2013 Serval Project Inc. - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -*/ - -#include "cli.h" -#include "constants.h" -#include "os.h" -#include "commandline.h" - -int nonce_initialised=0; -unsigned char nonce_buffer[128]; - -int generate_nonce(unsigned char *nonce,int bytes) -{ - if (bytes<1||bytes>128) return -1; - start: - if (!nonce_initialised) { - if (urandombytes(nonce_buffer,128)) - return -1; - nonce_initialised=1; - } - - // Increment nonce - int i; - for(i=0;i<128;i++) - { - unsigned char b=nonce_buffer[i]+1; - nonce_buffer[i]=b; - if (b) break; - } - if (i>=128) { - nonce_initialised=0; - goto start; - } - - bcopy(nonce_buffer,nonce,bytes); - return 0; -} - -DEFINE_CMD(app_nonce_test, 0, - "Run nonce generation test", - "test","nonce"); -static int app_nonce_test(const struct cli_parsed *UNUSED(parsed), struct cli_context *context) -{ - int i,j; - unsigned char nonces[0x10001][32]; - for(i=0;i<0x10001;i++) - { - if (generate_nonce(&nonces[i][0],32)) - return WHYF("Failed to generate nonce #%d\n",i); - for(j=0;j diff --git a/overlay_mdp.c b/overlay_mdp.c index d3daebc3..c12a3b02 100644 --- a/overlay_mdp.c +++ b/overlay_mdp.c @@ -749,6 +749,30 @@ void overlay_mdp_encode_ports(struct overlay_buffer *plaintext, mdp_port_t dst_p ob_append_packed_ui32(plaintext, src_port); } +static int nonce_initialised=0; +static uint8_t nonce_buffer[128]; + +static int generate_nonce(uint8_t *nonce, size_t bytes) +{ + if (bytes<1||bytes>128) return -1; + if (!nonce_initialised) { + if (urandombytes(nonce_buffer,128)) + return -1; + nonce_initialised=1; + } + + // Increment nonce + unsigned i; + for(i=0;i<128;i++){ + uint8_t b=nonce_buffer[i]+1; + nonce_buffer[i]=b; + if (b) break; + } + + bcopy(nonce_buffer,nonce,bytes); + return 0; +} + static struct overlay_buffer * encrypt_payload( struct subscriber *source, struct subscriber *dest, diff --git a/overlay_packetradio.c b/overlay_packetradio.c index 44c6ae36..9a72ce83 100644 --- a/overlay_packetradio.c +++ b/overlay_packetradio.c @@ -22,6 +22,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "serval.h" #include "conf.h" #include "overlay_interface.h" +#include "server.h" int overlay_packetradio_setup_port(overlay_interface *interface) { diff --git a/rhizome_cli.c b/rhizome_cli.c new file mode 100644 index 00000000..95547481 --- /dev/null +++ b/rhizome_cli.c @@ -0,0 +1,743 @@ +/* + Serval Rhizome command line functions + Copyright (C) 2014 Serval Project Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cli.h" +#include "conf.h" +#include "keyring.h" +#include "commandline.h" +#include "rhizome.h" +#include "instance.h" + +static void cli_put_manifest(struct cli_context *context, const rhizome_manifest *m) +{ + assert(m->filesize != RHIZOME_SIZE_UNSET); + cli_field_name(context, "manifestid", ":"); // TODO rename to "bundleid" or "bid" + cli_put_string(context, alloca_tohex_rhizome_bid_t(m->cryptoSignPublic), "\n"); + cli_field_name(context, "version", ":"); + cli_put_long(context, m->version, "\n"); + cli_field_name(context, "filesize", ":"); + cli_put_long(context, m->filesize, "\n"); + if (m->filesize != 0) { + cli_field_name(context, "filehash", ":"); + cli_put_string(context, alloca_tohex_rhizome_filehash_t(m->filehash), "\n"); + } + if (m->has_bundle_key) { + cli_field_name(context, "BK", ":"); + cli_put_string(context, alloca_tohex_rhizome_bk_t(m->bundle_key), "\n"); + } + if (m->has_date) { + cli_field_name(context, "date", ":"); + cli_put_long(context, m->date, "\n"); + } + switch (m->payloadEncryption) { + case PAYLOAD_CRYPT_UNKNOWN: + break; + case PAYLOAD_CLEAR: + cli_field_name(context, "crypt", ":"); + cli_put_long(context, 0, "\n"); + break; + case PAYLOAD_ENCRYPTED: + cli_field_name(context, "crypt", ":"); + cli_put_long(context, 1, "\n"); + break; + } + if (m->service) { + cli_field_name(context, "service", ":"); + cli_put_string(context, m->service, "\n"); + } + if (m->name) { + cli_field_name(context, "name", ":"); + cli_put_string(context, m->name, "\n"); + } + cli_field_name(context, ".readonly", ":"); + cli_put_long(context, m->haveSecret ? 0 : 1, "\n"); + if (m->haveSecret) { + char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1]; + rhizome_bytes_to_hex_upper(m->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES); + cli_field_name(context, ".secret", ":"); + cli_put_string(context, secret, "\n"); + } + if (m->authorship == AUTHOR_AUTHENTIC) { + cli_field_name(context, ".author", ":"); + cli_put_string(context, alloca_tohex_sid_t(m->author), "\n"); + } + cli_field_name(context, ".rowid", ":"); + cli_put_long(context, m->rowid, "\n"); + cli_field_name(context, ".inserttime", ":"); + cli_put_long(context, m->inserttime, "\n"); +} + +DEFINE_CMD(app_rhizome_hash_file, 0, + "Compute the Rhizome hash of a file", + "rhizome","hash","file",""); +static int app_rhizome_hash_file(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + /* compute hash of file. We do this without a manifest, so it will necessarily + return the hash of the file unencrypted. */ + const char *filepath; + cli_arg(parsed, "filepath", &filepath, NULL, ""); + rhizome_filehash_t hash; + uint64_t size; + if (rhizome_hash_file(NULL, filepath, &hash, &size) == -1) + return -1; + cli_put_string(context, size ? alloca_tohex_rhizome_filehash_t(hash) : "", "\n"); + return 0; +} + +DEFINE_CMD(app_rhizome_add_file, 0, + "Add a file to Rhizome and optionally write its manifest to the given path", + "rhizome","add","file" KEYRING_PIN_OPTIONS,"[--force-new]","","","[]","[]"); +DEFINE_CMD(app_rhizome_add_file, 0, + "Append content to a journal bundle", + "rhizome", "journal", "append" KEYRING_PIN_OPTIONS, "", "", "", "[]"); +static int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *filepath, *manifestpath, *manifestid, *authorSidHex, *bskhex; + + int force_new = 0 == cli_arg(parsed, "--force-new", NULL, NULL, NULL); + cli_arg(parsed, "filepath", &filepath, NULL, ""); + if (cli_arg(parsed, "author_sid", &authorSidHex, cli_optional_sid, "") == -1) + return -1; + cli_arg(parsed, "manifestpath", &manifestpath, NULL, ""); + cli_arg(parsed, "manifestid", &manifestid, NULL, ""); + if (cli_arg(parsed, "bsk", &bskhex, cli_optional_bundle_key, NULL) == -1) + return -1; + + sid_t authorSid; + if (authorSidHex[0] && str_to_sid_t(&authorSid, authorSidHex) == -1) + return WHYF("invalid author_sid: %s", authorSidHex); + rhizome_bk_t bsk; + + // treat empty string the same as null + if (bskhex && !*bskhex) + bskhex=NULL; + + if (bskhex && str_to_rhizome_bk_t(&bsk, bskhex) == -1) + return WHYF("invalid bsk: \"%s\"", bskhex); + + int journal = strcasecmp(parsed->args[1], "journal")==0; + + if (create_serval_instance_dir() == -1) + return -1; + + if (!(keyring = keyring_open_instance_cli(parsed))) + return -1; + + if (rhizome_opendb() == -1){ + keyring_free(keyring); + keyring = NULL; + return -1; + } + + /* Create a new manifest that will represent the file. If a manifest file was supplied, then read + * it, otherwise create a blank manifest. */ + rhizome_manifest *m = rhizome_new_manifest(); + if (!m){ + keyring_free(keyring); + keyring = NULL; + return WHY("Manifest struct could not be allocated -- not added to rhizome"); + } + if (manifestpath && *manifestpath && access(manifestpath, R_OK) == 0) { + if (config.debug.rhizome) + DEBUGF("reading manifest from %s", manifestpath); + /* Don't verify the manifest, because it will fail if it is incomplete. + This is okay, because we fill in any missing bits and sanity check before + trying to write it out. However, we do insist that whatever we load is + parsed okay and not malformed. */ + if (rhizome_read_manifest_from_file(m, manifestpath) || m->malformed) { + rhizome_manifest_free(m); + keyring_free(keyring); + keyring = NULL; + return WHY("Manifest file could not be loaded -- not added to rhizome"); + } + } else if (manifestid && *manifestid) { + if (config.debug.rhizome) + DEBUGF("Reading manifest from database"); + rhizome_bid_t bid; + if (str_to_rhizome_bid_t(&bid, manifestid) == -1) { + rhizome_manifest_free(m); + keyring_free(keyring); + keyring = NULL; + return WHYF("Invalid bundle ID: %s", alloca_str_toprint(manifestid)); + } + if (rhizome_retrieve_manifest(&bid, m)){ + rhizome_manifest_free(m); + keyring_free(keyring); + keyring = NULL; + return WHY("Existing manifest could not be loaded -- not added to rhizome"); + } + } else { + if (config.debug.rhizome) + DEBUGF("Creating new manifest"); + if (journal) { + rhizome_manifest_set_filesize(m, 0); + rhizome_manifest_set_tail(m, 0); + } + } + + if (journal && !m->is_journal){ + rhizome_manifest_free(m); + keyring_free(keyring); + keyring = NULL; + return WHY("Existing manifest is not a journal"); + } + if (!journal && m->is_journal) { + rhizome_manifest_free(m); + keyring_free(keyring); + keyring = NULL; + return WHY("Existing manifest is a journal"); + } + + if (bskhex) + rhizome_apply_bundle_secret(m, &bsk); + if (m->service == NULL) + rhizome_manifest_set_service(m, RHIZOME_SERVICE_FILE); + if (rhizome_fill_manifest(m, filepath, *authorSidHex ? &authorSid : NULL)) { + rhizome_manifest_free(m); + keyring_free(keyring); + keyring = NULL; + return -1; + } + + enum rhizome_bundle_status status = RHIZOME_BUNDLE_STATUS_NEW; + enum rhizome_payload_status pstatus; + if (journal){ + pstatus = rhizome_append_journal_file(m, 0, filepath); + } else { + pstatus = rhizome_stat_payload_file(m, filepath); + assert(m->filesize != RHIZOME_SIZE_UNSET); + if (pstatus == RHIZOME_PAYLOAD_STATUS_NEW) { + assert(m->filesize > 0); + pstatus = rhizome_store_payload_file(m, filepath); + } + } + switch (pstatus) { + case RHIZOME_PAYLOAD_STATUS_EMPTY: + case RHIZOME_PAYLOAD_STATUS_STORED: + case RHIZOME_PAYLOAD_STATUS_NEW: + break; + case RHIZOME_PAYLOAD_STATUS_TOO_BIG: + case RHIZOME_PAYLOAD_STATUS_EVICTED: + status = RHIZOME_BUNDLE_STATUS_NO_ROOM; + INFO("Insufficient space to store payload"); + break; + case RHIZOME_PAYLOAD_STATUS_ERROR: + status = RHIZOME_BUNDLE_STATUS_ERROR; + break; + case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: + case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: + status = RHIZOME_BUNDLE_STATUS_INCONSISTENT; + break; + case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: + status = RHIZOME_BUNDLE_STATUS_READONLY; + break; + default: + FATALF("pstatus = %d", pstatus); + } + rhizome_manifest *mout = NULL; + if (status == RHIZOME_BUNDLE_STATUS_NEW) { + if (!rhizome_manifest_validate(m) || m->malformed) + status = RHIZOME_BUNDLE_STATUS_INVALID; + else { + status = rhizome_manifest_finalise(m, &mout, !force_new); + if (mout && mout != m && !rhizome_manifest_validate(mout)) { + WHYF("Stored manifest id=%s is invalid -- overwriting", alloca_tohex_rhizome_bid_t(mout->cryptoSignPublic)); + status = RHIZOME_BUNDLE_STATUS_NEW; + } + } + } + int status_valid = 0; + switch (status) { + case RHIZOME_BUNDLE_STATUS_NEW: + if (mout && mout != m) + rhizome_manifest_free(mout); + mout = m; + // fall through + case RHIZOME_BUNDLE_STATUS_SAME: + case RHIZOME_BUNDLE_STATUS_DUPLICATE: + case RHIZOME_BUNDLE_STATUS_OLD: + assert(mout != NULL); + cli_put_manifest(context, mout); + if ( manifestpath && *manifestpath + && rhizome_write_manifest_file(mout, manifestpath, 0) == -1 + ) + WHYF("Could not write manifest to %s", alloca_str_toprint(manifestpath)); + status_valid = 1; + break; + case RHIZOME_BUNDLE_STATUS_READONLY: + case RHIZOME_BUNDLE_STATUS_INCONSISTENT: + case RHIZOME_BUNDLE_STATUS_ERROR: + case RHIZOME_BUNDLE_STATUS_INVALID: + case RHIZOME_BUNDLE_STATUS_FAKE: + case RHIZOME_BUNDLE_STATUS_NO_ROOM: + status_valid = 1; + break; + // Do not use a default: label! With no default, if a new value is added to the enum, then the + // compiler will issue a warning on switch statements that do not cover all the values, which is + // a valuable tool for the developer. + } + if (!status_valid) + FATALF("status=%d", status); + if (mout && mout != m) + rhizome_manifest_free(mout); + rhizome_manifest_free(m); + keyring_free(keyring); + keyring = NULL; + return status; +} + +DEFINE_CMD(app_rhizome_import_bundle, 0, + "Import a payload/manifest pair into Rhizome", + "rhizome","import","bundle","",""); +static int app_rhizome_import_bundle(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *filepath, *manifestpath; + cli_arg(parsed, "filepath", &filepath, NULL, ""); + cli_arg(parsed, "manifestpath", &manifestpath, NULL, ""); + if (rhizome_opendb() == -1) + return -1; + rhizome_manifest *m = rhizome_new_manifest(); + if (!m) + return WHY("Out of manifests."); + rhizome_manifest *m_out = NULL; + enum rhizome_bundle_status status = rhizome_bundle_import_files(m, &m_out, manifestpath, filepath); + switch (status) { + case RHIZOME_BUNDLE_STATUS_NEW: + case RHIZOME_BUNDLE_STATUS_SAME: + case RHIZOME_BUNDLE_STATUS_DUPLICATE: + case RHIZOME_BUNDLE_STATUS_OLD: + cli_put_manifest(context, m_out); + break; + case RHIZOME_BUNDLE_STATUS_ERROR: + case RHIZOME_BUNDLE_STATUS_INVALID: + case RHIZOME_BUNDLE_STATUS_INCONSISTENT: + break; + default: + FATALF("rhizome_bundle_import_files() returned %d", status); + } + if (m_out && m_out != m) + rhizome_manifest_free(m_out); + rhizome_manifest_free(m); + return status; +} + +DEFINE_CMD(app_rhizome_append_manifest, 0, + "Append a manifest to the end of the file it belongs to.", + "rhizome", "append", "manifest", "", ""); +static int app_rhizome_append_manifest(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *manifestpath, *filepath; + if ( cli_arg(parsed, "manifestpath", &manifestpath, NULL, "") == -1 + || cli_arg(parsed, "filepath", &filepath, NULL, "") == -1) + return -1; + rhizome_manifest *m = rhizome_new_manifest(); + if (!m) + return WHY("Out of manifests."); + int ret = -1; + if ( rhizome_read_manifest_from_file(m, manifestpath) != -1 + && rhizome_manifest_validate(m) + && rhizome_manifest_verify(m) + ) { + if (rhizome_write_manifest_file(m, filepath, 1) != -1) + ret = 0; + } + rhizome_manifest_free(m); + return ret; +} + +DEFINE_CMD(app_rhizome_delete, 0, + "Remove the manifest, or payload, or both for the given Bundle ID from the Rhizome store", + "rhizome","delete","manifest|payload|bundle",""); +DEFINE_CMD(app_rhizome_delete, 0, + "Remove the file with the given hash from the Rhizome store", + "rhizome","delete","|file",""); +static int app_rhizome_delete(const struct cli_parsed *parsed, struct cli_context *UNUSED(context)) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *manifestid, *fileid; + if (cli_arg(parsed, "manifestid", &manifestid, cli_manifestid, NULL) == -1) + return -1; + if (cli_arg(parsed, "fileid", &fileid, cli_fileid, NULL) == -1) + return -1; + /* Ensure the Rhizome database exists and is open */ + if (create_serval_instance_dir() == -1) + return -1; + if (rhizome_opendb() == -1) + return -1; + if (!(keyring = keyring_open_instance_cli(parsed))) + return -1; + int ret=0; + if (cli_arg(parsed, "file", NULL, NULL, NULL) == 0) { + if (!fileid){ + keyring_free(keyring); + keyring = NULL; + return WHY("missing argument"); + } + rhizome_filehash_t hash; + if (str_to_rhizome_filehash_t(&hash, fileid) == -1){ + keyring_free(keyring); + keyring = NULL; + return WHYF("invalid argument: %s", alloca_str_toprint(fileid)); + } + ret = rhizome_delete_file(&hash); + } else { + if (!manifestid){ + keyring_free(keyring); + keyring = NULL; + return WHY("missing argument"); + } + rhizome_bid_t bid; + if (str_to_rhizome_bid_t(&bid, manifestid) == -1){ + keyring_free(keyring); + keyring = NULL; + return WHY("Invalid manifest ID"); + } + if (cli_arg(parsed, "bundle", NULL, NULL, NULL) == 0) + ret = rhizome_delete_bundle(&bid); + else if (cli_arg(parsed, "manifest", NULL, NULL, NULL) == 0) + ret = rhizome_delete_manifest(&bid); + else if (cli_arg(parsed, "payload", NULL, NULL, NULL) == 0) + ret = rhizome_delete_payload(&bid); + else{ + keyring_free(keyring); + keyring = NULL; + return WHY("unrecognised command"); + } + } + keyring_free(keyring); + keyring = NULL; + return ret; +} + +DEFINE_CMD(app_rhizome_clean, 0, + "Remove stale and orphaned content from the Rhizome store", + "rhizome","clean","[verify]"); +static int app_rhizome_clean(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + int verify = cli_arg(parsed, "verify", NULL, NULL, NULL) == 0; + + /* Ensure the Rhizome database exists and is open */ + if (create_serval_instance_dir() == -1) + return -1; + if (rhizome_opendb() == -1) + return -1; + + if (verify) + verify_bundles(); + struct rhizome_cleanup_report report; + if (rhizome_cleanup(&report) == -1) + return -1; + cli_field_name(context, "deleted_stale_incoming_files", ":"); + cli_put_long(context, report.deleted_stale_incoming_files, "\n"); + cli_field_name(context, "deleted_orphan_files", ":"); + cli_put_long(context, report.deleted_orphan_files, "\n"); + cli_field_name(context, "deleted_orphan_fileblobs", ":"); + cli_put_long(context, report.deleted_orphan_fileblobs, "\n"); + cli_field_name(context, "deleted_orphan_manifests", ":"); + cli_put_long(context, report.deleted_orphan_manifests, "\n"); + return 0; +} + +DEFINE_CMD(app_rhizome_extract, 0, + "Export a manifest and payload file to the given paths, without decrypting.", + "rhizome","export","bundle" KEYRING_PIN_OPTIONS, + "","[]","[]"); +DEFINE_CMD(app_rhizome_extract, 0, + "Export a manifest from Rhizome and write it to the given path", + "rhizome","export","manifest" KEYRING_PIN_OPTIONS, + "","[]"); +DEFINE_CMD(app_rhizome_extract, 0, + "Extract and decrypt a manifest and file to the given paths.", + "rhizome","extract","bundle" KEYRING_PIN_OPTIONS, + "","[]","[]","[]"); +DEFINE_CMD(app_rhizome_extract, 0, + "Extract and decrypt a file from Rhizome and write it to the given path", + "rhizome","extract","file" KEYRING_PIN_OPTIONS, + "","[]","[]"); +static int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *manifestpath, *filepath, *manifestid, *bskhex; + if ( cli_arg(parsed, "manifestid", &manifestid, cli_manifestid, "") == -1 + || cli_arg(parsed, "manifestpath", &manifestpath, NULL, "") == -1 + || cli_arg(parsed, "filepath", &filepath, NULL, "") == -1 + || cli_arg(parsed, "bsk", &bskhex, cli_optional_bundle_key, NULL) == -1) + return -1; + + int extract = strcasecmp(parsed->args[1], "extract")==0; + + /* Ensure the Rhizome database exists and is open */ + if (create_serval_instance_dir() == -1) + return -1; + if (rhizome_opendb() == -1) + return -1; + + if (!(keyring = keyring_open_instance_cli(parsed))) + return -1; + + int ret=0; + + rhizome_bid_t bid; + if (str_to_rhizome_bid_t(&bid, manifestid) == -1){ + keyring_free(keyring); + keyring = NULL; + return WHY("Invalid manifest ID"); + } + + // treat empty string the same as null + if (bskhex && !*bskhex) + bskhex=NULL; + + rhizome_bk_t bsk; + if (bskhex && str_to_rhizome_bk_t(&bsk, bskhex) == -1){ + keyring_free(keyring); + keyring = NULL; + return WHYF("invalid bsk: \"%s\"", bskhex); + } + + rhizome_manifest *m = rhizome_new_manifest(); + if (m==NULL){ + keyring_free(keyring); + keyring = NULL; + return WHY("Out of manifests"); + } + ret = rhizome_retrieve_manifest(&bid, m); + if (ret==0){ + assert(m->finalised); + if (bskhex) + rhizome_apply_bundle_secret(m, &bsk); + rhizome_authenticate_author(m); + assert(m->authorship != AUTHOR_LOCAL); + cli_put_manifest(context, m); + } + + enum rhizome_payload_status pstatus = RHIZOME_PAYLOAD_STATUS_EMPTY; + if (ret==0 && m->filesize != 0 && filepath && *filepath){ + if (extract){ + // Save the file, implicitly decrypting if required. + pstatus = rhizome_extract_file(m, filepath); + if (pstatus != RHIZOME_PAYLOAD_STATUS_EMPTY && pstatus != RHIZOME_PAYLOAD_STATUS_STORED) + WHYF("rhizome_extract_file() returned %d", pstatus); + }else{ + // Save the file without attempting to decrypt + uint64_t length; + pstatus = rhizome_dump_file(&m->filehash, filepath, &length); + if (pstatus != RHIZOME_PAYLOAD_STATUS_EMPTY && pstatus != RHIZOME_PAYLOAD_STATUS_STORED) + WHYF("rhizome_dump_file() returned %d", pstatus); + } + } + + if (ret==0 && manifestpath && *manifestpath){ + if (strcmp(manifestpath, "-") == 0) { + // always extract a manifest to stdout, even if writing the file itself failed. + cli_field_name(context, "manifest", ":"); + cli_write(context, m->manifestdata, m->manifest_all_bytes); + cli_delim(context, "\n"); + } else { + int append = (strcmp(manifestpath, filepath)==0)?1:0; + // don't write out the manifest if we were asked to append it and writing the file failed. + if (!append || (pstatus == RHIZOME_PAYLOAD_STATUS_EMPTY || pstatus == RHIZOME_PAYLOAD_STATUS_STORED)) { + if (rhizome_write_manifest_file(m, manifestpath, append) == -1) + ret = -1; + } + } + } + switch (pstatus) { + case RHIZOME_PAYLOAD_STATUS_EMPTY: + case RHIZOME_PAYLOAD_STATUS_STORED: + break; + case RHIZOME_PAYLOAD_STATUS_NEW: + ret = 1; // payload not found + break; + case RHIZOME_PAYLOAD_STATUS_ERROR: + case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: + case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: + case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: + ret = -1; + break; + default: + FATALF("pstatus = %d", pstatus); + } + if (m) + rhizome_manifest_free(m); + keyring_free(keyring); + keyring = NULL; + return ret; +} + +DEFINE_CMD(app_rhizome_export_file, 0, + "Export a file from Rhizome and write it to the given path without attempting decryption", + "rhizome","export","file","","[]"); +static int app_rhizome_export_file(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *fileid, *filepath; + if ( cli_arg(parsed, "filepath", &filepath, NULL, "") == -1 + || cli_arg(parsed, "fileid", &fileid, cli_fileid, NULL) == -1) + return -1; + rhizome_filehash_t hash; + if (str_to_rhizome_filehash_t(&hash, fileid) == -1) + return WHYF("invalid argument: %s", alloca_str_toprint(fileid)); + if (create_serval_instance_dir() == -1) + return -1; + if (rhizome_opendb() == -1) + return -1; + if (!rhizome_exists(&hash)) + return 1; + uint64_t length; + enum rhizome_payload_status pstatus = rhizome_dump_file(&hash, filepath, &length); + switch (pstatus) { + case RHIZOME_PAYLOAD_STATUS_EMPTY: + case RHIZOME_PAYLOAD_STATUS_STORED: + break; + case RHIZOME_PAYLOAD_STATUS_NEW: + return 1; // payload not found + case RHIZOME_PAYLOAD_STATUS_ERROR: + case RHIZOME_PAYLOAD_STATUS_WRONG_SIZE: + case RHIZOME_PAYLOAD_STATUS_WRONG_HASH: + case RHIZOME_PAYLOAD_STATUS_CRYPTO_FAIL: + return -1; + default: + FATALF("pstatus = %d", pstatus); + } + cli_field_name(context, "filehash", ":"); + cli_put_string(context, alloca_tohex_rhizome_filehash_t(hash), "\n"); + cli_field_name(context, "filesize", ":"); + cli_put_long(context, length, "\n"); + return 0; +} + +DEFINE_CMD(app_rhizome_list, 0, + "List all manifests and files in Rhizome", + "rhizome","list" KEYRING_PIN_OPTIONS, + "[]","[]","[]","[]","[]","[]"); +static int app_rhizome_list(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + const char *service = NULL, *name = NULL, *sender_hex = NULL, *recipient_hex = NULL, *offset_ascii = NULL, *limit_ascii = NULL; + cli_arg(parsed, "service", &service, NULL, ""); + cli_arg(parsed, "name", &name, NULL, ""); + cli_arg(parsed, "sender_sid", &sender_hex, cli_optional_sid, ""); + cli_arg(parsed, "recipient_sid", &recipient_hex, cli_optional_sid, ""); + cli_arg(parsed, "offset", &offset_ascii, cli_uint, "0"); + cli_arg(parsed, "limit", &limit_ascii, cli_uint, "0"); + /* Create the instance directory if it does not yet exist */ + if (create_serval_instance_dir() == -1) + return -1; + if (!(keyring = keyring_open_instance_cli(parsed))) + return -1; + if (rhizome_opendb() == -1) { + keyring_free(keyring); + keyring = NULL; + return -1; + } + size_t rowlimit = atoi(limit_ascii); + size_t rowoffset = atoi(offset_ascii); + struct rhizome_list_cursor cursor; + bzero(&cursor, sizeof cursor); + cursor.service = service && service[0] ? service : NULL; + cursor.name = name && name[0] ? name : NULL; + if (sender_hex && sender_hex[0]) { + if (str_to_sid_t(&cursor.sender, sender_hex) == -1) + return WHYF("Invalid : %s", sender_hex); + cursor.is_sender_set = 1; + } + if (recipient_hex && recipient_hex[0]) { + if (str_to_sid_t(&cursor.recipient, recipient_hex) == -1) + return WHYF("Invalid filesize != RHIZOME_SIZE_UNSET); + rhizome_lookup_author(m); + cli_put_long(context, m->rowid, ":"); + cli_put_string(context, m->service, ":"); + cli_put_hexvalue(context, m->cryptoSignPublic.binary, sizeof m->cryptoSignPublic.binary, ":"); + cli_put_long(context, m->version, ":"); + cli_put_long(context, m->has_date ? m->date : 0, ":"); + cli_put_long(context, m->inserttime, ":"); + switch (m->authorship) { + case AUTHOR_LOCAL: + case AUTHOR_AUTHENTIC: + cli_put_hexvalue(context, m->author.binary, sizeof m->author.binary, ":"); + cli_put_long(context, 1, ":"); + break; + default: + cli_put_string(context, NULL, ":"); + cli_put_long(context, 0, ":"); + break; + } + cli_put_long(context, m->filesize, ":"); + cli_put_hexvalue(context, m->filesize ? m->filehash.binary : NULL, sizeof m->filehash.binary, ":"); + cli_put_hexvalue(context, m->has_sender ? m->sender.binary : NULL, sizeof m->sender.binary, ":"); + cli_put_hexvalue(context, m->has_recipient ? m->recipient.binary : NULL, sizeof m->recipient.binary, ":"); + cli_put_string(context, m->name, "\n"); + } + } + rhizome_list_release(&cursor); + keyring_free(keyring); + keyring = NULL; + if (n == -1) + return -1; + cli_row_count(context, rowcount); + return 0; +} + diff --git a/serval.h b/serval.h index 148d975f..1bd3bfc2 100644 --- a/serval.h +++ b/serval.h @@ -173,15 +173,6 @@ struct overlay_buffer; struct overlay_frame; struct broadcast; -extern int serverMode; - -int server_pid(); -const char *_server_pidfile_path(struct __sourceloc); -#define server_pidfile_path() (_server_pidfile_path(__WHENCE__)) -void server_save_argv(int argc, const char *const *argv); -int server(void); -int server_write_proc_state(const char *path, const char *fmt, ...); -int server_get_proc_state(const char *path, char *buff, size_t buff_len); void overlay_mdp_clean_socket_files(); int overlay_forward_payload(struct overlay_frame *f); @@ -326,6 +317,4 @@ int link_stop_routing(struct subscriber *subscriber); int link_has_neighbours(); int link_interface_has_neighbours(struct overlay_interface *interface); -int generate_nonce(unsigned char *nonce,int bytes); - #endif // __SERVAL_DNA__SERVAL_H diff --git a/server.c b/server.c index 1fc16c2c..102212fe 100644 --- a/server.c +++ b/server.c @@ -26,6 +26,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include #include #include +#include #include "serval.h" #include "conf.h" @@ -35,6 +36,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "overlay_packet.h" #include "server.h" #include "keyring.h" +#include "commandline.h" #define PROC_SUBDIR "proc" #define PIDFILE_NAME "servald.pid" @@ -49,6 +51,9 @@ static int server_write_pid(); static int server_unlink_pid(); static void signal_handler(int signal); static void serverCleanUp(); +static const char *_server_pidfile_path(struct __sourceloc __whence); +#define server_pidfile_path() (_server_pidfile_path(__WHENCE__)) +void server_shutdown_check(struct sched_ent *alarm); /** Return the PID of the currently running server process, return 0 if there is none. */ @@ -83,7 +88,8 @@ int server_pid() return 0; } -const char *_server_pidfile_path(struct __sourceloc __whence) +#define server_pidfile_path() (_server_pidfile_path(__WHENCE__)) +static const char *_server_pidfile_path(struct __sourceloc __whence) { if (!pidfile_path[0]) { if (!FORMF_SERVAL_RUN_PATH(pidfile_path, PIDFILE_NAME)) @@ -92,7 +98,7 @@ const char *_server_pidfile_path(struct __sourceloc __whence) return pidfile_path; } -int server() +static int server() { IN(); serverMode = SERVER_RUNNING; @@ -458,7 +464,7 @@ static void serverCleanUp() clean_proc(); } -void signal_handler(int signal) +static void signal_handler(int signal) { switch (signal) { case SIGHUP: @@ -480,3 +486,268 @@ void signal_handler(int signal) serverCleanUp(); exit(0); } + +DEFINE_CMD(app_server_start, 0, + "Start daemon with instance path from SERVALINSTANCE_PATH environment variable.", + "start" KEYRING_PIN_OPTIONS, "[foreground|exec ]"); +static int app_server_start(const struct cli_parsed *parsed, struct cli_context *context) +{ + IN(); + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + /* Process optional arguments */ + int pid=-1; + int cpid=-1; + const char *execpath; + if (cli_arg(parsed, "exec", &execpath, cli_absolute_path, NULL) == -1) + RETURN(-1); + int foregroundP = cli_arg(parsed, "foreground", NULL, NULL, NULL) == 0; +#ifdef HAVE_JNI_H + if (context && context->jni_env && execpath == NULL) + RETURN(WHY("Must supply \"exec \" arguments when invoked via JNI")); +#endif + /* Create the instance directory if it does not yet exist */ + if (create_serval_instance_dir() == -1) + RETURN(-1); + /* Now that we know our instance path, we can ask for the default set of + network interfaces that we will take interest in. */ + if (config.interfaces.ac == 0) + NOWHENCE(WARN("No network interfaces configured (empty 'interfaces' config option)")); + if (pid == -1) + pid = server_pid(); + if (pid < 0) + RETURN(-1); + int ret = -1; + // If the pidfile identifies this process, it probably means we are re-spawning after a SEGV, so + // go ahead and do the fork/exec. + if (pid > 0 && pid != getpid()) { + WARNF("Server already running (pid=%d)", pid); + ret = 10; + } else { + if (foregroundP) + INFOF("Foreground server process %s", execpath ? execpath : "without exec"); + else + INFOF("Starting background server %s", execpath ? execpath : "without exec"); + /* Start the Serval process. All server settings will be read by the server process from the + instance directory when it starts up. */ + // Open the keyring and ensure it contains at least one unlocked identity. + keyring = keyring_open_instance_cli(parsed); + if (!keyring) + RETURN(WHY("Could not open keyring file")); + if (keyring_seed(keyring) == -1) { + WHY("Could not seed keyring"); + goto exit; + } + if (foregroundP) { + ret = server(); + goto exit; + } + const char *dir = getenv("SERVALD_SERVER_CHDIR"); + if (!dir) + dir = config.server.chdir; + switch (cpid = fork()) { + case -1: + /* Main process. Fork failed. There is no child process. */ + WHY_perror("fork"); + goto exit; + case 0: { + /* Child process. Fork then exit, to disconnect daemon from parent process, so that + when daemon exits it does not live on as a zombie. N.B. On Android, do not return from + within this process; that will unroll the JNI call stack and cause havoc -- call _exit() + instead (not exit(), because we want to avoid any Java atexit(3) callbacks as well). If + _exit() is used on non-Android systems, then source code coverage does not get reported, + because it relies on an atexit() callback to write the accumulated counters into .gcda + files. */ +#ifdef ANDROID +# define EXIT_CHILD(n) _exit(n) +#else +# define EXIT_CHILD(n) exit(n) +#endif + // Ensure that all stdio streams are flushed before forking, so that if a child calls + // exit(), it will not result in any buffered output being written twice to the file + // descriptor. + fflush(stdout); + fflush(stderr); + switch (fork()) { + case -1: + EXIT_CHILD(WHY_perror("fork")); + case 0: { + /* Grandchild process. Close logfile (so that it gets re-opened again on demand, with + our own file pointer), disable logging to stderr (about to get redirected to + /dev/null), disconnect from current directory, disconnect standard I/O streams, and + start a new process session so that if we are being started by an adb shell session + on an Android device, then we don't receive a SIGHUP when the adb shell process ends. + */ + close_log_file(); + disable_log_stderr(); + int fd; + if ((fd = open("/dev/null", O_RDWR, 0)) == -1) + EXIT_CHILD(WHY_perror("open(\"/dev/null\")")); + if (setsid() == -1) + EXIT_CHILD(WHY_perror("setsid")); + if (chdir(dir) == -1) + EXIT_CHILD(WHYF_perror("chdir(%s)", alloca_str_toprint(dir))); + if (dup2(fd, 0) == -1) + EXIT_CHILD(WHYF_perror("dup2(%d,0)", fd)); + if (dup2(fd, 1) == -1) + EXIT_CHILD(WHYF_perror("dup2(%d,1)", fd)); + if (dup2(fd, 2) == -1) + EXIT_CHILD(WHYF_perror("dup2(%d,2)", fd)); + if (fd > 2) + (void)close(fd); + /* The execpath option is provided so that a JNI call to "start" can be made which + creates a new server daemon process with the correct argv[0]. Otherwise, the servald + process appears as a process with argv[0] = "org.servalproject". */ + if (execpath) { + /* Need the cast on Solaris because it defines NULL as 0L and gcc doesn't see it as a + sentinal. */ + execl(execpath, execpath, "start", "foreground", (void *)NULL); + WHYF_perror("execl(%s,\"start\",\"foreground\")", alloca_str_toprint(execpath)); + EXIT_CHILD(-1); + } + EXIT_CHILD(server()); + // NOT REACHED + } + } + // TODO wait for server_write_pid() to signal more directly? + EXIT_CHILD(0); // Main process is waitpid()-ing for this. +#undef EXIT_CHILD + } + } + /* Main process. Wait for the child process to fork the grandchild and exit. */ + waitpid(cpid, NULL, 0); + /* Allow a few seconds for the grandchild process to report for duty. */ + time_ms_t timeout = gettime_ms() + 5000; + do { + sleep_ms(200); // 5 Hz + } while ((pid = server_pid()) == 0 && gettime_ms() < timeout); + if (pid == -1) + goto exit; + if (pid == 0) { + WHY("Server process did not start"); + goto exit; + } + ret = 0; + } + const char *ipath = instance_path(); + if (ipath) { + cli_field_name(context, "instancepath", ":"); + cli_put_string(context, ipath, "\n"); + } + cli_field_name(context, "pidfile", ":"); + cli_put_string(context, server_pidfile_path(), "\n"); + cli_field_name(context, "pid", ":"); + cli_put_long(context, pid, "\n"); + char buff[256]; + if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){ + cli_field_name(context, "http_port", ":"); + cli_put_string(context, buff, "\n"); + } + if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){ + cli_field_name(context, "mdp_inet_port", ":"); + cli_put_string(context, buff, "\n"); + } + cli_flush(context); + /* Sleep before returning if env var is set. This is used in testing, to simulate the situation + on Android phones where the "start" command is invoked via the JNI interface and the calling + process does not die. + */ + const char *post_sleep = getenv("SERVALD_START_POST_SLEEP"); + if (post_sleep) { + time_ms_t milliseconds = atoi(post_sleep); + INFOF("Sleeping for %"PRId64" milliseconds", (int64_t) milliseconds); + sleep_ms(milliseconds); + } +exit: + keyring_free(keyring); + keyring = NULL; + RETURN(ret); + OUT(); +} + +DEFINE_CMD(app_server_stop,CLIFLAG_PERMISSIVE_CONFIG, + "Stop a running daemon with instance path from SERVALINSTANCE_PATH environment variable.", + "stop"); +static int app_server_stop(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + int pid, tries, running; + time_ms_t timeout; + const char *ipath = instance_path(); + if (ipath) { + cli_field_name(context, "instancepath", ":"); + cli_put_string(context, ipath, "\n"); + } + cli_field_name(context, "pidfile", ":"); + cli_put_string(context, server_pidfile_path(), "\n"); + pid = server_pid(); + /* Not running, nothing to stop */ + if (pid <= 0) + return 1; + INFOF("Stopping server (pid=%d)", pid); + /* Set the stop file and signal the process */ + cli_field_name(context, "pid", ":"); + cli_put_long(context, pid, "\n"); + tries = 0; + running = pid; + while (running == pid) { + if (tries >= 5) { + WHYF("Servald pid=%d (pidfile=%s) did not stop after %d SIGHUP signals", + pid, server_pidfile_path(), tries); + return 253; + } + ++tries; + if (kill(pid, SIGHUP) == -1) { + // ESRCH means process is gone, possibly we are racing with another stop, or servald just died + // voluntarily. We DO NOT call serverCleanUp() in this case (once used to!) because that + // would race with a starting server process. + if (errno == ESRCH) + break; + WHY_perror("kill"); + WHYF("Error sending SIGHUP to Servald pid=%d (pidfile %s)", pid, server_pidfile_path()); + return 252; + } + /* Allow a few seconds for the process to die. */ + timeout = gettime_ms() + 2000; + do + sleep_ms(200); // 5 Hz + while ((running = server_pid()) == pid && gettime_ms() < timeout); + } + cli_field_name(context, "tries", ":"); + cli_put_long(context, tries, "\n"); + return 0; +} + +DEFINE_CMD(app_server_status,CLIFLAG_PERMISSIVE_CONFIG, + "Display information about running daemon.", + "status"); +static int app_server_status(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + int pid = server_pid(); + const char *ipath = instance_path(); + if (ipath) { + cli_field_name(context, "instancepath", ":"); + cli_put_string(context, ipath, "\n"); + } + cli_field_name(context, "pidfile", ":"); + cli_put_string(context, server_pidfile_path(), "\n"); + cli_field_name(context, "status", ":"); + cli_put_string(context, pid > 0 ? "running" : "stopped", "\n"); + if (pid > 0) { + cli_field_name(context, "pid", ":"); + cli_put_long(context, pid, "\n"); + char buff[256]; + if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){ + cli_field_name(context, "http_port", ":"); + cli_put_string(context, buff, "\n"); + } + if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){ + cli_field_name(context, "mdp_inet_port", ":"); + cli_put_string(context, buff, "\n"); + } + } + return pid > 0 ? 0 : 1; +} diff --git a/server.h b/server.h index fdfa0cae..81b1e423 100644 --- a/server.h +++ b/server.h @@ -28,4 +28,10 @@ DECLARE_ALARM(server_config_reload); DECLARE_ALARM(rhizome_sync_announce); DECLARE_ALARM(fd_periodicstats); +extern int serverMode; + +int server_pid(); +int server_write_proc_state(const char *path, const char *fmt, ...); +int server_get_proc_state(const char *path, char *buff, size_t buff_len); + #endif // __SERVAL_DNA__SERVER_H diff --git a/sourcefiles.mk b/sourcefiles.mk index 3a15516f..2b74fdb3 100644 --- a/sourcefiles.mk +++ b/sourcefiles.mk @@ -45,6 +45,11 @@ SQLITE3_SOURCES = \ # The source files for building the Serval DNA daemon. SERVAL_DAEMON_SOURCES = \ commandline.c \ + conf_cli.c \ + rhizome_cli.c \ + keyring_cli.c \ + network_cli.c \ + test_cli.c \ crypto.c \ directory_client.c \ dna_helper.c \ @@ -63,7 +68,6 @@ SERVAL_DAEMON_SOURCES = \ monitor.c \ monitor-client.c \ monitor-cli.c \ - nonce.c \ overlay_address.c \ overlay_buffer.c \ overlay_interface.c \ diff --git a/test_cli.c b/test_cli.c new file mode 100644 index 00000000..2abb2cec --- /dev/null +++ b/test_cli.c @@ -0,0 +1,262 @@ +/* + Serval testing command line functions + Copyright (C) 2014 Serval Project Inc. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "serval_types.h" +#include "dataformats.h" +#include "os.h" +#include "cli.h" +#include "conf.h" +#include "commandline.h" + +DEFINE_CMD(app_byteorder_test, 0, + "Run byte order handling test", + "test","byteorder"); +static int app_byteorder_test(const struct cli_parsed *UNUSED(parsed), struct cli_context *UNUSED(context)) +{ + uint64_t in=0x1234; + uint64_t out; + + unsigned char bytes[8]; + + write_uint64(&bytes[0],in); + out=read_uint64(&bytes[0]); + if (in!=out) + cli_printf(context,"Byte order mangled (0x%016"PRIx64" should have been %016"PRIx64")\n", + out,in); + else cli_printf(context,"Byte order preserved.\n"); + return -1; +} + +DEFINE_CMD(app_crypt_test, 0, + "Run cryptography speed test", + "test","crypt"); +static int app_crypt_test(const struct cli_parsed *parsed, struct cli_context *context) +{ + if (config.debug.verbose) + DEBUG_cli_parsed(parsed); + unsigned char nonce[crypto_box_curve25519xsalsa20poly1305_NONCEBYTES]; + unsigned char k[crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES]; + + unsigned char plain_block[65536]; + + urandombytes(nonce,sizeof(nonce)); + urandombytes(k,sizeof(k)); + + int len,i; + + cli_printf(context, "Benchmarking CryptoBox Auth-Cryption:\n"); + int count=1024; + for(len=16;len<=16384;len*=2) { + time_ms_t start = gettime_ms(); + for (i=0;i1.00) count/=2; + } + + + cli_printf(context, "Benchmarking CryptoSign signature verification:\n"); + { + + unsigned char sign_pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES]; + unsigned char sign_sk[crypto_sign_edwards25519sha512batch_SECRETKEYBYTES]; + if (crypto_sign_edwards25519sha512batch_keypair(sign_pk,sign_sk)) + return WHY("crypto_sign_curve25519xsalsa20poly1305_keypair() failed.\n"); + + unsigned char plainTextIn[1024]; + unsigned char cipherText[1024]; + unsigned char plainTextOut[1024]; + unsigned long long cipherLen=0; + unsigned long long plainLenOut; + bzero(plainTextIn,1024); + bzero(cipherText,1024); + snprintf((char *)&plainTextIn[0],1024,"%s","No casaba melons allowed in the lab."); + int plainLenIn=64; + + time_ms_t start = gettime_ms(); + for(i=0;i<10;i++) { + int r=crypto_sign_edwards25519sha512batch(cipherText,&cipherLen, + plainTextIn,plainLenIn, + sign_sk); + if (r) + return WHY("crypto_sign_edwards25519sha512batch() failed.\n"); + } + + time_ms_t end=gettime_ms(); + cli_printf(context, "mean signature generation time = %.2fms\n", + (end-start)*1.0/i); + start = gettime_ms(); + + for(i=0;i<10;i++) { + bzero(&plainTextOut,1024); plainLenOut=0; + int r=crypto_sign_edwards25519sha512batch_open(plainTextOut,&plainLenOut, + &cipherText[0],cipherLen, + sign_pk); + if (r) + return WHYF("crypto_sign_edwards25519sha512batch_open() failed (r=%d, i=%d).\n", + r,i); + } + end = gettime_ms(); + cli_printf(context, "mean signature verification time = %.2fms\n", + (end-start)*1.0/i); + } + + /* We can't do public signing with a crypto_box key, but we should be able to + do shared-secret generation using crypto_sign keys. */ + { + cli_printf(context, "Testing supercop-20120525 Ed25519 CryptoSign implementation:\n"); + + unsigned char sign1_pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES]; + unsigned char sign1_sk[crypto_sign_edwards25519sha512batch_SECRETKEYBYTES]; + if (crypto_sign_edwards25519sha512batch_keypair(sign1_pk,sign1_sk)) + return WHY("crypto_sign_edwards25519sha512batch_keypair() failed.\n"); + + /* Try calculating public key from secret key */ + unsigned char pk[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES]; + + /* New Ed25519 implementation has public key as 2nd half of private key. */ + bcopy(&sign1_sk[32],pk,32); + + if (memcmp(pk, sign1_pk, crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES)) { + WHY("Could not calculate public key from private key.\n"); + dump("calculated",&pk,sizeof(pk)); + dump("original",&sign1_pk,sizeof(sign1_pk)); + } else + cli_printf(context, "Can calculate public key from private key.\n"); + + /* Now use a pre-tested keypair and make sure that we can sign and verify with + it, and that the signatures are as expected. */ + + unsigned char key[64]={ + 0xf6,0x70,0x6b,0x8a,0x4e,0x1e,0x4b,0x01, + 0x11,0x56,0x85,0xac,0x63,0x46,0x67,0x5f, + 0xc1,0x44,0xcf,0xdf,0x98,0x5c,0x2b,0x8b, + 0x18,0xff,0x70,0x9c,0x12,0x71,0x48,0xb9, + + 0x32,0x2a,0x88,0xba,0x9c,0xdd,0xed,0x35, + 0x8f,0x01,0x18,0xf7,0x60,0x1b,0xfb,0x80, + 0xaf,0xce,0x74,0xe0,0x85,0x39,0xac,0x13, + 0x15,0xf6,0x79,0xaa,0x68,0xef,0x5d,0xc6}; + + unsigned char plainTextIn[1024]; + unsigned char plainTextOut[1024]; + unsigned char cipherText[1024]; + unsigned long long cipherLen=0; + unsigned long long plainLenOut; + bzero(plainTextIn,1024); + bzero(cipherText,1024); + snprintf((char *)&plainTextIn[0],1024,"%s","No casaba melons allowed in the lab."); + int plainLenIn=64; + + int r=crypto_sign_edwards25519sha512batch(cipherText,&cipherLen, + plainTextIn,plainLenIn, + key); + if (r) + return WHY("crypto_sign_edwards25519sha512batch() failed.\n"); + + dump("signature",cipherText,cipherLen); + + unsigned char casabamelons[128]={ + 0xa4,0xea,0xd0,0x7f,0x11,0x65,0x28,0x3f,0x90,0x45,0x87,0xbf,0xe5,0xb9,0x15,0x2a,0x9a,0x2d,0x99,0x35,0x0d,0x0e,0x7b,0xb0,0xcd,0x15,0x2e,0xe8,0xeb,0xb3,0xc2,0xb1,0x13,0x8e,0xe3,0x82,0x55,0x6c,0x6e,0x34,0x44,0xe4,0xbc,0xa3,0xd5,0xe0,0x7a,0x6a,0x67,0x61,0xda,0x79,0x67,0xb6,0x1c,0x2e,0x48,0xc7,0x28,0x5b,0xd8,0xd0,0x54,0x0c,0x4e,0x6f,0x20,0x63,0x61,0x73,0x61,0x62,0x61,0x20,0x6d,0x65,0x6c,0x6f,0x6e,0x73,0x20,0x61,0x6c,0x6c,0x6f,0x77,0x65,0x64,0x20,0x69,0x6e,0x20,0x74,0x68,0x65,0x20,0x6c,0x61,0x62,0x2e,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + }; + + if (cipherLen!=128||memcmp(casabamelons, cipherText, 128)) { + WHY("Computed signature for stored key+message does not match expected value.\n"); + dump("expected signature",casabamelons,sizeof(casabamelons)); + } + + bzero(&plainTextOut,1024); plainLenOut=0; + r=crypto_sign_edwards25519sha512batch_open(plainTextOut,&plainLenOut, + &casabamelons[0],128, + /* the public key, which is the 2nd + half of the secret key. */ + &key[32]); + if (r) + WHY("Cannot open rearranged ref/ version of signature.\n"); + else + cli_printf(context, "Signature open fine.\n"); + + } + + return 0; +} + +void context_switch_test(int); +DEFINE_CMD(app_mem_test, 0, + "Run memory speed test", + "test","memory"); +static int app_mem_test(const struct cli_parsed *UNUSED(parsed), struct cli_context *UNUSED(context)) +{ + size_t mem_size; + size_t addr; + uint64_t count; + + + // First test context switch speed + context_switch_test(1); + + for(mem_size=1024;mem_size<=(128*1024*1024);mem_size*=2) { + uint8_t *mem=malloc(mem_size); + if (!mem) { + fprintf(stderr,"Could not allocate %zdKB memory -- stopping test.\n",mem_size/1024); + return -1; + } + + // Fill memory with random stuff so that we don't have memory page-in + // delays when doing the reads + for(addr=0;addr