diff --git a/commandline.c b/commandline.c index c20d1ad5..ca68034f 100644 --- a/commandline.c +++ b/commandline.c @@ -434,6 +434,11 @@ int app_echo(int argc, const char *const *argv, struct command_line_option *o) return 0; } +int cli_lookup_did(const char *text) +{ + return text[0] == '\0' || strcmp(text, "*") == 0 || str_is_did(text); +} + int app_dna_lookup(int argc, const char *const *argv, struct command_line_option *o) { int i; @@ -447,7 +452,7 @@ int app_dna_lookup(int argc, const char *const *argv, struct command_line_option char uris[MAXREPLIES][MAXURILEN]; const char *did; - if (cli_arg(argc, argv, o, "did", &did, NULL, "*") == -1) + if (cli_arg(argc, argv, o, "did", &did, cli_lookup_did, "*") == -1) return -1; /* Bind to MDP socket and await confirmation */ @@ -503,28 +508,32 @@ int app_dna_lookup(int argc, const char *const *argv, struct command_line_option else if ((rx.packetTypeAndFlags&MDP_TYPE_MASK)==MDP_TX) { /* Extract DID, Name, URI from response. */ if (strlen((char *)rx.in.payload)<512) { - char sidhex[512]; - char did[512]; - char name[512]; + char sidhex[SID_STRLEN + 1]; + char did[DID_MAXSIZE + 1]; + char name[64]; char uri[512]; - if (!parseDnaReply(rx.in.payload,rx.in.payload_length, - sidhex,did,name,uri)) - { - /* Have we seen this response before? */ - int i; - for(i=0;i31) return WHY("DID too long (31 digits max)"); if (strlen(name)>63) return WHY("Name too long (31 char max)"); if (!(keyring = keyring_open_with_pins(pin))) @@ -1647,14 +1660,18 @@ int app_node_info(int argc, const char *const *argv, struct command_line_option continue; } - { - char sidhex[512]; - char did[512]; - char name[512]; + { + char sidhex[SID_STRLEN + 1]; + char did[DID_MAXSIZE + 1]; + char name[64]; char uri[512]; - if (!parseDnaReply(m2.in.payload,m2.in.payload_length, - sidhex,did,name,uri)) - { + if ( !parseDnaReply((char *)m2.in.payload, m2.in.payload_length, sidhex, did, name, uri, NULL) + || !str_is_subscriber_id(sidhex) + || !str_is_did(did) + || !str_is_uri(uri) + ) { + WHYF("Received malformed DNA reply: %s", alloca_toprint(160, (const char *)m2.in.payload, m2.in.payload_length)); + } else { /* Got a good DNA reply, copy it into place */ bcopy(did,mdp.nodeinfo.did,32); bcopy(name,mdp.nodeinfo.name,64); diff --git a/dataformats.c b/dataformats.c index 527bebfe..22ccc359 100644 --- a/dataformats.c +++ b/dataformats.c @@ -1,6 +1,6 @@ -/* +/* Serval Distributed Numbering Architecture (DNA) -Copyright (C) 2010 Paul Gardner-Stephen +Copyright (C) 2010 Paul Gardner-Stephen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -99,7 +99,8 @@ int fromhexstr(unsigned char *dstBinary, const char *srcHex, size_t nbinary) int str_is_subscriber_id(const char *sid) { - return strcasecmp(sid, "broadcast") == 0 || _is_xstring(sid, SID_STRLEN); + size_t len = 0; + return strn_is_subscriber_id(sid, &len) && sid[len] == '\0'; } int strn_is_subscriber_id(const char *sid, size_t *lenp) @@ -157,6 +158,29 @@ int rhizome_str_is_file_hash(const char *hash) return _is_xstring(hash, RHIZOME_FILEHASH_STRLEN); } +int str_is_did(const char *did) +{ + size_t len = 0; + return strn_is_did(did, &len) && did[len] == '\0'; +} + +int is_didchar(char c) +{ + return isdigit(c) || c == '*' || c == '#' || c == '+'; +} + +int strn_is_did(const char *did, size_t *lenp) +{ + int i; + for (i = 0; i < DID_MAXSIZE && is_didchar(did[i]); ++i) + ; + if (i < DID_MINSIZE) + return 0; + if (lenp) + *lenp = i; + return 1; +} + int extractDid(unsigned char *packet,int *ofs,char *did) { int d=0; @@ -337,3 +361,45 @@ int safeZeroField(unsigned char *packet,int start,int count) return 0; } +int is_uri_char_scheme(char c) +{ + return isalpha(c) || isdigit(c) || c == '+' || c == '-' || c == '.'; +} + +int is_uri_char_unreserved(char c) +{ + return isalpha(c) || isdigit(c) || c == '-' || c == '.' || c == '_' || c == '~'; +} + +int is_uri_char_reserved(char c) +{ + switch (c) { + case ':': case '/': case '?': case '#': case '[': case ']': case '@': + case '!': case '$': case '&': case '\'': case '(': case ')': + case '*': case '+': case ',': case ';': case '=': + return 1; + } + return 0; +} + +/* Return true if the string resembles a URI. + Based on RFC-3986 generic syntax, assuming nothing about the hierarchical part. + @author Andrew Bettison + */ +int str_is_uri(const char *uri) +{ + const char *p = uri; + // Scheme is ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + if (!isalpha(*p++)) + return 0; + while (is_uri_char_scheme(*p)) + ++p; + // Scheme is followed by colon ":". + if (*p++ != ':') + return 0; + // Hierarchical part must contain only valid characters. + const char *q = p; + while (is_uri_char_unreserved(*p) || is_uri_char_reserved(*p)) + ++p; + return p != q && *p == '\0'; +} diff --git a/dna_helper.c b/dna_helper.c index b0fd0241..d203577d 100644 --- a/dna_helper.c +++ b/dna_helper.c @@ -17,7 +17,12 @@ 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 "serval.h" +#include "strbuf.h" +#include "strbuf_helpers.h" /* The challenge with making an interface for calling an external program to @@ -38,194 +43,563 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. main loop. */ +int +parseDnaReply(const char *buf, size_t len, char *token, char *did, char *name, char *uri, const char **bufp) +{ + /* Replies look like: TOKEN|URI|DID|NAME| where TOKEN is usually a hex SID */ + const char *b = buf; + const char *e = buf + len; + char *p, *q; + for (p = token, q = token + SID_STRLEN; b != e && *b != '|' && p != q; ++p, ++b) + *p = *b; + *p = '\0'; + if (b == e || *b++ != '|') + return 0; + for (p = uri, q = uri + 511; b != e && *b != '|' && p != q; ++p, ++b) + *p = *b; + *p = '\0'; + if (b == e || *b++ != '|') + return 0; + for (p = did, q = did + DID_MAXSIZE; b != e && *b != '|' && p != q; ++p, ++b) + *p = *b; + *p = '\0'; + if (b == e || *b++ != '|') + return 0; + for (p = name, q = name + 63; b != e && *b != '|' && p != q; ++p, ++b) + *p = *b; + *p = '\0'; + if (b == e || *b++ != '|') + return 0; + if (bufp) + *bufp = b; + return 1; +} + +static pid_t dna_helper_pid = -1; static int dna_helper_stdin = -1; static int dna_helper_stdout = -1; +static int dna_helper_stderr = -1; +static int dna_helper_started = 0; -int -parseDnaReply(unsigned char *bytes, int count, - char *sidhex, char *did, char *name, char *uri) { - bzero(sidhex, SID_SIZE*2+1); - bzero(did, SID_SIZE); - bzero(name,64); - bzero(uri,512); - int i,l,maxlen; +static struct sched_ent sched_requests = STRUCT_SCHED_ENT_UNUSED; +static struct sched_ent sched_replies = STRUCT_SCHED_ENT_UNUSED; +static struct sched_ent sched_harvester = STRUCT_SCHED_ENT_UNUSED; +static struct sched_ent sched_errors = STRUCT_SCHED_ENT_UNUSED; +static struct sched_ent sched_restart = STRUCT_SCHED_ENT_UNUSED; +static struct sched_ent sched_timeout = STRUCT_SCHED_ENT_UNUSED; - l=0; - - /* Replies look like: TOKEN|URI|DID|CALLERID| */ +// This buffer must hold "SID|DID|\n\0" +static char request_buffer[SID_STRLEN + DID_MAXSIZE + 4]; +static char *request_bufptr = NULL; +static char *request_bufend = NULL; +static overlay_mdp_data_frame request_mdp_data; +static char request_did[DID_MAXSIZE + 1]; - maxlen=SID_SIZE*2+1; - for(i=0;l=count) return WHY("DNA helper response does not contain URI field"); - if (l>=maxlen) return WHY("DNA helper response SID field too long"); - l=0; i++; maxlen=511; - for(;l=count) return WHY("DNA helper response does not contain DID field"); - if (l>=maxlen) return WHY("DNA helper response URI field too long"); - l=0; i++; maxlen=SID_SIZE; - for(;l=count) return WHY("DNA helper response does not contain CALLERID field"); - if (l>=maxlen) return WHY("DNA helper response DID field too long"); - l=0; i++; maxlen=SID_SIZE; - for(;l=count) return WHY("DNA helper response does not contain terminator"); - if (l>=maxlen) return WHY("DNA helper response CALLERID field too long"); - - /* DEBUGF("did='%s', name='%s', uri='%s'",did,name,uri); */ - - return 0; -} - -void dna_helper_monitor(int fd) +static void +dna_helper_close_pipes() { - return; + if (dna_helper_stdin != -1) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER closing stdin pipe fd=%d", dna_helper_stdin); + close(dna_helper_stdin); + dna_helper_stdin = -1; + } + if (sched_requests.poll.fd != -1) { + unwatch(&sched_requests); + sched_requests.poll.fd = -1; + } + if (dna_helper_stdout != -1) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER closing stdout pipe fd=%d", dna_helper_stdout); + close(dna_helper_stdout); + dna_helper_stdout = -1; + } + if (sched_replies.poll.fd != -1) { + unwatch(&sched_replies); + sched_replies.poll.fd = -1; + } + if (dna_helper_stderr != -1) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER closing stderr pipe fd=%d", dna_helper_stderr); + close(dna_helper_stderr); + dna_helper_stderr = -1; + } + if (sched_errors.poll.fd != -1) { + unwatch(&sched_errors); + sched_errors.poll.fd = -1; + } } -struct sched_ent dna_helper_sched; - static int -dna_helper_start(const char *command, const char *arg) { - int stdin_fds[2], stdout_fds[2]; - pid_t pid; - - DEBUG("Starting DNA helper"); - - if (pipe(stdin_fds)) +dna_helper_start(const char *command, const char *arg) +{ + dna_helper_close_pipes(); + int stdin_fds[2], stdout_fds[2], stderr_fds[2]; + if (pipe(stdin_fds) == -1) return WHY_perror("pipe"); - - if (pipe(stdout_fds)) { + if (pipe(stdout_fds) == -1) { + WHY_perror("pipe"); close(stdin_fds[0]); - close(stdin_fds[1]); - return WHY_perror("pipe"); + close(stdin_fds[1]); + return -1; } - - if ((pid = fork()) == 0) { + if (pipe(stderr_fds) == -1) { + WHY_perror("pipe"); + close(stdin_fds[0]); + close(stdin_fds[1]); + close(stdout_fds[0]); + close(stdout_fds[1]); + return -1; + } + switch (dna_helper_pid = fork()) { + case 0: /* Child, should exec() to become helper after installing file descriptors. */ - if (dup2(stdin_fds[1], 0)) /* replace stdin */ - exit(-1); - if (dup2(stdout_fds[0], 1)) /* replace stdout */ - exit(-1); - if (dup2(stdout_fds[0], 2)) /* replace stderr */ - exit(-1); - execl(command, command, arg, NULL); - DEBUG("execl() failed"); - abort(); /* Can't get here */ - } else { - if (pid == -1) { - /* fork failed */ - WHY_perror("fork"); - close(stdin_fds[0]); - close(stdin_fds[1]); - close(stdout_fds[0]); - close(stdout_fds[1]); - return -1; - } else { - /* Parent, should put file descriptors into place for use */ - dna_helper_stdin = stdin_fds[0]; - dna_helper_stdout = stdout_fds[1]; - - /* Need to watch dna_helper_stdout */ - // XXX need to initialise structure before calling watch - // XXX watch(&dna_helper_sched); - // XXX need to add unwatch() when we detect that the process has died. - return 0; + set_logging(stderr); + signal(SIGTERM, SIG_DFL); + close(stdin_fds[1]); + close(stdout_fds[0]); + close(stderr_fds[0]); + if (dup2(stderr_fds[1], 2) == -1 || dup2(stdout_fds[1], 1) == -1 || dup2(stdin_fds[0], 0) == -1) { + LOG_perror(LOG_LEVEL_FATAL, "dup2"); + fflush(stderr); + _exit(-1); } + execl(command, command, arg, NULL); + LOGF_perror(LOG_LEVEL_FATAL, "execl(%s, %s, %s, NULL)", command, command, arg ? arg : "NULL"); + fflush(stderr); + do { _exit(-1); } while (1); + break; + case -1: + /* fork failed */ + WHY_perror("fork"); + close(stdin_fds[0]); + close(stdin_fds[1]); + close(stdout_fds[0]); + close(stdout_fds[1]); + close(stderr_fds[0]); + close(stderr_fds[1]); + return -1; + default: + /* Parent, should put file descriptors into place for use */ + close(stdin_fds[0]); + close(stdout_fds[1]); + close(stderr_fds[1]); + dna_helper_started = 0; + dna_helper_stdin = stdin_fds[1]; + dna_helper_stdout = stdout_fds[0]; + dna_helper_stderr = stderr_fds[0]; + INFOF("STARTED DNA HELPER pid=%u stdin=%d stdout=%d stderr=%d executable=%s arg=%s", + dna_helper_pid, + dna_helper_stdin, + dna_helper_stdout, + dna_helper_stderr, + command, + arg ? arg : "NULL" + ); + sched_requests.function = monitor_requests; + sched_requests.context = NULL; + sched_requests.poll.fd = -1; + sched_requests.poll.events = POLLOUT; + sched_requests.stats = NULL; + sched_timeout.function = reply_timeout; + sched_timeout.context = NULL; + sched_timeout.stats = NULL; + sched_replies.function = monitor_replies; + sched_replies.context = NULL; + sched_replies.poll.fd = dna_helper_stdout; + sched_replies.poll.events = POLLIN; + sched_replies.stats = NULL; + sched_errors.function = monitor_errors; + sched_errors.context = NULL; + sched_errors.poll.fd = dna_helper_stderr; + sched_errors.poll.events = POLLIN; + sched_errors.stats = NULL; + sched_harvester.function = harvester; + sched_harvester.stats = NULL; + sched_harvester.alarm = overlay_gettime_ms() + 1000; + sched_harvester.deadline = sched_harvester.alarm + 1000; + reply_bufend = reply_buffer; + discarding_until_nl = 0; + awaiting_reply = 0; + watch(&sched_replies); + watch(&sched_errors); + schedule(&sched_harvester); + return 0; } return -1; } -int -dna_helper_enqueue(char *did, unsigned char *requestorSid) { - const char *dna_helper, *dna_helper_arg; - char buffer[1024]; - - if (dna_helper_stdin == -2) - return -1; +static int +dna_helper_kill() +{ + if (awaiting_reply) { + unschedule(&sched_timeout); + awaiting_reply = 0; + } + if (dna_helper_pid > 0) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER sending SIGTERM to pid=%d", dna_helper_pid); + if (kill(dna_helper_pid, SIGTERM) == -1) + WHYF_perror("kill(%d, SIGTERM)", dna_helper_pid); + // The process is wait()ed for in dna_helper_monitor() so that we do not block here. + return 1; + } + return 0; +} - if (dna_helper_stdin == -1) { - dna_helper = confValueGet("dna.helper", NULL); - if (!dna_helper || !dna_helper[0]) { - /* Check if we have a helper configured. If not, then set - dna_helper_stdin to magic value of -2 so that we don't waste time - in future looking up the dna helper configuration value. */ - DEBUG("We have no DNA helper configured"); - dna_helper_stdin = -2; - return -1; +static int +dna_helper_harvest(int blocking) +{ + if (dna_helper_pid > 0) { + if (blocking && (debug & DEBUG_DNAHELPER)) + DEBUGF("DNAHELPER waiting for pid=%d to die", dna_helper_pid); + int status; + pid_t pid = waitpid(dna_helper_pid, &status, blocking ? 0 : WNOHANG); + if (pid == dna_helper_pid) { + strbuf b = strbuf_alloca(80); + INFOF("DNAHELPER process pid=%u %s", pid, strbuf_str(strbuf_append_exit_status(b, status))); + unschedule(&sched_harvester); + dna_helper_pid = -1; + if (awaiting_reply) { + unschedule(&sched_timeout); + awaiting_reply = 0; + } + return 1; + } else if (pid == -1) { + return WHYF_perror("waitpid(%d, %s)", dna_helper_pid, blocking ? "0" : "WNOHANG"); + } else if (pid) { + return WHYF("waitpid(%d, %s) returned %d", dna_helper_pid, blocking ? "0" : "WNOHANG", pid); } - - /* Look for optional argument */ - dna_helper_arg = confValueGet("dna.helperarg", NULL); + } + return 0; +} - /* Okay, so we have a helper configured. - Run it */ - if (dna_helper_start(dna_helper, dna_helper_arg) < 0) { +int dna_helper_shutdown() +{ + if (debug & DEBUG_DNAHELPER) + DEBUG("DNAHELPER shutting down"); + dna_helper_close_pipes(); + switch (dna_helper_kill()) { + case -1: + return -1; + case 0: + return 0; + default: + return dna_helper_harvest(1); + } +} + +static void monitor_requests(struct sched_ent *alarm) +{ + if (debug & DEBUG_DNAHELPER) { + DEBUGF("sched_requests.poll.revents=%s", + strbuf_str(strbuf_append_poll_events(strbuf_alloca(40), sched_requests.poll.revents)) + ); + } + if (sched_requests.poll.revents & (POLLHUP | POLLERR)) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER closing stdin fd=%d", dna_helper_stdin); + close(dna_helper_stdin); + dna_helper_stdin = -1; + unwatch(&sched_requests); + sched_requests.poll.fd = -1; + dna_helper_kill(); + } + else if (sched_requests.poll.revents & POLLOUT) { + if (request_bufptr) { + if (request_bufptr < request_bufend) { + size_t remaining = request_bufend - request_bufptr; + sigPipeFlag = 0; + ssize_t written = write_nonblock(dna_helper_stdin, request_bufptr, remaining); + if (sigPipeFlag) { + /* Broken pipe is probably due to a dead helper, but make sure the helper is dead, just to be + sure. It will be harvested at the next harvester() timeout, and restarted on the first + request that arrives after a suitable pause has elapsed. Losing the current request is not + a big problem, because DNA preemptively retries. + */ + INFO("DNAHELPER got SIGPIPE on write -- stopping process"); + dna_helper_kill(); + } else if (written > 0) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER wrote request %s", alloca_toprint(-1, request_bufptr, written)); + request_bufptr += written; + } + } + if (request_bufptr >= request_bufend) { + // Request sent successfully. Start watching for reply. + request_bufptr = request_bufend = NULL; + awaiting_reply = 1; + sched_timeout.alarm = overlay_gettime_ms() + 1500; + sched_timeout.deadline = sched_timeout.alarm + 3000; + schedule(&sched_timeout); + } + } + // If no request to send, stop monitoring the helper's stdin pipe. + if (!request_bufptr) { + unwatch(&sched_requests); + sched_requests.poll.fd = -1; + } + } +} + +static char *strnstr(char *haystack, size_t haystack_len, const char *needle) +{ + size_t needle_len = strlen(needle); + for (; haystack_len >= needle_len; ++haystack, --haystack_len) { + if (strncmp(haystack, needle, needle_len) == 0) + return haystack; + } + return NULL; +} + +void handle_reply_line(const char *bufp, size_t len) +{ + if (!dna_helper_started) { + if (len == 8 && strncmp(bufp, "STARTED\n", 8) == 0) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER got STARTED ACK"); + dna_helper_started = 1; + // Start sending request if there is one pending. + if (request_bufptr) { + sched_requests.poll.fd = dna_helper_stdin; + watch(&sched_requests); + } + } else { + WHYF("DNAHELPER malformed start ACK %s", alloca_toprint(-1, bufp, len)); + dna_helper_kill(); + } + } else if (awaiting_reply) { + if (len == 5 && strncmp(bufp, "DONE\n", 5) == 0) { + if (debug & DEBUG_DNAHELPER) + DEBUG("DNAHELPER reply DONE"); + unschedule(&sched_timeout); + awaiting_reply = 0; + } else { + char sidhex[SID_STRLEN + 1]; + char did[DID_MAXSIZE + 1]; + char name[64]; + char uri[512]; + const char *replyend = NULL; + if (!parseDnaReply(bufp, len, sidhex, did, name, uri, &replyend)) + WHYF("DNAHELPER reply %s invalid -- ignored", alloca_toprint(-1, bufp, len)); + else if (uri[0] == '\0') + WHYF("DNAHELPER reply %s contains empty URI -- ignored", alloca_toprint(-1, bufp, len)); + else if (!str_is_uri(uri)) + WHYF("DNAHELPER reply %s contains invalid URI -- ignored", alloca_toprint(-1, bufp, len)); + else if (sidhex[0] == '\0') + WHYF("DNAHELPER reply %s contains empty token -- ignored", alloca_toprint(-1, bufp, len)); + else if (!str_is_subscriber_id(sidhex)) + WHYF("DNAHELPER reply %s contains invalid token -- ignored", alloca_toprint(-1, bufp, len)); + else if (did[0] == '\0') + WHYF("DNAHELPER reply %s contains empty DID -- ignored", alloca_toprint(-1, bufp, len)); + else if (!str_is_did(did)) + WHYF("DNAHELPER reply %s contains invalid DID -- ignored", alloca_toprint(-1, bufp, len)); + else if (strcmp(did, request_did) != 0) + WHYF("DNAHELPER reply %s contains mismatched DID -- ignored", alloca_toprint(-1, bufp, len)); + else if (*replyend != '\n') + WHYF("DNAHELPER reply %s contains spurious trailing chars -- ignored", alloca_toprint(-1, bufp, len)); + else { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER reply %s", alloca_toprint(-1, bufp, len)); + unsigned char sid[SID_SIZE]; + fromhex(sid, sidhex, SID_SIZE); + overlay_mdp_dnalookup_reply(&request_mdp_data, sid, uri, did, name); + } + } + } else { + WARNF("DNAHELPER spurious output %s -- ignored", alloca_toprint(-1, bufp, len)); + } +} + +static void monitor_replies(struct sched_ent *alarm) +{ + if (debug & DEBUG_DNAHELPER) { + DEBUGF("sched_replies.poll.revents=%s", + strbuf_str(strbuf_append_poll_events(strbuf_alloca(40), sched_replies.poll.revents)) + ); + } + if (sched_replies.poll.revents & POLLIN) { + size_t remaining = reply_buffer + sizeof reply_buffer - reply_bufend; + ssize_t nread = read_nonblock(sched_replies.poll.fd, reply_bufend, remaining); + if (nread > 0) { + char *bufp = reply_buffer; + char *readp = reply_bufend; + reply_bufend += nread; + char *nl; + while (nread > 0 && (nl = strnstr(readp, nread, "\n"))) { + size_t len = nl - bufp + 1; + if (discarding_until_nl) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("Discarding %s", alloca_toprint(-1, bufp, len)); + discarding_until_nl = 0; + } else { + handle_reply_line(bufp, len); + } + readp = bufp = nl + 1; + nread = reply_bufend - readp; + } + if (bufp != reply_buffer) { + size_t len = reply_bufend - bufp; + memmove(reply_buffer, bufp, len); + reply_bufend = reply_buffer + len; + } else if (reply_bufend >= reply_buffer + sizeof reply_buffer) { + WHY("DNAHELPER reply buffer overrun"); + if (debug & DEBUG_DNAHELPER) + DEBUGF("Discarding %s", alloca_toprint(-1, reply_buffer, sizeof reply_buffer)); + reply_bufend = reply_buffer; + discarding_until_nl = 1; + } + } + } + if (sched_replies.poll.revents & (POLLHUP | POLLERR)) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER closing stdout fd=%d", dna_helper_stdout); + close(dna_helper_stdout); + dna_helper_stdout = -1; + unwatch(&sched_replies); + sched_replies.poll.fd = -1; + dna_helper_kill(); + } +} + +static void monitor_errors(struct sched_ent *alarm) +{ + if (debug & DEBUG_DNAHELPER) { + DEBUGF("sched_errors.poll.revents=%s", + strbuf_str(strbuf_append_poll_events(strbuf_alloca(40), sched_errors.poll.revents)) + ); + } + if (sched_errors.poll.revents & POLLIN) { + char buffer[1024]; + ssize_t nread = read_nonblock(sched_errors.poll.fd, buffer, sizeof buffer); + if (nread > 0) + WHYF("DNAHELPER stderr %s", alloca_toprint(-1, buffer, nread)); + } + if (sched_errors.poll.revents & (POLLHUP | POLLERR)) { + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER closing stderr fd=%d", dna_helper_stderr); + close(dna_helper_stderr); + dna_helper_stderr = -1; + unwatch(&sched_errors); + sched_errors.poll.fd = -1; + } +} + +static void harvester(struct sched_ent *alarm) +{ + // While the helper process appears to still be running, keep calling this function. + // Otherwise, wait a while before re-starting the helper. + if (dna_helper_harvest(0) <= 0) { + sched_harvester.alarm = overlay_gettime_ms() + 1000; + sched_harvester.deadline = sched_harvester.alarm + 1000; + schedule(&sched_harvester); + } else { + const int delay_ms = 500; + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER process died, pausing %d ms before restart", delay_ms); + dna_helper_pid = 0; // Will be set to -1 after delay + sched_restart.function = restart_delayer; + sched_restart.alarm = overlay_gettime_ms() + delay_ms; + sched_restart.deadline = sched_restart.alarm + 500; + schedule(&sched_restart); + } +} + +static void restart_delayer(struct sched_ent *alarm) +{ + if (dna_helper_pid == 0) { + if (debug & DEBUG_DNAHELPER) + DEBUG("DNAHELPER re-enable restart"); + dna_helper_pid = -1; + } +} + +static void reply_timeout(struct sched_ent *alarm) +{ + if (awaiting_reply) { + WHY("DNAHELPER reply timeout"); + dna_helper_kill(); + } +} + +int +dna_helper_enqueue(overlay_mdp_frame *mdp, const char *did, const unsigned char *requestorSid) +{ + if (debug & DEBUG_DNAHELPER) + DEBUGF("DNAHELPER request did=%s sid=%s", did, alloca_tohex_sid(requestorSid)); + if (dna_helper_pid == 0) + return 0; + // Only try to restart a DNA helper process if the previous one is well and truly gone. + if (dna_helper_pid == -1 && dna_helper_stdin == -1 && dna_helper_stdout == -1 && dna_helper_stderr == -1) { + const char *dna_helper_executable = confValueGet("dna.helper.executable", NULL); + const char *dna_helper_arg1 = confValueGet("dna.helper.argv.1", NULL); + if (!dna_helper_executable || !dna_helper_executable[0]) { + /* Check if we have a helper configured. If not, then set + dna_helper_pid to magic value of 0 so that we don't waste time + in future looking up the dna helper configuration value. */ + INFO("DNAHELPER none configured"); + dna_helper_pid = 0; + return 0; + } + if (dna_helper_start(dna_helper_executable, dna_helper_arg1) == -1) { /* Something broke, bail out */ - DEBUG("Failed to start dna helper"); + WHY("DNAHELPER start failed"); return -1; } } - /* Write request to dna helper. Request takes form: SID-of-Requestor|DID|\n By passing the requestor's SID to the helper, we don't need to maintain any state, as all we have to do is wait for responses from the helper, which will include the requestor's SID. */ - bzero(buffer, sizeof(buffer)); - if (snprintf(buffer, sizeof(buffer) - 1, "%s|%s|\n", alloca_tohex_sid(requestorSid), did) > - sizeof(buffer) - 1) - return WHY("Command to helper is too long"); - - sigPipeFlag = 0; - write_str(dna_helper_stdin, buffer); - - if (sigPipeFlag) { - /* Assume broken pipe due to dead helper. - Next request will cause it to be restarted. - (Losing the current request is not a big problem, because - DNA preemptively retries, anyway. - XXX In fact, we should probably have a limit to the number of restarts - in quick succession so that we don't waste lots of time with a buggy or - suicidal helper. - */ - close(dna_helper_stdin); - close(dna_helper_stdout); - dna_helper_stdin = -1; - dna_helper_stdout = -1; - DEBUG("Sigpipe encountered"); - return -1; + if (dna_helper_stdin == -1) + return 0; + if (request_bufptr && request_bufptr != request_buffer) { + WARNF("DNAHELPER currently sending request %s -- dropping new request", request_buffer); + return 0; } - - return 0; -} - -int dna_return_resolution(overlay_mdp_frame *mdp, unsigned char *fromSid, - const char *did,const char *name, const char *uri) { - /* copy SID out into source address of frame */ - bcopy(fromSid,&mdp->out.src.sid[0],SID_SIZE); - - /* and build reply as did\nname\nURI */ - snprintf((char *)&mdp->out.payload[0],512,"%s\n%s\n%s", - did,name,uri); - mdp->out.payload_length=strlen((char *)mdp->out.payload)+1; - - /* Dispatch response */ - mdp->packetTypeAndFlags&=MDP_FLAG_MASK; - mdp->packetTypeAndFlags|=MDP_TX; - overlay_mdp_dispatch(mdp,0 /* system generated */, - NULL,0); - - return 0; + if (awaiting_reply) { + WARNF("DNAHELPER currently awaiting reply -- dropping new request", request_buffer); + return 0; + } + char buffer[sizeof request_buffer]; + strbuf b = strbuf_local(request_bufptr == request_buffer ? buffer : request_buffer, sizeof buffer); + strbuf_tohex(b, requestorSid, SID_SIZE); + strbuf_putc(b, '|'); + strbuf_puts(b, did); + strbuf_putc(b, '|'); + strbuf_putc(b, '\n'); + if (strbuf_overrun(b)) { + WHYF("DNAHELPER request buffer overrun: %s -- request not sent", strbuf_str(b)); + request_bufptr = request_bufend = NULL; + } else { + if (strbuf_str(b) != request_buffer) { + if (strcmp(strbuf_str(b), request_buffer) != 0) + WARNF("DNAHELPER overwriting unsent request %s", request_buffer); + strcpy(request_buffer, strbuf_str(b)); + } + request_bufptr = request_buffer; + request_bufend = request_buffer + strbuf_len(b); + request_mdp_data = mdp->out; + strncpy(request_did, did, sizeof request_did); + request_did[sizeof request_did - 1] = '\0'; + } + if (dna_helper_started) { + sched_requests.poll.fd = dna_helper_stdin; + watch(&sched_requests); + } + return 1; } diff --git a/fdqueue.c b/fdqueue.c index 9e652b31..31c5379c 100644 --- a/fdqueue.c +++ b/fdqueue.c @@ -137,6 +137,7 @@ int watch(struct sched_ent *alarm){ if (fdcount>=MAX_WATCHED_FDS) return WHY("Too many file handles to watch"); fd_callbacks[fdcount]=alarm; + alarm->poll.revents = 0; alarm->_poll_index=fdcount; fdcount++; } diff --git a/log.c b/log.c index 4873ed1a..b5a5d721 100644 --- a/log.c +++ b/log.c @@ -20,6 +20,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "serval.h" #include "strbuf.h" #include +#include #include #include #include @@ -39,9 +40,20 @@ static char _log_buf[8192]; static struct strbuf logbuf = STRUCT_STRBUF_EMPTY; #ifdef ANDROID -#include +#include #endif +void set_logging(FILE *f) +{ + logfile = f; + if (f == stdout) + INFO("Logging to stdout"); + else if (f == stderr) + INFO("Logging to stderr"); + else if (f != NULL) + INFOF("Logging to stream with fd=%d", fileno(f)); +} + FILE *open_logging() { if (!logfile) { @@ -231,7 +243,7 @@ unsigned int debugFlagMask(const char *flagname) { else if (!strcasecmp(flagname,"verbio")) return DEBUG_VERBOSE_IO; else if (!strcasecmp(flagname,"peers")) return DEBUG_PEERS; else if (!strcasecmp(flagname,"dnaresponses")) return DEBUG_DNARESPONSES; - else if (!strcasecmp(flagname,"dnarequests")) return DEBUG_DNAREQUESTS; + else if (!strcasecmp(flagname,"dnahelper")) return DEBUG_DNAHELPER; else if (!strcasecmp(flagname,"simulation")) return DEBUG_SIMULATION; else if (!strcasecmp(flagname,"packetformats")) return DEBUG_PACKETFORMATS; else if (!strcasecmp(flagname,"packetconstruction")) return DEBUG_PACKETCONSTRUCTION; @@ -251,40 +263,54 @@ unsigned int debugFlagMask(const char *flagname) { else if (!strcasecmp(flagname,"manifests")) return DEBUG_MANIFESTS; else if (!strcasecmp(flagname,"mdprequests")) return DEBUG_MDPREQUESTS; else if (!strcasecmp(flagname,"timing")) return DEBUG_TIMING; - return 0; } +static strbuf _toprint(strbuf sb, const char *srcBuf, size_t srcBytes) +{ + strbuf_putc(sb, '"'); + for (; srcBytes && !strbuf_overrun(sb); ++srcBuf, --srcBytes) { + if (*srcBuf == '\0') + strbuf_puts(sb, "\\0"); + else if (*srcBuf == '\n') + strbuf_puts(sb, "\\n"); + else if (*srcBuf == '\r') + strbuf_puts(sb, "\\r"); + else if (*srcBuf == '\t') + strbuf_puts(sb, "\\t"); + else if (*srcBuf == '\\') + strbuf_puts(sb, "\\\\"); + else if (*srcBuf >= ' ' && *srcBuf <= '~') + strbuf_putc(sb, *srcBuf); + else + strbuf_sprintf(sb, "\\x%02x", *srcBuf); + } + strbuf_putc(sb, '"'); + if (strbuf_overrun(sb)) { + strbuf_trunc(sb, -4); + strbuf_puts(sb, "\"..."); + } + return sb; +} + /* Format a buffer of data as a printable representation, eg: "Abc\x0b\n\0", for display - in log messages. + in log messages. If dstStrLen == -1 then assumes the dstStr buffer is large enough to + hold the representation of the entire srcBuf. @author Andrew Bettison */ -char *toprint(char *dstStr, size_t dstChars, const unsigned char *srcBuf, size_t srcBytes) +char *toprint(char *dstStr, ssize_t dstStrLen, const char *srcBuf, size_t srcBytes) { - strbuf b = strbuf_local(dstStr, dstChars); - strbuf_putc(b, '"'); - for (; srcBytes && !strbuf_overrun(b); ++srcBuf, --srcBytes) { - if (*srcBuf == '\0') - strbuf_puts(b, "\\0"); - else if (*srcBuf == '\n') - strbuf_puts(b, "\\n"); - else if (*srcBuf == '\r') - strbuf_puts(b, "\\r"); - else if (*srcBuf == '\t') - strbuf_puts(b, "\\t"); - else if (*srcBuf == '\\') - strbuf_puts(b, "\\\\"); - else if (*srcBuf >= ' ' && *srcBuf <= '~') - strbuf_putc(b, *srcBuf); - else - strbuf_sprintf(b, "\\x%02x", *srcBuf); - } - strbuf_putc(b, '"'); - if (strbuf_overrun(b)) { - strbuf_trunc(b, -4); - strbuf_puts(b, "\"..."); - } - return dstStr; + return strbuf_str(_toprint(strbuf_local(dstStr, (dstStrLen == -1 ? 2 + srcBytes * 4 : dstStrLen) + 1), srcBuf, srcBytes)); +} + +/* Compute the length of the printable string produced by toprint(). If dstStrLen == -1 then + returns the exact number of characters in the printable representation, otherwise returns + dstStrLen. + @author Andrew Bettison + */ +size_t toprint_strlen(ssize_t dstStrLen, const char *srcBuf, size_t srcBytes) +{ + return dstStrLen == -1 ? strbuf_count(_toprint(strbuf_local(NULL, 0), srcBuf, srcBytes)) : dstStrLen; } /* Read the symbolic link into the supplied buffer and add a terminating nul. Return -1 if the diff --git a/net.c b/net.c index 427fffbe..33753435 100644 --- a/net.c +++ b/net.c @@ -47,7 +47,7 @@ int _set_block(int fd, const char *file, unsigned int line, const char *function return 0; } -int _read_nonblock(int fd, void *buf, size_t len, const char *file, unsigned int line, const char *function) +ssize_t _read_nonblock(int fd, void *buf, size_t len, const char *file, unsigned int line, const char *function) { ssize_t nread = read(fd, buf, len); if (nread == -1) { @@ -66,7 +66,7 @@ int _read_nonblock(int fd, void *buf, size_t len, const char *file, unsigned int return nread; } -int _write_all(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function) +ssize_t _write_all(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function) { ssize_t written = write(fd, buf, len); if (written == -1) { @@ -82,7 +82,7 @@ int _write_all(int fd, const void *buf, size_t len, const char *file, unsigned i return written; } -int _write_nonblock(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function) +ssize_t _write_nonblock(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function) { ssize_t written = write(fd, buf, len); if (written == -1) { @@ -101,7 +101,7 @@ int _write_nonblock(int fd, const void *buf, size_t len, const char *file, unsig return written; } -int _write_all_nonblock(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function) +ssize_t _write_all_nonblock(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function) { ssize_t written = _write_nonblock(fd, buf, len, file, line, function); if (written != -1 && written != len) { @@ -112,12 +112,12 @@ int _write_all_nonblock(int fd, const void *buf, size_t len, const char *file, u return written; } -int _write_str(int fd, const char *str, const char *file, unsigned int line, const char *function) +ssize_t _write_str(int fd, const char *str, const char *file, unsigned int line, const char *function) { return _write_all(fd, str, strlen(str), file, line, function); } -int _write_str_nonblock(int fd, const char *str, const char *file, unsigned int line, const char *function) +ssize_t _write_str_nonblock(int fd, const char *str, const char *file, unsigned int line, const char *function) { return _write_all_nonblock(fd, str, strlen(str), file, line, function); } diff --git a/overlay_mdp.c b/overlay_mdp.c index 8c1a2963..3cbc969d 100644 --- a/overlay_mdp.c +++ b/overlay_mdp.c @@ -16,8 +16,9 @@ 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.h" #include +#include "serval.h" +#include "strbuf.h" struct sched_ent mdp_abstract; struct sched_ent mdp_named; @@ -502,53 +503,26 @@ int overlay_saw_mdp_frame(overlay_mdp_frame *mdp,long long now) if (mdp->out.payload_length<1) { RETURN(WHY("Empty DID in DNA resolution request")); } bcopy(&mdp->out.payload[0],&did[0],pll); - /* make sure it is null terminated */ did[pll]=0; - /* remember source sid for putting back later */ - overlay_mdp_frame mdpreply; - int results=0; while(keyring_find_did(keyring,&cn,&in,&kp,did)) { - bzero(&mdpreply,sizeof(mdpreply)); - - /* mark as outgoing MDP message */ - mdpreply.packetTypeAndFlags=MDP_TX; - - /* Set source and destination addresses */ - bcopy(&mdp->out.dst.sid,mdpreply.out.src.sid,SID_SIZE); - bcopy(&mdp->out.src.sid,mdpreply.out.dst.sid,SID_SIZE); - mdpreply.out.src.port=mdp->out.dst.port; - mdpreply.out.dst.port=mdp->out.src.port; - /* package DID and Name into reply (we include the DID because it could be a wild-card DID search, but the SID is implied in the source address of our reply). */ - if (keyring->contexts[cn]->identities[in]->keypairs[kp] - ->private_key_len>64) + if (keyring->contexts[cn]->identities[in]->keypairs[kp]->private_key_len > DID_MAXSIZE) /* skip excessively long DID records */ continue; - /* and null-terminated DID */ - unsigned char *unpackedDid= - keyring->contexts[cn]->identities[in]->keypairs[kp] - ->private_key; - unsigned char *packedSid= - keyring->contexts[cn]->identities[in]->keypairs[0] - ->public_key; - char *name= - (char *)keyring->contexts[cn]->identities[in]->keypairs[kp] - ->public_key; - /* copy SID out into source address of frame */ - bcopy(packedSid,&mdpreply.out.src.sid[0],SID_SIZE); - /* and build reply as did\nname\nURI */ - snprintf((char *)&mdpreply.out.payload[0],512,"%s|sid://%s/%s|%s|%s|", - alloca_tohex_sid(packedSid), - alloca_tohex_sid(packedSid),unpackedDid, - unpackedDid,name); - mdpreply.out.payload_length=strlen((char *)mdpreply.out.payload)+1; - - /* deliver reply */ - overlay_mdp_dispatch(&mdpreply,0 /* system generated */,NULL,0); + const unsigned char *packedSid = keyring->contexts[cn]->identities[in]->keypairs[0]->public_key; + const char *unpackedDid = (const char *) keyring->contexts[cn]->identities[in]->keypairs[kp]->private_key; + const char *name = (const char *)keyring->contexts[cn]->identities[in]->keypairs[kp]->public_key; + // URI is sid://SIDHEX/DID + strbuf b = strbuf_alloca(SID_STRLEN + DID_MAXSIZE + 10); + strbuf_puts(b, "sid://"); + strbuf_tohex(b, packedSid, SID_SIZE); + strbuf_putc(b, '/'); + strbuf_puts(b, unpackedDid); + overlay_mdp_dnalookup_reply(&mdp->out, packedSid, strbuf_str(b), unpackedDid, name); kp++; results++; } @@ -563,8 +537,7 @@ int overlay_saw_mdp_frame(overlay_mdp_frame *mdp,long long now) when results become available, so this function will return immediately, so as not to cause blockages and delays in servald. */ - DEBUGF("Asking DNA helper to resolve '%s'",did); - dna_helper_enqueue(did,mdp->out.src.sid); + dna_helper_enqueue(mdp, did, mdp->out.src.sid); } RETURN(0); DEBUG("Got here"); @@ -624,6 +597,26 @@ int overlay_saw_mdp_frame(overlay_mdp_frame *mdp,long long now) RETURN(0); } +int overlay_mdp_dnalookup_reply(const overlay_mdp_data_frame *mdpd, const unsigned char *sid, const char *uri, const char *did, const char *name) +{ + overlay_mdp_frame mdpreply; + bzero(&mdpreply, sizeof mdpreply); + mdpreply.packetTypeAndFlags = MDP_TX; // outgoing MDP message + bcopy(mdpd->src.sid, mdpreply.out.dst.sid, SID_SIZE); + mdpreply.out.dst.port = mdpd->src.port; + mdpreply.out.src.port = mdpd->dst.port; + bcopy(sid, mdpreply.out.src.sid, SID_SIZE); + /* build reply as TOKEN|URI|DID|NAME| */ + strbuf b = strbuf_local((char *)mdpreply.out.payload, sizeof mdpreply.out.payload); + strbuf_tohex(b, sid, SID_SIZE); + strbuf_sprintf(b, "|%s|%s|%s|", uri, did, name); + if (strbuf_overrun(b)) + return WHY("MDP payload overrun"); + mdpreply.out.payload_length = strbuf_len(b) + 1; + /* deliver reply */ + return overlay_mdp_dispatch(&mdpreply, 0 /* system generated */, NULL, 0); +} + int overlay_mdp_sanitytest_sourceaddr(sockaddr_mdp *src,int userGeneratedFrameP, struct sockaddr_un *recvaddr, int recvaddrlen) diff --git a/packetformats.c b/packetformats.c index 2d2b2650..428cb84d 100644 --- a/packetformats.c +++ b/packetformats.c @@ -296,7 +296,7 @@ int extractRequest(unsigned char *packet,int *packet_ofs,int packet_len, *bytes|=packet[(*packet_ofs)++]; *flags=packet[(*packet_ofs)++]; - if (debug&DEBUG_DNAREQUESTS) printf("Write flags = 0x%02x\n",*flags); + if (debug&DEBUG_PACKETFORMATS) printf("Write flags = 0x%02x\n",*flags); if (*packet_ofs<0||(*packet_ofs)+(*bytes)>=packet_len) { diff --git a/rhizome_bundle.c b/rhizome_bundle.c index 8201a6a7..f446962c 100644 --- a/rhizome_bundle.c +++ b/rhizome_bundle.c @@ -141,7 +141,7 @@ int rhizome_read_manifest_file(rhizome_manifest *m, const char *filename, int bu m->errors++; WARNF(bufferP ? "Malformed manifest line in buffer %p: %s" : "Malformed manifest line in file %s: %s", - filename, alloca_toprint(80, (unsigned char *)line, linelen)); + filename, alloca_toprint(80, line, linelen)); } else { *p++ = '\0'; char *var = line; diff --git a/rhizome_fetch.c b/rhizome_fetch.c index 414693c8..207ff65e 100644 --- a/rhizome_fetch.c +++ b/rhizome_fetch.c @@ -869,7 +869,7 @@ void rhizome_fetch_poll(struct sched_ent *alarm) q->request_len += bytes; if (http_header_complete(q->request, q->request_len, bytes + 4)) { if (debug & DEBUG_RHIZOME_RX) - DEBUGF("Got HTTP reply: %s", alloca_toprint(160, (unsigned char *)q->request, q->request_len)); + DEBUGF("Got HTTP reply: %s", alloca_toprint(160, q->request, q->request_len)); /* We have all the reply headers, so parse them, taking care of any following bytes of content. */ char *p = NULL; diff --git a/rhizome_http.c b/rhizome_http.c index fd924df6..a4ed8947 100644 --- a/rhizome_http.c +++ b/rhizome_http.c @@ -564,7 +564,7 @@ static int rhizome_server_parse_http_request(rhizome_http_request *r) if (path) { char *id = NULL; if (debug & DEBUG_RHIZOME_TX) - DEBUGF("GET %s", alloca_toprint(1024, (unsigned char *)path, pathlen)); + DEBUGF("GET %s", alloca_toprint(1024, path, pathlen)); if (strcmp(path, "/favicon.ico") == 0) { r->request_type = RHIZOME_HTTP_REQUEST_FAVICON; rhizome_server_http_response_header(r, 200, "image/vnd.microsoft.icon", favicon_len); @@ -605,7 +605,7 @@ static int rhizome_server_parse_http_request(rhizome_http_request *r) } } else { if (debug & DEBUG_RHIZOME_TX) - DEBUGF("Received malformed HTTP request: %s", alloca_toprint(120, (unsigned char *)r->request, r->request_length)); + DEBUGF("Received malformed HTTP request: %s", alloca_toprint(120, (const char *)r->request, r->request_length)); rhizome_server_simple_http_response(r, 400, "

Malformed request

\r\n"); } @@ -669,7 +669,7 @@ static int rhizome_server_set_response(rhizome_http_request *r, const struct htt r->buffer_offset = 0; r->request_type |= RHIZOME_HTTP_REQUEST_FROMBUFFER; if (debug & DEBUG_RHIZOME_TX) - DEBUGF("Sending HTTP response: %s", alloca_toprint(120, r->buffer, r->buffer_length)); + DEBUGF("Sending HTTP response: %s", alloca_toprint(120, (const char *)r->buffer, r->buffer_length)); return 0; } diff --git a/serval.h b/serval.h index d558a86c..33babe87 100644 --- a/serval.h +++ b/serval.h @@ -299,7 +299,8 @@ void keyring_identity_extract(const keyring_identity *id, const unsigned char ** get - 8 bit variable value */ -#define SID_SIZE 32 +#define SID_SIZE 32 +#define DID_MINSIZE 5 #define DID_MAXSIZE 32 #define SIDDIDFIELD_LEN (SID_SIZE+1) #define PINFIELD_LEN 32 @@ -501,6 +502,7 @@ struct sched_ent{ int _poll_index; }; +#define STRUCT_SCHED_ENT_UNUSED ((struct sched_ent){NULL, NULL, NULL, NULL, {-1, 0, 0}, 0LL, 0LL, NULL, -1}) extern int overlayMode; #define OVERLAY_INTERFACE_UNKNOWN 0 @@ -703,6 +705,9 @@ char *str_toupper_inplace(char *s); int str_is_subscriber_id(const char *sid); int strn_is_subscriber_id(const char *sid, size_t *lenp); +int str_is_did(const char *did); +int strn_is_did(const char *did, size_t *lenp); +int str_is_uri(const char *uri); int stowSid(unsigned char *packet, int ofs, const char *sid); int stowDid(unsigned char *packet,int *ofs,char *did); @@ -798,6 +803,7 @@ int overlay_frame_resolve_addresses(overlay_frame *f); #define LOG_LEVEL_FATAL (4) extern unsigned int debug; +void set_logging(FILE *f); FILE *open_logging(); void close_logging(); void logMessage(int level, const char *file, unsigned int line, const char *function, const char *fmt, ...); @@ -805,42 +811,48 @@ void vlogMessage(int level, const char *file, unsigned int line, const char *fun unsigned int debugFlagMask(const char *flagname); char *catv(const char *data, char *buf, size_t len); int dump(char *name, unsigned char *addr, size_t len); -char *toprint(char *dstStr, size_t dstChars, const unsigned char *srcBuf, size_t srcBytes); int log_backtrace(); +char *toprint(char *dstStr, ssize_t dstStrLen, const char *srcBuf, size_t srcBytes); +size_t toprint_strlen(ssize_t dstStrLen, const char *srcBuf, size_t srcBytes); -#define alloca_toprint(dstlen,buf,len) toprint((char *)alloca((dstlen) + 1), (dstlen) + 1, (buf), (len)) +#define alloca_toprint(dstlen,buf,len) toprint((char *)alloca(toprint_strlen((dstlen), (buf), (len)) + 1), (dstlen), (buf), (len)) #define alloca_tohex(buf,len) tohex((char *)alloca((len)*2+1), (buf), (len)) #define alloca_tohex_sid(sid) alloca_tohex((sid), SID_SIZE) const char *trimbuildpath(const char *s); -#define LOGF(L,F,...) (logMessage(L, __FILE__, __LINE__, __FUNCTION__, F, ##__VA_ARGS__)) #define logMessage_perror(L,file,line,func,F,...) \ (logMessage(L, file, line, func, F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno)) +#define LOGF(L,F,...) logMessage(L, __FILE__, __LINE__, __FUNCTION__, F, ##__VA_ARGS__) +#define LOGF_perror(L,F,...) logMessage_perror(L, __FILE__, __LINE__, __FUNCTION__, F, ##__VA_ARGS__) +#define LOG_perror(L,X) LOGF_perror(L, "%s", (X)) + #define FATALF(F,...) do { LOGF(LOG_LEVEL_FATAL, F, ##__VA_ARGS__); exit(-1); } while (1) #define FATAL(X) FATALF("%s", (X)) -#define FATAL_perror(X) FATALF("%s: %s [errno=%d]", (X), strerror(errno), errno) +#define FATALF_perror(F,...) do { LOGF_perror(LOG_LEVEL_FATAL, F, ##__VA_ARGS__); exit(-1); } while (1) +#define FATAL_perror(X) FATALF_perror("%s", (X)) #define WHYF(F,...) (LOGF(LOG_LEVEL_ERROR, F, ##__VA_ARGS__), -1) #define WHY(X) WHYF("%s", (X)) #define WHYNULL(X) (LOGF(LOG_LEVEL_ERROR, "%s", X), NULL) -#define WHYF_perror(F,...) WHYF(F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno) -#define WHY_perror(X) WHYF("%s: %s [errno=%d]", (X), strerror(errno), errno) +#define WHYF_perror(F,...) (LOGF_perror(LOG_LEVEL_ERROR, F, ##__VA_ARGS__), -1) +#define WHY_perror(X) WHYF_perror("%s", (X)) #define WARNF(F,...) LOGF(LOG_LEVEL_WARN, F, ##__VA_ARGS__) #define WARN(X) WARNF("%s", (X)) -#define WARN_perror(X) WARNF("%s: %s [errno=%d]", (X), strerror(errno), errno) +#define WARNF_perror(F,...) LOGF_perror(LOG_LEVEL_WARN, F, ##__VA_ARGS__) +#define WARN_perror(X) WARNF_perror("%s", (X)) #define INFOF(F,...) LOGF(LOG_LEVEL_INFO, F, ##__VA_ARGS__) #define INFO(X) INFOF("%s", (X)) #define DEBUGF(F,...) LOGF(LOG_LEVEL_DEBUG, F, ##__VA_ARGS__) #define DEBUG(X) DEBUGF("%s", (X)) -#define DEBUGF_perror(F,...) DEBUGF(F ": %s [errno=%d]", ##__VA_ARGS__, strerror(errno), errno) -#define DEBUG_perror(X) DEBUGF("%s: %s [errno=%d]", (X), strerror(errno), errno) -#define D DEBUG("D") +#define DEBUGF_perror(F,...) LOGF_perror(LOG_LEVEL_DEBUG, F, ##__VA_ARGS__) +#define DEBUG_perror(X) DEBUGF_perror("%s", (X)) +#define D DEBUG("D") overlay_buffer *ob_new(int size); overlay_buffer *ob_static(unsigned char *bytes, int size); @@ -1112,7 +1124,7 @@ int overlay_saw_mdp_containing_frame(overlay_frame *f,long long now); #define DEBUG_VERBOSE_IO (1 << 3) #define DEBUG_PEERS (1 << 4) #define DEBUG_DNARESPONSES (1 << 5) -#define DEBUG_DNAREQUESTS (1 << 6) +#define DEBUG_DNAHELPER (1 << 6) #define DEBUG_SIMULATION (1 << 7) #define DEBUG_RHIZOME_RX (1 << 8) #define DEBUG_PACKETFORMATS (1 << 9) @@ -1380,6 +1392,7 @@ int overlay_mdp_reply(int sock,struct sockaddr_un *recvaddr,int recvaddrlen, int overlay_mdp_relevant_bytes(overlay_mdp_frame *mdp); int overlay_mdp_dispatch(overlay_mdp_frame *mdp,int userGeneratedFrameP, struct sockaddr_un *recvaddr,int recvaddlen); +int overlay_mdp_dnalookup_reply(const overlay_mdp_data_frame *mdpd, const unsigned char *sid, const char *uri, const char *did, const char *name); int dump_payload(overlay_frame *p,char *message); @@ -1575,11 +1588,11 @@ int stopAudio(); #define SERVER_RUNNING 4 int server_probe(int *pid); -int dna_helper_enqueue(char *did, unsigned char *requestorSid); +int dna_helper_shutdown(); +int dna_helper_enqueue(overlay_mdp_frame *mdp, const char *did, const unsigned char *requestorSid); int dna_return_resolution(overlay_mdp_frame *mdp, unsigned char *fromSid, const char *did,const char *name,const char *uri); -int parseDnaReply(unsigned char *bytes, int count, - char *sidhex, char *did, char *name, char *uri); +int parseDnaReply(const char *buf, size_t len, char *token, char *did, char *name, char *uri, const char **bufp); extern int sigPipeFlag; extern int sigIoFlag; void sigPipeHandler(int signal); @@ -1599,12 +1612,12 @@ void sigIoHandler(int signal); int _set_nonblock(int fd, const char *file, unsigned int line, const char *function); int _set_block(int fd, const char *file, unsigned int line, const char *function); -int _read_nonblock(int fd, void *buf, size_t len, const char *file, unsigned int line, const char *function); -int _write_all(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function); -int _write_nonblock(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function); -int _write_all_nonblock(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function); -int _write_str(int fd, const char *str, const char *file, unsigned int line, const char *function); -int _write_str_nonblock(int fd, const char *str, const char *file, unsigned int line, const char *function); +ssize_t _read_nonblock(int fd, void *buf, size_t len, const char *file, unsigned int line, const char *function); +ssize_t _write_all(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function); +ssize_t _write_nonblock(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function); +ssize_t _write_all_nonblock(int fd, const void *buf, size_t len, const char *file, unsigned int line, const char *function); +ssize_t _write_str(int fd, const char *str, const char *file, unsigned int line, const char *function); +ssize_t _write_str_nonblock(int fd, const char *str, const char *file, unsigned int line, const char *function); int rhizome_http_server_start(); int overlay_mdp_setup_sockets(); diff --git a/server.c b/server.c index 8f7b4cb4..89b3778d 100644 --- a/server.c +++ b/server.c @@ -309,6 +309,7 @@ void serverCleanUp() } else { overlay_mdp_client_done(); } + dna_helper_shutdown(); } static void signame(char *buf, size_t len, int signal) @@ -486,7 +487,7 @@ int processRequest(unsigned char *packet,int len, while(pofscurrent; + sb->current += len * 2; + if (sb->start) { + char *e = sb->current < sb->end ? sb->current : sb->end; + // The following loop could overwrite the '\0' at *sp->end. + for (; p < e; ++data) { + *p++ = hexdigit[*data >> 4]; + *p++ = hexdigit[*data & 0xf]; + } + // This will restore the '\0' at *sp->end if it was overwritten. + *e = '\0'; + } + return sb; +} + strbuf strbuf_putc(strbuf sb, char ch) { if (sb->start && sb->current < sb->end) { diff --git a/strbuf.h b/strbuf.h index 5546aab0..10a08c7f 100644 --- a/strbuf.h +++ b/strbuf.h @@ -254,6 +254,23 @@ strbuf strbuf_ncat(strbuf sb, const char *text, size_t len); strbuf strbuf_puts(strbuf sb, const char *text); +/** Append binary data strbuf, in uppercase hexadecimal format, truncating if + * necessary to avoid buffer overrun. Return a pointer to the strbuf. + * + * After these operations: + * n = strbuf_len(sb); + * c = strbuf_count(sb); + * strbuf_tohex(data, len); + * the following invariants hold: + * strbuf_count(sb) == c + len * 2 + * strbuf_len(sb) >= n + * strbuf_len(sb) <= n + len * 2 + * + * @author Andrew Bettison + */ +strbuf strbuf_tohex(strbuf sb, const unsigned char *data, size_t len); + + /** Append a single character to the strbuf if there is space, and place a * terminating nul after it. Return a pointer to the strbuf so that * concatenations can be chained in a single line. diff --git a/strbuf_helpers.c b/strbuf_helpers.c index aa5572ed..0d7d3d08 100644 --- a/strbuf_helpers.c +++ b/strbuf_helpers.c @@ -20,6 +20,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include "strbuf_helpers.h" #include #include +#include +#include strbuf strbuf_append_poll_events(strbuf sb, short events) { @@ -88,3 +90,22 @@ strbuf strbuf_append_shell_quotemeta(strbuf sb, const char *word) } return sb; } + +strbuf strbuf_append_exit_status(strbuf sb, int status) +{ + if (WIFEXITED(status)) + strbuf_sprintf(sb, "exited normally with status %u", WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) { + strbuf_sprintf(sb, "terminated by signal %u (%s)", WTERMSIG(status), strsignal(WTERMSIG(status))); +#ifdef WCOREDUMP + if (WCOREDUMP(status)) + strbuf_puts(sb, " and dumped core"); +#endif + } else if (WIFSTOPPED(status)) + strbuf_sprintf(sb, "stopped by signal %u (%s)", WSTOPSIG(status), strsignal(WSTOPSIG(status))); +#ifdef WIFCONTINUED + else if (WIFCONTINUED(status)) + strbuf_sprintf(sb, "continued by signal %u (SIGCONT)", SIGCONT); +#endif + return sb; +} diff --git a/strbuf_helpers.h b/strbuf_helpers.h index 53aefdb3..05ff4e99 100644 --- a/strbuf_helpers.h +++ b/strbuf_helpers.h @@ -32,4 +32,10 @@ strbuf strbuf_append_poll_events(strbuf sb, short events); */ strbuf strbuf_append_shell_quotemeta(strbuf sb, const char *word); +/* Append a textual description of a process exit status as produced by wait(2) + * and waitpid(2). + * @author Andrew Bettison + */ +strbuf strbuf_append_exit_status(strbuf sb, int status); + #endif //__STRBUF_HELPERS_H__ diff --git a/testframework.sh b/testframework.sh index 0b8ee0ec..052a7faf 100644 --- a/testframework.sh +++ b/testframework.sh @@ -941,7 +941,7 @@ _tfw_assert_stdxxx_is() { '') ln -f "$_tfw_tmp/$qual" "$_tfw_tmp/content";; *) sed -n -e "${_tfw_opt_line}p" "$_tfw_tmp/$qual" >"$_tfw_tmp/content";; esac - local message="${_tfw_message:-${_tfw_opt_line:+line $_tfw_opt_line of }$qual of ($executed) is $*}" + local message="${_tfw_message:-${_tfw_opt_line:+line $_tfw_opt_line of }$qual of ($executed) is $(_tfw_shellarg "$@")}" echo -n "$@" >$_tfw_tmp/stdxxx_is.tmp if ! cmp --quiet $_tfw_tmp/stdxxx_is.tmp "$_tfw_tmp/content"; then _tfw_failmsg "assertion failed: $message" diff --git a/tests/dnahelper b/tests/dnahelper index 17f667a8..a50823a3 100755 --- a/tests/dnahelper +++ b/tests/dnahelper @@ -25,6 +25,7 @@ setup() { setup_servald assert_no_servald_processes setup_dnahelper + start_servald_instances +A } teardown() { @@ -36,69 +37,366 @@ teardown() { # Called by start_servald_instances immediately before starting the server # process in each instance. configure_servald_server() { - executeOk_servald config set dna.helper "$TFWTMP/dnahelper" + executeOk_servald config set log.show_pid on + executeOk_servald config set log.show_time on + executeOk_servald config set debug.dnahelper on + executeOk_servald config set dna.helper.executable "$dnahelper" } setup_dnahelper() { - cat >"$TFWTMP/dnahelper" <"$dnahelper" <<'EOF' +#!/bin/sh +echo STARTED +while read line +do + token="${line%%|*}" + line="${line#*|}" + did="${line%%|*}" + line="${line#*|}" + case "$token|$did|$line" in + '|'*'|') + echo "empty token" >&2 + ;; + *'||') + echo "empty DID" >&2 + ;; + *'|00000|') + # For verification during setup + echo "$token|A|$did|B|" + ;; + *'|00001|') + # One valid reply + echo "$token|sip://$SID_JOE_A@10.1.1.1|$did|Joe A. Bloggs|" + ;; + *'|00002|') + # Two valid replies + echo "$token|sip://$SID_JOE_A@10.1.1.1|$did|Joe A. Bloggs|" + sleep 0.1 + echo "$token|sip://$SID_JOE_B@10.1.1.1|$did|Joe B. Bloggs|" + sleep 0.1 + ;; + *'|00003|') + # Three valid replies + echo "$token|sip://$SID_JOE_A@10.1.1.1|$did|Joe A. Bloggs|" + sleep 0.1 + echo "$token|sip://$SID_JOE_B@10.1.1.1|$did|Joe B. Bloggs|" + sleep 0.1 + echo "$token|sip://$SID_JOE_C@10.1.1.1|$did|Joe C. Bloggs|" + sleep 0.1 + ;; + *'|00004|') + # Empty URI + echo "$token||$did|Eccles|" + ;; + *'|000051|') + # Malformed URI + echo "$token|Bluebottle|$did|Eccles|" + ;; + *'|000052|') + # Malformed URI + echo "$token|sip://Sea goon|$did|Eccles|" + ;; + *'|000053|') + # Malformed URI + echo "$token|sip:|$did|Eccles|" + ;; + *'|000061|') + # Mismatched token + echo "$SID_ECCLES|did://$SID_ECCLES/$did|$did|Eccles|" + ;; + *'|000062|') + # Empty token + echo "|did://$SID_ECCLES/$did|$did|Eccles|" + ;; + *'|000063|') + # Invalid token (not a SID) + echo "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEX|did://$SID_ECCLES/$did|$did|Eccles|" + ;; + *'|000064|') + # Long token + echo "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF0|did://$SID_ECCLES/$did|$did|Eccles|" + ;; + *'|000065|') + # Short token + echo "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDE|did://$SID_ECCLES/$did|$did|Eccles|" + ;; + *'|000071|') + # Mismatched DID + echo "$token|sip://$SID_ECCLES/$did|99999|Eccles|" + ;; + *'|000072|') + # Empty DID + echo "$token|sip://$SID_ECCLES/$did||Eccles|" + ;; + *'|000073|') + # Invalid DID + echo "$token|sip://$SID_ECCLES/$did|9999X|Eccles|" + ;; + *'|000074|') + # Long DID + echo "$token|sip://$SID_ECCLES/$did|123456789012345678901234567890123|Eccles|" + ;; + *'|000075|') + # Short DID + echo "$token|sip://$SID_ECCLES/$did|9999|Eccles|" + ;; + *'|000081|') + # Malformed reply, missing final delimiter + echo "$token|sip://$SID_ECCLES/$did|9999|Eccles" + ;; + *'|000082|') + # Malformed reply, long name + echo "$token|sip://$SID_ECCLES/$did|9999|Abcd efgh ijkl mnop qrst uvwx yzab cdef ghij klmn opqr stuv wxyz abcd efgh ijkl|" + ;; + *'|000083|') + # Malformed reply, empty line + echo + ;; + *'|000084|') + # Malformed reply, missing \n (which swallows the following DONE line) + echo -n "$token|sip://$SID_JOE_A@10.1.1.1|$did|Joe A. Bloggs|" + ;; + *'|000085|') + # Malformed reply, line too long + for i in 1 2 3 4 5 6 7 8 9 0; do + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + echo -n 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890 + done + echo + ;; + *'|00009|') + # Take too long to respond + sleep 2 + echo "$token|sip://$SID_JOE_D@10.1.1.1|$did|Joe D. Bloggs|" + ;; + *'|00010|') + # Spurious output after DONE + echo "$token|sip://$SID_JOE_E@10.1.1.1|$did|Joe E. Bloggs|" + echo DONE + echo "$token|sip://$SID_JOE_F@10.1.1.1|$did|Joe F. Bloggs|" + ;; + *'|00011|') + # Die unexpectedly + echo "goodbye cruel world" >&2 + exit 42 + ;; + *'|'*'|') + echo "token=$token did=$did line=$line" >&2 + ;; + *) + echo "garbage line" >&2 + ;; + esac + echo DONE +done EOF + chmod 0755 "$dnahelper" + executeOk "$dnahelper" <