From b9faf54c9130bbd6d8c0b97f6cd7ea666c8b012d Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Mon, 28 Oct 2013 22:27:27 +1030 Subject: [PATCH 1/7] Support more HTTP multipart inner headers Now Content-Length and Content-Type are parsed as well as Content-Disposition --- http_server.c | 303 ++++++++++++++++++++++++++---------------- http_server.h | 61 ++++++--- rhizome_direct_http.c | 26 ++-- strbuf_helpers.c | 78 +++++++---- strbuf_helpers.h | 15 ++- 5 files changed, 317 insertions(+), 166 deletions(-) diff --git a/http_server.c b/http_server.c index 87645cd8..00890b97 100644 --- a/http_server.c +++ b/http_server.c @@ -281,11 +281,13 @@ static const char * _reserve(struct http_request *r, struct substring str) return ret; } +#if 0 static const char * _reserve_str(struct http_request *r, const char *str) { struct substring sub = { .start = str, .end = str + strlen(str) }; return _reserve(r, sub); } +#endif static inline int _end_of_content(struct http_request *r) { @@ -556,6 +558,60 @@ static unsigned _parse_ranges(struct http_request *r, struct http_range *range, return i; } +static int _parse_content_type(struct http_request *r, struct mime_content_type *ct) +{ + size_t n = _parse_token(r, ct->type, sizeof ct->type); + if (n == 0) + return 0; + if (n >= sizeof ct->type) { + WARNF("HTTP Content-Type type truncated: %s", alloca_str_toprint(ct->type)); + return 0; + } + if (!_skip_literal(r, "/")) + return 0; + n = _parse_token(r, ct->subtype, sizeof ct->subtype); + if (n == 0) + return 0; + if (n >= sizeof ct->subtype) { + WARNF("HTTP Content-Type subtype truncated: %s", alloca_str_toprint(ct->subtype)); + return 0; + } + while (_skip_optional_space(r) && _skip_literal(r, ";") && _skip_optional_space(r)) { + const char *start = r->cursor; + if (_skip_literal(r, "charset=")) { + size_t n = _parse_token_or_quoted_string(r, ct->charset, sizeof ct->charset); + if (n == 0) + return 0; + if (n >= sizeof ct->charset) { + WARNF("HTTP Content-Type charset truncated: %s", alloca_str_toprint(ct->charset)); + return 0; + } + continue; + } + r->cursor = start; + if (_skip_literal(r, "boundary=")) { + size_t n = _parse_token_or_quoted_string(r, ct->multipart_boundary, sizeof ct->multipart_boundary); + if (n == 0) + return 0; + if (n >= sizeof ct->multipart_boundary) { + WARNF("HTTP Content-Type boundary truncated: %s", alloca_str_toprint(ct->multipart_boundary)); + return 0; + } + continue; + } + r->cursor = start; + struct substring param; + if (_skip_token(r, ¶m) && _skip_literal(r, "=") && _parse_token_or_quoted_string(r, NULL, 0)) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping HTTP Content-Type parameter: %s", alloca_substring_toprint(param)); + continue; + } + WARNF("Malformed HTTP Content-Type: %s", alloca_toprint(50, r->cursor, r->end - r->cursor)); + return 0; + } + return 1; +} + static int _parse_quoted_rfc822_time(struct http_request *r, time_t *timep) { char datestr[40]; @@ -743,6 +799,13 @@ static int http_request_parse_header(struct http_request *r) _rewind(r); const char *const sol = r->cursor; if (_skip_literal_nocase(r, "Content-Length:")) { + if (r->request_header.content_length != CONTENT_LENGTH_UNKNOWN) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP header Content-Length: %s", alloca_toprint(50, sol, r->end - sol)); + r->cursor = nextline; + _commit(r); + return 0; + } _skip_optional_space(r); http_size_t length; if (_parse_http_size_t(r, &length) && _skip_optional_space(r) && r->cursor == eol) { @@ -757,50 +820,35 @@ static int http_request_parse_header(struct http_request *r) } _rewind(r); if (_skip_literal_nocase(r, "Content-Type:")) { + if (r->request_header.content_type.type[0]) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP header Content-Type: %s", alloca_toprint(50, sol, r->end - sol)); + r->cursor = nextline; + _commit(r); + return 0; + } _skip_optional_space(r); - struct substring type = substring_NULL; - struct substring subtype = substring_NULL; - char boundary[BOUNDARY_STRING_MAXLEN + 1]; - boundary[0] = '\0'; - if (_skip_token(r, &type) && _skip_literal(r, "/") && _skip_token(r, &subtype)) { - // Parse zero or more content-type parameters. - for (_skip_optional_space(r); r->cursor < eol && _skip_literal(r, ";"); _skip_optional_space(r)) { - _skip_optional_space(r); - const char *startparam = r->cursor; - if (_skip_literal(r, "boundary=")) { - size_t n = _parse_token_or_quoted_string(r, boundary, sizeof boundary); - if (n == 0 || n >= sizeof boundary || !is_valid_http_boundary_string(boundary)) - goto malformed; - continue; - } - // Silently ignore unrecognised parameters (eg, charset=) if they are well formed. - r->cursor = startparam; // partial rewind - if (_skip_token(r, NULL) && _skip_literal(r, "=") && _parse_token_or_quoted_string(r, NULL, 0)) - continue; - break; - } - if (r->cursor == eol) { - r->cursor = nextline; - _commit(r); - if ( (r->request_header.content_type = _reserve(r, type)) == NULL - || (r->request_header.content_subtype = _reserve(r, subtype)) == NULL - || (boundary[0] && (r->request_header.boundary = _reserve_str(r, boundary)) == NULL) - ) - return 0; // error - if (r->debug_flag && *r->debug_flag) - DEBUGF("Parsed HTTP request Content-type: %s/%s%s%s", - r->request_header.content_type, - r->request_header.content_subtype, - r->request_header.boundary ? "; boundary=" : "", - r->request_header.boundary ? alloca_str_toprint(r->request_header.boundary) : "" - ); - return 0; - } + if ( _parse_content_type(r, &r->request_header.content_type) + && _skip_optional_space(r) + && r->cursor == eol + ) { + r->cursor = nextline; + _commit(r); + if (r->debug_flag && *r->debug_flag) + DEBUGF("Parsed HTTP request Content-type: %s", alloca_mime_content_type(&r->request_header.content_type)); + return 0; } goto malformed; } _rewind(r); if (_skip_literal_nocase(r, "Range:")) { + if (r->request_header.content_range_count) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP header Range: %s", alloca_toprint(50, sol, r->end - sol)); + r->cursor = nextline; + _commit(r); + return 0; + } _skip_optional_space(r); unsigned int n; if ( _skip_literal(r, "bytes=") @@ -858,7 +906,7 @@ static int http_request_start_body(struct http_request *r) DEBUGF("Malformed HTTP %s request: non-zero Content-Length not allowed", r->verb); return 400; } - if (r->request_header.content_type) { + if (r->request_header.content_type.type) { if (r->debug_flag && *r->debug_flag) DEBUGF("Malformed HTTP %s request: Content-Type not allowed", r->verb); return 400; @@ -871,26 +919,28 @@ static int http_request_start_body(struct http_request *r) DEBUGF("Malformed HTTP %s request: missing Content-Length header", r->verb); return 411; } - if (r->request_header.content_type == NULL) { + if (r->request_header.content_type.type == NULL) { if (r->debug_flag && *r->debug_flag) DEBUGF("Malformed HTTP %s request: missing Content-Type header", r->verb); return 400; } - if ( strcmp(r->request_header.content_type, "multipart") == 0 - && strcmp(r->request_header.content_subtype, "form-data") == 0 + if ( strcmp(r->request_header.content_type.type, "multipart") == 0 + && strcmp(r->request_header.content_type.subtype, "form-data") == 0 ) { - if (r->request_header.boundary == NULL || r->request_header.boundary[0] == '\0') { + if ( r->request_header.content_type.multipart_boundary == NULL + || r->request_header.content_type.multipart_boundary[0] == '\0' + ) { if (r->debug_flag && *r->debug_flag) DEBUGF("Malformed HTTP %s request: Content-Type %s/%s missing boundary parameter", - r->verb, r->request_header.content_type, r->request_header.content_subtype); + r->verb, r->request_header.content_type.type, r->request_header.content_type.subtype); return 400; } r->parser = http_request_parse_body_form_data; r->form_data_state = START; } else { if (r->debug_flag && *r->debug_flag) - DEBUGF("Unsupported HTTP %s request: Content-Type %s/%s not supported", - r->verb, r->request_header.content_type, r->request_header.content_subtype); + DEBUGF("Unsupported HTTP %s request: Content-Type %s not supported", + r->verb, alloca_mime_content_type(&r->request_header.content_type)); return 415; } } @@ -909,7 +959,7 @@ static int http_request_start_body(struct http_request *r) */ static int _skip_mime_boundary(struct http_request *r) { - if (!_skip_literal(r, "--") || !_skip_literal(r, r->request_header.boundary)) + if (!_skip_literal(r, "--") || !_skip_literal(r, r->request_header.content_type.multipart_boundary)) return 0; if (_skip_literal(r, "--") && _skip_crlf(r)) return 2; @@ -988,6 +1038,43 @@ malformed: return 1; } +static void http_request_form_data_start_part(struct http_request *r, int b) +{ + switch (r->form_data_state) { + case BODY: + if ( r->part_header.content_length != CONTENT_LENGTH_UNKNOWN + && r->part_body_length != r->part_header.content_length + ) { + WARNF("HTTP multipart part body length (%"PRIhttp_size_t") does not match Content-Length header (%"PRIhttp_size_t")", + r->part_body_length, + r->part_header.content_length + ); + } + // fall through... + case HEADER: + if (r->form_data.handle_mime_part_end) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("handle_mime_part_end()"); + r->form_data.handle_mime_part_end(r); + } + break; + default: + break; + } + if (b == 1) { + r->form_data_state = HEADER; + bzero(&r->part_header, sizeof r->part_header); + r->part_body_length = 0; + r->part_header.content_length = CONTENT_LENGTH_UNKNOWN; + if (r->form_data.handle_mime_part_start) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("handle_mime_part_start()"); + r->form_data.handle_mime_part_start(r); + } + } else + r->form_data_state = EPILOGUE; +} + /* If parsing completes (ie, parsed to end of epilogue), then sets r->parser to NULL and returns 0, * so this function will not be called again. If parsing cannot complete due to running out of * data, returns 100, so this function will not be called again until more data has been read. @@ -1029,15 +1116,7 @@ static int http_request_parse_body_form_data(struct http_request *r) } _rewind_crlf(r); _commit(r); - if (b == 1) { - r->form_data_state = HEADER; - if (r->form_data.handle_mime_part_start) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_part_start()"); - r->form_data.handle_mime_part_start(r); - } - } else - r->form_data_state = EPILOGUE; + http_request_form_data_start_part(r, b); return 0; } } @@ -1074,6 +1153,16 @@ static int http_request_parse_body_form_data(struct http_request *r) // A blank line finishes the headers. The CRLF does not form part of the body. if (_skip_crlf(r)) { _commit(r); + if (r->form_data.handle_mime_part_header) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("handle_mime_part_header(Content-Length: %"PRIhttp_size_t", Content-Type: %s, Content-Disposition: %s)", + r->part_header.content_length, + alloca_mime_content_type(&r->part_header.content_type), + alloca_mime_content_disposition(&r->part_header.content_disposition) + ); + r->form_data.handle_mime_part_header(r, &r->part_header); + } + r->form_data_state = BODY; return 0; } @@ -1086,23 +1175,9 @@ static int http_request_parse_body_form_data(struct http_request *r) if ((b = _skip_mime_boundary(r))) { _rewind_crlf(r); _commit(r); - if (r->form_data.handle_mime_part_end) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_part_end()"); - r->form_data.handle_mime_part_end(r); - } // A boundary in the middle of headers finishes the current part and starts a new part. // An end boundary terminates the current part and starts the epilogue. - if (b == 1) { - r->form_data_state = HEADER; - if (r->form_data.handle_mime_part_start) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_part_start()"); - r->form_data.handle_mime_part_start(r); - } - } - else - r->form_data_state = EPILOGUE; + http_request_form_data_start_part(r, b); return 0; } if (_run_out(r)) @@ -1115,27 +1190,54 @@ static int http_request_parse_body_form_data(struct http_request *r) strncpy(labelstr, label.start, labellen)[labellen] = '\0'; str_tolower_inplace(labelstr); const char *value = r->cursor; - if (strcmp(labelstr, "content-disposition") == 0) { - struct mime_content_disposition cd; - bzero(&cd, sizeof cd); - if (_parse_content_disposition(r, &cd) && _skip_optional_space(r) && _skip_crlf(r)) { + if (strcmp(labelstr, "content-length") == 0) { + if (r->part_header.content_length != CONTENT_LENGTH_UNKNOWN) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP multipart header Content-Length: %s", alloca_toprint(50, sol, r->end - sol)); + return 400; + } + http_size_t length; + if (_parse_http_size_t(r, &length) && _skip_optional_space(r) && _skip_crlf(r)) { _rewind_crlf(r); _commit(r); - if (r->form_data.handle_mime_content_disposition) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_content_disposition(%s)", alloca_mime_content_disposition(&cd)); - r->form_data.handle_mime_content_disposition(r, &cd); - } + r->part_header.content_length = length; + if (r->debug_flag && *r->debug_flag) + DEBUGF("Parsed HTTP multipart header Content-Length: %"PRIhttp_size_t, r->part_header.content_length); + return 0; + } + } + else if (strcmp(labelstr, "content-type") == 0) { + if (r->part_header.content_type.type[0]) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP multipart header Content-Type: %s", alloca_toprint(50, sol, r->end - sol)); + return 400; + } + if (_parse_content_type(r, &r->part_header.content_type) && _skip_optional_space(r) && _skip_crlf(r)) { + _rewind_crlf(r); + _commit(r); + if (r->debug_flag && *r->debug_flag) + DEBUGF("Parsed HTTP multipart header Content-Type: %s", alloca_mime_content_type(&r->part_header.content_type)); + return 0; + } + } + else if (strcmp(labelstr, "content-disposition") == 0) { + if (r->part_header.content_disposition.type[0]) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP multipart header Content-Disposition: %s", alloca_toprint(50, sol, r->end - sol)); + return 400; + } + if (_parse_content_disposition(r, &r->part_header.content_disposition) && _skip_optional_space(r) && _skip_crlf(r)) { + _rewind_crlf(r); + _commit(r); + if (r->debug_flag && *r->debug_flag) + DEBUGF("Parsed HTTP multipart header Content-Disposition: %s", alloca_mime_content_disposition(&r->part_header.content_disposition)); return 0; } } else if (_skip_to_crlf(r)) { _commit(r); - if (r->form_data.handle_mime_header) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_header(%s, %s)", alloca_str_toprint(labelstr), alloca_toprint(-1, value, value - r->cursor)); - r->form_data.handle_mime_header(r, labelstr, value, value - r->cursor); // excluding CRLF at end - } + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skip HTTP multipart header: %s: %s", alloca_str_toprint(labelstr), alloca_toprint(-1, value, value - r->cursor)); return 0; } } @@ -1169,25 +1271,14 @@ static int http_request_parse_body_form_data(struct http_request *r) if ((b = _skip_mime_boundary(r))) { _rewind_crlf(r); _commit(r); + assert(end_body >= start); + r->part_body_length += end_body - start; if (end_body > start && r->form_data.handle_mime_body) { if (r->debug_flag && *r->debug_flag) DEBUGF("handle_mime_body(%s length=%zu)", alloca_toprint(80, start, end_body - start), end_body - start); r->form_data.handle_mime_body(r, start, end_body - start); // excluding CRLF at end } - if (r->form_data.handle_mime_part_end) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_part_end()"); - r->form_data.handle_mime_part_end(r); - } - r->form_data_state = EPILOGUE; - if (b == 1) { - r->form_data_state = HEADER; - if (r->form_data.handle_mime_part_start) { - if (r->debug_flag && *r->debug_flag) - DEBUGF("handle_mime_part_start()"); - r->form_data.handle_mime_part_start(r); - } - } + http_request_form_data_start_part(r, b); return 0; } } @@ -1198,6 +1289,8 @@ static int http_request_parse_body_form_data(struct http_request *r) } _rewind_optional_cr(r); _commit(r); + assert(r->parsed >= start); + r->part_body_length += r->parsed - start; if (r->parsed > start && r->form_data.handle_mime_body) { if (r->debug_flag && *r->debug_flag) DEBUGF("handle_mime_body(%s length=%zu)", alloca_toprint(80, start, r->parsed - start), r->parsed - start); @@ -1538,18 +1631,6 @@ static const char *httpResultString(int response_code) } } -static strbuf strbuf_append_quoted_string(strbuf sb, const char *str) -{ - strbuf_putc(sb, '"'); - for (; *str; ++str) { - if (*str == '"' || *str == '\\') - strbuf_putc(sb, '\\'); - strbuf_putc(sb, *str); - } - strbuf_putc(sb, '"'); - return sb; -} - /* Render the HTTP response into the current response buffer. Return 1 if it fits, 0 if it does * not. The buffer response_pointer may be NULL, in which case no response is rendered, but the * content_length is still computed diff --git a/http_server.h b/http_server.h index a647c30e..be6d43c1 100644 --- a/http_server.h +++ b/http_server.h @@ -56,11 +56,16 @@ http_size_t http_range_bytes(const struct http_range *range, unsigned nranges); #define CONTENT_LENGTH_UNKNOWN UINT64_MAX +struct mime_content_type { + char type[64]; + char subtype[64]; + char multipart_boundary[71]; + char charset[31]; +}; + struct http_request_headers { http_size_t content_length; - const char *content_type; - const char *content_subtype; - const char *boundary; + struct mime_content_type content_type; unsigned short content_range_count; struct http_range content_ranges[5]; }; @@ -94,11 +99,16 @@ struct mime_content_disposition { time_t read_date; }; +struct mime_part_headers { + http_size_t content_length; + struct mime_content_type content_type; + struct mime_content_disposition content_disposition; +}; + struct http_mime_handler { void (*handle_mime_preamble)(struct http_request *, const char *, size_t); void (*handle_mime_part_start)(struct http_request *); - void (*handle_mime_content_disposition)(struct http_request *, const struct mime_content_disposition *); - void (*handle_mime_header)(struct http_request *, const char *label, const char *, size_t); + void (*handle_mime_part_header)(struct http_request *, const struct mime_part_headers *); void (*handle_mime_body)(struct http_request *, const char *, size_t); void (*handle_mime_part_end)(struct http_request *); void (*handle_mime_epilogue)(struct http_request *, const char *, size_t); @@ -118,31 +128,49 @@ typedef int (*HTTP_REQUEST_PARSER)(struct http_request *); struct http_request { struct sched_ent alarm; // MUST BE FIRST ELEMENT + // The following control the lifetime of this struct. enum http_request_phase { RECEIVE, TRANSMIT, DONE } phase; - bool_t *debug_flag; - bool_t *disable_tx_flag; - time_ms_t initiate_time; // time connection was initiated - time_ms_t idle_timeout; // disconnect if no bytes received for this long - struct sockaddr_in client_sockaddr_in; - HTTP_REQUEST_PARSER parser; // current parser function - HTTP_REQUEST_PARSER handle_first_line; // called after first line is parsed - HTTP_REQUEST_PARSER handle_headers; // called after all headers are parsed - HTTP_REQUEST_PARSER handle_content_end; // called after all content is received - enum mime_state { START, PREAMBLE, HEADER, BODY, EPILOGUE } form_data_state; - struct http_mime_handler form_data; // called to parse multipart/form-data body void (*finalise)(struct http_request *); void (*free)(void*); + // These can be set up to point to config flags, to allow debug to be + // enabled indpendently for different instances HTTP server instances + // that use this code. + bool_t *debug_flag; + bool_t *disable_tx_flag; + // The following are used for parsing the HTTP request. + time_ms_t initiate_time; // time connection was initiated + time_ms_t idle_timeout; // disconnect if no bytes received for this long + struct sockaddr_in client_sockaddr_in; // caller may supply this + // The parsed HTTP request is accumulated into the following fields. const char *verb; // points to nul terminated static string, "GET", "PUT", etc. const char *path; // points into buffer; nul terminated uint8_t version_major; // m from from HTTP/m.n uint8_t version_minor; // n from HTTP/m.n struct http_request_headers request_header; + // Parsing is done by setting 'parser' to point to a series of parsing + // functions as the parsing state progresses. + HTTP_REQUEST_PARSER parser; // current parser function + // The caller may set these up, and they are invoked by the parser as request + // parsing reaches different stages. + HTTP_REQUEST_PARSER handle_first_line; // called after first line is parsed + HTTP_REQUEST_PARSER handle_headers; // called after all HTTP headers are parsed + HTTP_REQUEST_PARSER handle_content_end; // called after all content is received + // The following are used for managing the buffer during RECEIVE phase. const char *received; // start of received data in buffer[] const char *end; // end of received data in buffer[] const char *parsed; // start of unparsed data in buffer[] const char *cursor; // for parsing http_size_t request_content_remaining; + // The following are used for parsing a multipart body. + enum mime_state { START, PREAMBLE, HEADER, BODY, EPILOGUE } form_data_state; + struct http_mime_handler form_data; + struct mime_part_headers part_header; + http_size_t part_body_length; + // The following are used for constructing the response that will be sent in + // TRANSMIT phase. struct http_response response; + // The following are used during TRANSMIT phase to control buffering and + // sending. http_size_t response_length; // total response bytes (header + content) http_size_t response_sent; // for counting up to response_length char *response_buffer; @@ -150,6 +178,7 @@ struct http_request { size_t response_buffer_length; size_t response_buffer_sent; void (*response_free_buffer)(void*); + // This buffer is used during RECEIVE and TRANSMIT phase. char buffer[8 * 1024]; }; diff --git a/rhizome_direct_http.c b/rhizome_direct_http.c index 220fbcbd..b3bfd201 100644 --- a/rhizome_direct_http.c +++ b/rhizome_direct_http.c @@ -54,7 +54,7 @@ static void rhizome_direct_clear_temporary_files(rhizome_http_request *r) } } -int rhizome_direct_import_end(struct http_request *hr) +static int rhizome_direct_import_end(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; if (!r->received_manifest) { @@ -168,7 +168,7 @@ int rhizome_direct_enquiry_end(struct http_request *hr) return 0; } -int rhizome_direct_addfile_end(struct http_request *hr) +static int rhizome_direct_addfile_end(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; // If given a file without a manifest, we should only accept if it we are configured to do so, and @@ -271,14 +271,14 @@ int rhizome_direct_addfile_end(struct http_request *hr) } } -void rhizome_direct_process_mime_start(struct http_request *hr) +static void rhizome_direct_process_mime_start(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; assert(r->current_part == NONE); assert(r->part_fd == -1); } -void rhizome_direct_process_mime_end(struct http_request *hr) +static void rhizome_direct_process_mime_end(struct http_request *hr) { rhizome_http_request *r = (rhizome_http_request *) hr; if (r->part_fd != -1) { @@ -302,19 +302,19 @@ void rhizome_direct_process_mime_end(struct http_request *hr) r->current_part = NONE; } -void rhizome_direct_process_mime_content_disposition(struct http_request *hr, const struct mime_content_disposition *cd) +static void rhizome_direct_process_mime_part_header(struct http_request *hr, const struct mime_part_headers *h) { rhizome_http_request *r = (rhizome_http_request *) hr; - if (strcmp(cd->name, "data") == 0) { + if (strcmp(h->content_disposition.name, "data") == 0) { r->current_part = DATA; - strncpy(r->data_file_name, cd->filename, sizeof r->data_file_name)[sizeof r->data_file_name - 1] = '\0'; + strncpy(r->data_file_name, h->content_disposition.filename, sizeof r->data_file_name)[sizeof r->data_file_name - 1] = '\0'; } - else if (strcmp(cd->name, "manifest") == 0) { + else if (strcmp(h->content_disposition.name, "manifest") == 0) { r->current_part = MANIFEST; } else return; char path[512]; - if (form_temporary_file_path(r, path, cd->name) == -1) { + if (form_temporary_file_path(r, path, h->content_disposition.name) == -1) { http_request_simple_response(&r->http, 500, "Internal Error: Buffer overrun"); return; } @@ -325,7 +325,7 @@ void rhizome_direct_process_mime_content_disposition(struct http_request *hr, co } } -void rhizome_direct_process_mime_body(struct http_request *hr, const char *buf, size_t len) +static void rhizome_direct_process_mime_body(struct http_request *hr, const char *buf, size_t len) { rhizome_http_request *r = (rhizome_http_request *) hr; if (r->part_fd != -1) { @@ -344,7 +344,7 @@ int rhizome_direct_import(rhizome_http_request *r, const char *remainder) } r->http.form_data.handle_mime_part_start = rhizome_direct_process_mime_start; r->http.form_data.handle_mime_part_end = rhizome_direct_process_mime_end; - r->http.form_data.handle_mime_content_disposition = rhizome_direct_process_mime_content_disposition; + r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header; r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body; r->http.handle_content_end = rhizome_direct_import_end; r->current_part = NONE; @@ -361,7 +361,7 @@ int rhizome_direct_enquiry(rhizome_http_request *r, const char *remainder) } r->http.form_data.handle_mime_part_start = rhizome_direct_process_mime_start; r->http.form_data.handle_mime_part_end = rhizome_direct_process_mime_end; - r->http.form_data.handle_mime_content_disposition = rhizome_direct_process_mime_content_disposition; + r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header; r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body; r->http.handle_content_end = rhizome_direct_enquiry_end; r->current_part = NONE; @@ -394,7 +394,7 @@ int rhizome_direct_addfile(rhizome_http_request *r, const char *remainder) } r->http.form_data.handle_mime_part_start = rhizome_direct_process_mime_start; r->http.form_data.handle_mime_part_end = rhizome_direct_process_mime_end; - r->http.form_data.handle_mime_content_disposition = rhizome_direct_process_mime_content_disposition; + r->http.form_data.handle_mime_part_header = rhizome_direct_process_mime_part_header; r->http.form_data.handle_mime_body = rhizome_direct_process_mime_body; r->http.handle_content_end = rhizome_direct_addfile_end; r->current_part = NONE; diff --git a/strbuf_helpers.c b/strbuf_helpers.c index de8446d6..7d04446b 100644 --- a/strbuf_helpers.c +++ b/strbuf_helpers.c @@ -402,6 +402,18 @@ strbuf strbuf_append_iovec(strbuf sb, const struct iovec *iov, int iovcnt) return sb; } +strbuf strbuf_append_quoted_string(strbuf sb, const char *str) +{ + strbuf_putc(sb, '"'); + for (; *str; ++str) { + if (*str == '"' || *str == '\\') + strbuf_putc(sb, '\\'); + strbuf_putc(sb, *str); + } + strbuf_putc(sb, '"'); + return sb; +} + strbuf strbuf_append_http_ranges(strbuf sb, const struct http_range *ranges, unsigned nels) { unsigned i; @@ -427,31 +439,47 @@ strbuf strbuf_append_http_ranges(strbuf sb, const struct http_range *ranges, uns return sb; } -strbuf strbuf_append_mime_content_disposition(strbuf sb, const struct mime_content_disposition *cd) +strbuf strbuf_append_mime_content_type(strbuf sb, const struct mime_content_type *ct) { - strbuf_puts(sb, "type="); - strbuf_toprint_quoted(sb, "``", cd->type); - strbuf_puts(sb, " name="); - strbuf_toprint_quoted(sb, "``", cd->name); - strbuf_puts(sb, " filename="); - strbuf_toprint_quoted(sb, "``", cd->filename); - strbuf_puts(sb, " size="); - strbuf_sprintf(sb, "%"PRIhttp_size_t, cd->size); - struct tm tm; - strbuf_puts(sb, " creation_date="); - if (cd->creation_date) - strbuf_append_strftime(sb, "%a, %d %b %Y %T %z", gmtime_r(&cd->creation_date, &tm)); - else - strbuf_puts(sb, "0"); - strbuf_puts(sb, " modification_date="); - if (cd->modification_date) - strbuf_append_strftime(sb, "%a, %d %b %Y %T %z", gmtime_r(&cd->modification_date, &tm)); - else - strbuf_puts(sb, "0"); - strbuf_puts(sb, " read_date="); - if (cd->read_date) - strbuf_append_strftime(sb, "%a, %d %b %Y %T %z", gmtime_r(&cd->read_date, &tm)); - else - strbuf_puts(sb, "0"); + strbuf_puts(sb, ct->type); + strbuf_putc(sb, '/'); + strbuf_puts(sb, ct->subtype); + if (ct->charset) { + strbuf_puts(sb, "; charset="); + strbuf_append_quoted_string(sb, ct->charset); + } + if (ct->multipart_boundary) { + strbuf_puts(sb, "; boundary="); + strbuf_append_quoted_string(sb, ct->multipart_boundary); + } + return sb; +} + +strbuf strbuf_append_mime_content_disposition(strbuf sb, const struct mime_content_disposition *cd) +{ + strbuf_puts(sb, cd->type); + if (cd->name) { + strbuf_puts(sb, "; name="); + strbuf_append_quoted_string(sb, cd->name); + } + if (cd->filename) { + strbuf_puts(sb, "; filename="); + strbuf_append_quoted_string(sb, cd->filename); + } + if (cd->size) + strbuf_sprintf(sb, "; size=%"PRIhttp_size_t, cd->size); + struct tm tm; + if (cd->creation_date) { + strbuf_puts(sb, " creation_date="); + strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->creation_date, &tm)); + } + if (cd->modification_date) { + strbuf_puts(sb, " modification_date="); + strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->modification_date, &tm)); + } + if (cd->read_date) { + strbuf_puts(sb, " read_date="); + strbuf_append_strftime(sb, "\"%a, %d %b %Y %T %z\"", gmtime_r(&cd->read_date, &tm)); + } return sb; } diff --git a/strbuf_helpers.h b/strbuf_helpers.h index 289c7ddf..fb003dd0 100644 --- a/strbuf_helpers.h +++ b/strbuf_helpers.h @@ -145,6 +145,12 @@ struct iovec; strbuf strbuf_append_iovec(strbuf sb, const struct iovec *iov, int iovcnt); #define alloca_iovec(iov,cnt) strbuf_str(strbuf_append_iovec(strbuf_alloca(200), (iov), (cnt))) +/* Append a string using HTTP quoted-string format: delimited by double quotes (") and + * internal double quotes and backslash escaped by leading backslash. + * @author Andrew Bettison + */ +strbuf strbuf_append_quoted_string(strbuf sb, const char *str); + /* Append a representation of a struct http_range[] array. * @author Andrew Bettison */ @@ -152,7 +158,14 @@ struct http_range; strbuf strbuf_append_http_ranges(strbuf sb, const struct http_range *ranges, unsigned nels); #define alloca_http_ranges(ra) strbuf_str(strbuf_append_http_ranges(strbuf_alloca(25*NELS(ra)), (ra), NELS(ra))) -/* Append a representation of a struct mime_content_disposition struct. +/* Append a representation of a struct mime_content_type in HTTP header format. + * @author Andrew Bettison + */ +struct mime_content_type; +strbuf strbuf_append_mime_content_type(strbuf, const struct mime_content_type *); +#define alloca_mime_content_type(ct) strbuf_str(strbuf_append_mime_content_type(strbuf_alloca(500), (ct))) + +/* Append a representation of a struct mime_content_disposition, in HTTP header format. * @author Andrew Bettison */ struct mime_content_disposition; From 21fe12859fe95b5b1812dc85ef3911519ed83149 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Tue, 29 Oct 2013 17:32:04 +1030 Subject: [PATCH 2/7] Implement HTTP basic authentication Use it in /restful/rhizome/bundlelist.json -- first 'rhizomehttp' test passes --- http_server.c | 262 ++++++++++++++++++++++++++++++++++++++++------ http_server.h | 18 ++++ rhizome_http.c | 64 +++++++++-- tests/rhizomehttp | 20 +++- 4 files changed, 319 insertions(+), 45 deletions(-) diff --git a/http_server.c b/http_server.c index 00890b97..5f3f15b5 100644 --- a/http_server.c +++ b/http_server.c @@ -98,6 +98,8 @@ void http_request_init(struct http_request *r, int sockfd) assert(sockfd != -1); r->request_header.content_length = CONTENT_LENGTH_UNKNOWN; r->request_content_remaining = CONTENT_LENGTH_UNKNOWN; + r->response.header.content_length = CONTENT_LENGTH_UNKNOWN; + r->response.header.resource_length = CONTENT_LENGTH_UNKNOWN; r->alarm.stats = &http_server_stats; r->alarm.function = http_server_poll; if (r->idle_timeout == 0) @@ -162,31 +164,85 @@ void http_request_finalise(struct http_request *r) r->phase = DONE; } -#define _SEP (1 << 0) -#define _BND (1 << 1) +#define _BASE64 (1 << 6) +#define _MASK64 ((1 << 6) - 1) +#define _SEP (1 << 7) +#define _BND (1 << 8) -uint8_t http_ctype[256] = { - ['0'] = _BND, ['1'] = _BND, ['2'] = _BND, ['3'] = _BND, ['4'] = _BND, - ['5'] = _BND, ['6'] = _BND, ['7'] = _BND, ['8'] = _BND, ['9'] = _BND, - ['A'] = _BND, ['B'] = _BND, ['C'] = _BND, ['D'] = _BND, ['E'] = _BND, - ['F'] = _BND, ['G'] = _BND, ['H'] = _BND, ['I'] = _BND, ['J'] = _BND, - ['K'] = _BND, ['L'] = _BND, ['M'] = _BND, ['N'] = _BND, ['O'] = _BND, - ['P'] = _BND, ['Q'] = _BND, ['R'] = _BND, ['S'] = _BND, ['T'] = _BND, - ['U'] = _BND, ['V'] = _BND, ['W'] = _BND, ['X'] = _BND, ['Y'] = _BND, - ['Z'] = _BND, - ['a'] = _BND, ['b'] = _BND, ['c'] = _BND, ['d'] = _BND, ['e'] = _BND, - ['f'] = _BND, ['g'] = _BND, ['h'] = _BND, ['i'] = _BND, ['j'] = _BND, - ['k'] = _BND, ['l'] = _BND, ['m'] = _BND, ['n'] = _BND, ['o'] = _BND, - ['p'] = _BND, ['q'] = _BND, ['r'] = _BND, ['s'] = _BND, ['t'] = _BND, - ['u'] = _BND, ['v'] = _BND, ['w'] = _BND, ['x'] = _BND, ['y'] = _BND, - ['z'] = _BND, - ['+'] = _BND, ['-'] = _BND, ['.'] = _BND, ['/'] = _BND, [':'] = _BND, +uint16_t http_ctype[256] = { + ['A'] = _BND | _BASE64 | 0, + ['B'] = _BND | _BASE64 | 1, + ['C'] = _BND | _BASE64 | 2, + ['D'] = _BND | _BASE64 | 3, + ['E'] = _BND | _BASE64 | 4, + ['F'] = _BND | _BASE64 | 5, + ['G'] = _BND | _BASE64 | 6, + ['H'] = _BND | _BASE64 | 7, + ['I'] = _BND | _BASE64 | 8, + ['J'] = _BND | _BASE64 | 9, + ['K'] = _BND | _BASE64 | 10, + ['L'] = _BND | _BASE64 | 11, + ['M'] = _BND | _BASE64 | 12, + ['N'] = _BND | _BASE64 | 13, + ['O'] = _BND | _BASE64 | 14, + ['P'] = _BND | _BASE64 | 15, + ['Q'] = _BND | _BASE64 | 16, + ['R'] = _BND | _BASE64 | 17, + ['S'] = _BND | _BASE64 | 18, + ['T'] = _BND | _BASE64 | 19, + ['U'] = _BND | _BASE64 | 20, + ['V'] = _BND | _BASE64 | 21, + ['W'] = _BND | _BASE64 | 22, + ['X'] = _BND | _BASE64 | 23, + ['Y'] = _BND | _BASE64 | 24, + ['Z'] = _BND | _BASE64 | 25, + ['a'] = _BND | _BASE64 | 26, + ['b'] = _BND | _BASE64 | 27, + ['c'] = _BND | _BASE64 | 28, + ['d'] = _BND | _BASE64 | 29, + ['e'] = _BND | _BASE64 | 30, + ['f'] = _BND | _BASE64 | 31, + ['g'] = _BND | _BASE64 | 32, + ['h'] = _BND | _BASE64 | 33, + ['i'] = _BND | _BASE64 | 34, + ['j'] = _BND | _BASE64 | 35, + ['k'] = _BND | _BASE64 | 36, + ['l'] = _BND | _BASE64 | 37, + ['m'] = _BND | _BASE64 | 38, + ['n'] = _BND | _BASE64 | 39, + ['o'] = _BND | _BASE64 | 40, + ['p'] = _BND | _BASE64 | 41, + ['q'] = _BND | _BASE64 | 42, + ['r'] = _BND | _BASE64 | 43, + ['s'] = _BND | _BASE64 | 44, + ['t'] = _BND | _BASE64 | 45, + ['u'] = _BND | _BASE64 | 46, + ['v'] = _BND | _BASE64 | 47, + ['w'] = _BND | _BASE64 | 48, + ['x'] = _BND | _BASE64 | 49, + ['y'] = _BND | _BASE64 | 50, + ['z'] = _BND | _BASE64 | 51, + ['0'] = _BND | _BASE64 | 52, + ['1'] = _BND | _BASE64 | 53, + ['2'] = _BND | _BASE64 | 54, + ['3'] = _BND | _BASE64 | 55, + ['4'] = _BND | _BASE64 | 56, + ['5'] = _BND | _BASE64 | 57, + ['6'] = _BND | _BASE64 | 58, + ['7'] = _BND | _BASE64 | 59, + ['8'] = _BND | _BASE64 | 60, + ['9'] = _BND | _BASE64 | 61, + ['+'] = _BND | _BASE64 | 62, + ['/'] = _BND | _BASE64 | 63, + ['='] = _SEP | _BND, + ['-'] = _BND, + ['.'] = _BND, + [':'] = _BND, ['_'] = _BND, ['('] = _SEP | _BND, [')'] = _SEP | _BND, [','] = _SEP | _BND, ['?'] = _SEP | _BND, - ['='] = _SEP | _BND, [' '] = _SEP | _BND, ['\t'] = _SEP, ['<'] = _SEP, @@ -213,6 +269,21 @@ inline int is_http_ctl(char c) return iscntrl(c); } +inline int is_base64_digit(char c) +{ + return (http_ctype[(unsigned char) c] & _BASE64) != 0; +} + +inline int is_base64_pad(char c) +{ + return c == '='; +} + +inline uint8_t base64_digit(char c) +{ + return http_ctype[(unsigned char) c] & _MASK64; +} + inline int is_http_separator(char c) { return (http_ctype[(unsigned char) c] & _SEP) != 0; @@ -612,6 +683,92 @@ static int _parse_content_type(struct http_request *r, struct mime_content_type return 1; } +static size_t _parse_base64(struct http_request *r, char *bin, size_t binsize) +{ + uint8_t buf = 0; + size_t digits = 0; + size_t bytes = 0; + for (; !_run_out(r) && is_base64_digit(*r->cursor); _skip_optional_space(r), ++r->cursor) { + if (bytes < binsize) { + uint8_t d = base64_digit(*r->cursor); + switch (digits++ & 3) { + case 0: + buf = d << 2; + break; + case 1: + if (bin) + bin[bytes] = buf | (d >> 4); + ++bytes; + buf = d << 4; + break; + case 2: + if (bin) + bin[bytes] = buf | (d >> 2); + ++bytes; + buf = d << 6; + break; + case 3: + if (bin) + bin[bytes] = buf | d; + ++bytes; + break; + } + } + } + if (digits == 0) + return 0; + if (!_run_out(r) && is_base64_pad(*r->cursor)) + ++r->cursor; + if (!_run_out(r) && is_base64_pad(*r->cursor)) + ++r->cursor; + return bytes; +} + +static int _parse_authorization_credentials_basic(struct http_request *r, struct http_client_credentials_basic *cred, char *buf, size_t bufsz) +{ + size_t n = _parse_base64(r, buf, bufsz - 1); // leave room for NUL terminator on password + assert(n < bufsz); // buffer must be big enough + char *pw = (char *) strnchr(buf, n, ':'); + if (pw == NULL) + return 0; // malformed + cred->user = buf; + *pw++ = '\0'; // NUL terminate user + cred->password = pw; + buf[n] = '\0'; // NUL terminate password + return 1; +} + +static int _parse_authorization(struct http_request *r, struct http_client_authorization *auth, size_t header_bytes) +{ + const char *start = r->cursor; + if (_skip_literal(r, "Basic") && _skip_space(r)) { + size_t bufsz = 5 + header_bytes * 3 / 4; // enough for base64 decoding + char buf[bufsz]; + if (_parse_authorization_credentials_basic(r, &auth->credentials.basic, buf, bufsz) == -1) { + auth->scheme = BASIC; + return 1; + } + if (r->debug_flag && *r->debug_flag) + DEBUGF("Malformed HTTP header: Authorization: %s", alloca_toprint(50, start, header_bytes)); + return 0; + } + if (_skip_literal(r, "Digest") && _skip_space(r)) { + if (r->debug_flag && *r->debug_flag) + DEBUG("Ignoring unsupported HTTP Authorization scheme: Digest"); + r->cursor += header_bytes; + return 1; + } + struct substring scheme; + if (_skip_token(r, &scheme) && _skip_space(r)) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Unrecognised HTTP Authorization scheme: %s", alloca_toprint(-1, scheme.start, scheme.end - scheme.start)); + return 0; + } + if (r->debug_flag && *r->debug_flag) + DEBUGF("Malformed HTTP Authorization header: %s", alloca_toprint(50, r->parsed, r->end - r->parsed)); + return 0; +} + static int _parse_quoted_rfc822_time(struct http_request *r, time_t *timep) { char datestr[40]; @@ -854,7 +1011,7 @@ static int http_request_parse_header(struct http_request *r) if ( _skip_literal(r, "bytes=") && (n = _parse_ranges(r, r->request_header.content_ranges, NELS(r->request_header.content_ranges))) && _skip_optional_space(r) - && (r->cursor == eol) + && r->cursor == eol ) { r->cursor = nextline; _commit(r); @@ -874,6 +1031,26 @@ static int http_request_parse_header(struct http_request *r) goto malformed; } _rewind(r); + if (_skip_literal_nocase(r, "Authorization:")) { + if (r->request_header.authorization.scheme != NOAUTH) { + if (r->debug_flag && *r->debug_flag) + DEBUGF("Skipping duplicate HTTP header Authorization: %s", alloca_toprint(50, sol, r->end - sol)); + r->cursor = nextline; + _commit(r); + return 0; + } + _skip_optional_space(r); + if ( _parse_authorization(r, &r->request_header.authorization, eol - r->cursor) + && _skip_optional_space(r) + && r->cursor == eol + ) { + assert(r->request_header.authorization.scheme != NOAUTH); + r->cursor = nextline; + _commit(r); + } + goto malformed; + } + _rewind(r); if (r->debug_flag && *r->debug_flag) DEBUGF("Skipped HTTP request header: %s", alloca_toprint(-1, sol, eol - sol)); r->cursor = nextline; @@ -1520,6 +1697,9 @@ static void http_request_send_response(struct http_request *r) r->response_buffer_sent += (size_t) written; assert(r->response_sent <= r->response_length); assert(r->response_buffer_sent <= r->response_buffer_length); + if (r->debug_flag && *r->debug_flag) + DEBUGF("Wrote %zu bytes to HTTP socket, total %"PRIhttp_size_t", remaining=%"PRIhttp_size_t, + (size_t) written, r->response_sent, r->response_length - r->response_sent); // Reset inactivity timer. r->alarm.alarm = gettime_ms() + r->idle_timeout; r->alarm.deadline = r->alarm.alarm + r->idle_timeout; @@ -1640,24 +1820,30 @@ static const char *httpResultString(int response_code) static int _render_response(struct http_request *r) { struct http_response hr = r->response; - assert(hr.result_code != 0); - assert(hr.header.content_range_start <= hr.header.resource_length); - assert(hr.header.content_length <= hr.header.resource_length); - // To save page handlers having to decide between 200 (OK) and 206 (Partial Content), they can - // just send 200 and the content range fields, and this logic will detect if it should be 206. - if (hr.header.content_length > 0 && hr.header.content_length < hr.header.resource_length && hr.result_code == 200) - hr.result_code = 206; // Partial Content + assert(hr.result_code >= 200); + assert(hr.result_code < 600); const char *result_string = httpResultString(hr.result_code); strbuf sb = strbuf_local(r->response_buffer, r->response_buffer_size); if (hr.content == NULL && hr.content_generator == NULL) { + assert(hr.header.content_length == CONTENT_LENGTH_UNKNOWN); + assert(hr.header.resource_length == CONTENT_LENGTH_UNKNOWN); + assert(hr.header.content_range_start == 0); strbuf cb = strbuf_alloca(100 + strlen(result_string)); - strbuf_puts(cb, "

"); - strbuf_puts(cb, result_string); - strbuf_puts(cb, "

\r\n"); + strbuf_sprintf(cb, "

%03u %s

", hr.result_code, result_string); hr.content = strbuf_str(cb); hr.header.resource_length = hr.header.content_length = strbuf_len(cb); hr.header.content_type = "text/html"; hr.header.content_range_start = 0; + } else { + assert(hr.header.content_length != CONTENT_LENGTH_UNKNOWN); + assert(hr.header.resource_length != CONTENT_LENGTH_UNKNOWN); + assert(hr.header.content_length <= hr.header.resource_length); + assert(hr.header.content_range_start + hr.header.content_length <= hr.header.resource_length); + // To save page handlers having to decide between 200 (OK) and 206 (Partial Content), they can + // just set the content range fields and pass 200 to http_request_response_static(), and this + // logic will change it to 206 if appropriate. + if (hr.header.content_length > 0 && hr.header.content_length < hr.header.resource_length && hr.result_code == 200) + hr.result_code = 206; // Partial Content } assert(hr.header.content_type != NULL); assert(hr.header.content_type[0]); @@ -1684,6 +1870,16 @@ static int _render_response(struct http_request *r) ); } strbuf_sprintf(sb, "Content-Length: %"PRIhttp_size_t"\r\n", hr.header.content_length); + const char *scheme = NULL; + switch (hr.header.www_authenticate.scheme) { + case NOAUTH: break; + case BASIC: scheme = "Basic"; break; + } + if (scheme) { + strbuf_sprintf(sb, "WWW-Authenticate: %s realm=", scheme); + strbuf_append_quoted_string(sb, hr.header.www_authenticate.realm); + strbuf_puts(sb, "\r\n"); + } strbuf_puts(sb, "\r\n"); if (strbuf_overrun(sb)) return 0; @@ -1836,8 +2032,10 @@ void http_request_simple_response(struct http_request *r, uint16_t result, const r->response.result_code = result; r->response.header.content_type = "text/html"; r->response.header.content_range_start = 0; - r->response.header.resource_length = r->response.header.content_length = h ? strbuf_len(h) : 0; - r->response.content = h ? strbuf_str(h) : NULL; + if (h) { + r->response.header.resource_length = r->response.header.content_length = strbuf_len(h); + r->response.content = strbuf_str(h); + } r->response.content_generator = NULL; http_request_start_response(r); } diff --git a/http_server.h b/http_server.h index be6d43c1..20e8d439 100644 --- a/http_server.h +++ b/http_server.h @@ -63,11 +63,28 @@ struct mime_content_type { char charset[31]; }; + +struct http_client_authorization { + enum http_authorization_scheme { NOAUTH = 0, BASIC } scheme; + union { + struct http_client_credentials_basic { + const char *user; + const char *password; + } basic; + } credentials; +}; + +struct http_www_authenticate { + enum http_authorization_scheme scheme; + const char *realm; +}; + struct http_request_headers { http_size_t content_length; struct mime_content_type content_type; unsigned short content_range_count; struct http_range content_ranges[5]; + struct http_client_authorization authorization; }; struct http_response_headers { @@ -76,6 +93,7 @@ struct http_response_headers { http_size_t resource_length; // size of entire resource const char *content_type; // "type/subtype" const char *boundary; + struct http_www_authenticate www_authenticate; }; typedef int (*HTTP_CONTENT_GENERATOR)(struct http_request *); diff --git a/rhizome_http.c b/rhizome_http.c index d08180eb..391cc2b7 100644 --- a/rhizome_http.c +++ b/rhizome_http.c @@ -34,24 +34,29 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #define RHIZOME_SERVER_MAX_LIVE_REQUESTS 32 +typedef int HTTP_HANDLER(rhizome_http_request *r, const char *remainder); + struct http_handler{ const char *path; - int (*parser)(rhizome_http_request *r, const char *remainder); + HTTP_HANDLER *parser; }; -static int rhizome_status_page(rhizome_http_request *r, const char *remainder); -static int rhizome_file_page(rhizome_http_request *r, const char *remainder); -static int manifest_by_prefix_page(rhizome_http_request *r, const char *remainder); -static int interface_page(rhizome_http_request *r, const char *remainder); -static int neighbour_page(rhizome_http_request *r, const char *remainder); -static int fav_icon_header(rhizome_http_request *r, const char *remainder); -static int root_page(rhizome_http_request *r, const char *remainder); +static HTTP_HANDLER restful_rhizome_bundlelist_json; -extern int rhizome_direct_import(rhizome_http_request *r, const char *remainder); -extern int rhizome_direct_enquiry(rhizome_http_request *r, const char *remainder); -extern int rhizome_direct_dispatch(rhizome_http_request *r, const char *remainder); +static HTTP_HANDLER rhizome_status_page; +static HTTP_HANDLER rhizome_file_page; +static HTTP_HANDLER manifest_by_prefix_page; +static HTTP_HANDLER interface_page; +static HTTP_HANDLER neighbour_page; +static HTTP_HANDLER fav_icon_header; +static HTTP_HANDLER root_page; + +extern HTTP_HANDLER rhizome_direct_import; +extern HTTP_HANDLER rhizome_direct_enquiry; +extern HTTP_HANDLER rhizome_direct_dispatch; struct http_handler paths[]={ + {"/restful/rhizome/bundlelist.json", restful_rhizome_bundlelist_json}, {"/rhizome/status", rhizome_status_page}, {"/rhizome/file/", rhizome_file_page}, {"/rhizome/import", rhizome_direct_import}, @@ -312,6 +317,43 @@ int is_http_header_complete(const char *buf, size_t len, size_t read_since_last_ OUT(); } +/* Return 1 if the given authorization credentials are acceptable. + * Return 0 if not. + */ +static int is_authorized(struct http_client_authorization *auth) +{ + if (auth->scheme != BASIC) + return 0; + unsigned i; + for (i = 0; i != config.rhizome.api.restful.users.ac; ++i) { + if ( strcmp(config.rhizome.api.restful.users.av[i].key, auth->credentials.basic.user) == 0 + && strcmp(config.rhizome.api.restful.users.av[i].value.password, auth->credentials.basic.password) == 0 + ) + return 1; + } + return 0; +} + +static int restful_rhizome_bundlelist_json(rhizome_http_request *r, const char *remainder) +{ + if (!is_rhizome_http_enabled()) + return 1; + if (*remainder) + return 1; + if (r->http.verb != HTTP_VERB_GET) { + http_request_simple_response(&r->http, 405, NULL); + return 0; + } + if (!is_authorized(&r->http.request_header.authorization)) { + r->http.response.header.www_authenticate.scheme = BASIC; + r->http.response.header.www_authenticate.realm = "Serval Rhizome"; + http_request_simple_response(&r->http, 401, NULL); + return 0; + } + http_request_simple_response(&r->http, 200, NULL); + return 0; +} + static int neighbour_page(rhizome_http_request *r, const char *remainder) { if (r->http.verb != HTTP_VERB_GET) { diff --git a/tests/rhizomehttp b/tests/rhizomehttp index 8fa74c77..84ac4ad0 100755 --- a/tests/rhizomehttp +++ b/tests/rhizomehttp @@ -41,8 +41,20 @@ setup() { get_rhizome_server_port PORTA +A } +finally() { + stop_all_servald_servers +} + +teardown() { + kill_all_servald_processes + assert_no_servald_processes + report_all_servald_servers +} + set_rhizome_config() { executeOk_servald config \ + set debug.httpd on \ + set debug.rhizome_httpd on \ set debug.rhizome on \ set debug.verbose on \ set log.console.level debug @@ -50,14 +62,18 @@ set_rhizome_config() { doc_AuthBasicMissing="Basic Authentication credentials are required" test_AuthBasicMissing() { - execute --exit-status=67 curl \ - --silent --fail --show-error \ + executeOk curl \ + --silent --show-error --write-out '%{http_code}' \ --output http.output \ --dump-header http.headers \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" + assertStdoutIs '401' + CR=' ' + assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR$" } teardown_AuthBasicMissing() { tfw_cat http.headers http.output + teardown } doc_AuthBasicWrong="Basic Authentication credentials must be correct" From 2ac1bc32403ecbf3deb54073bb02d8effa4e6264 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Wed, 30 Oct 2013 11:07:45 +1030 Subject: [PATCH 3/7] Get HTTP Basic authentication working Second 'rhizomehttp' test now passes --- http_server.c | 29 ++++++++++++++++++----------- tests/rhizomehttp | 15 +++++++++++---- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/http_server.c b/http_server.c index 5f3f15b5..1e08e587 100644 --- a/http_server.c +++ b/http_server.c @@ -352,13 +352,11 @@ static const char * _reserve(struct http_request *r, struct substring str) return ret; } -#if 0 static const char * _reserve_str(struct http_request *r, const char *str) { struct substring sub = { .start = str, .end = str + strlen(str) }; return _reserve(r, sub); } -#endif static inline int _end_of_content(struct http_request *r) { @@ -744,8 +742,12 @@ static int _parse_authorization(struct http_request *r, struct http_client_autho if (_skip_literal(r, "Basic") && _skip_space(r)) { size_t bufsz = 5 + header_bytes * 3 / 4; // enough for base64 decoding char buf[bufsz]; - if (_parse_authorization_credentials_basic(r, &auth->credentials.basic, buf, bufsz) == -1) { + if (_parse_authorization_credentials_basic(r, &auth->credentials.basic, buf, bufsz)) { auth->scheme = BASIC; + if ( (auth->credentials.basic.user = _reserve_str(r, auth->credentials.basic.user)) == NULL + || (auth->credentials.basic.password = _reserve_str(r, auth->credentials.basic.password)) == NULL + ) + return 0; // error return 1; } if (r->debug_flag && *r->debug_flag) @@ -1047,6 +1049,7 @@ static int http_request_parse_header(struct http_request *r) assert(r->request_header.authorization.scheme != NOAUTH); r->cursor = nextline; _commit(r); + return 0; } goto malformed; } @@ -1820,8 +1823,10 @@ static const char *httpResultString(int response_code) static int _render_response(struct http_request *r) { struct http_response hr = r->response; - assert(hr.result_code >= 200); + assert(hr.result_code >= 100); assert(hr.result_code < 600); + if (hr.result_code == 401) + assert(hr.header.www_authenticate.scheme != NOAUTH); const char *result_string = httpResultString(hr.result_code); strbuf sb = strbuf_local(r->response_buffer, r->response_buffer_size); if (hr.content == NULL && hr.content_generator == NULL) { @@ -1876,6 +1881,7 @@ static int _render_response(struct http_request *r) case BASIC: scheme = "Basic"; break; } if (scheme) { + assert(hr.result_code == 401); strbuf_sprintf(sb, "WWW-Authenticate: %s realm=", scheme); strbuf_append_quoted_string(sb, hr.header.www_authenticate.realm); strbuf_puts(sb, "\r\n"); @@ -1933,7 +1939,6 @@ static size_t http_request_drain(struct http_request *r) static void http_request_start_response(struct http_request *r) { assert(r->phase == RECEIVE); - assert(r->response.result_code != 0); if (r->response.content || r->response.content_generator) { assert(r->response.header.content_type != NULL); assert(r->response.header.content_type[0]); @@ -1951,6 +1956,13 @@ static void http_request_start_response(struct http_request *r) http_request_drain(r); if (r->phase != RECEIVE) return; + // Ensure conformance to HTTP standards. + if (r->response.result_code == 401 && r->response.header.www_authenticate.scheme == NOAUTH) { + WHY("HTTP 401 response missing WWW-Authenticate header, sending 500 Server Error instead"); + r->response.result_code = 500; + r->response.content = NULL; + r->response.content_generator = NULL; + } // If the response cannot be rendered, then render a 500 Server Error instead. If that fails, // then just close the connection. http_request_render_response(r); @@ -1958,6 +1970,7 @@ static void http_request_start_response(struct http_request *r) WARN("Cannot render HTTP response, sending 500 Server Error instead"); r->response.result_code = 500; r->response.content = NULL; + r->response.content_generator = NULL; http_request_render_response(r); if (r->response_buffer == NULL) { WHY("Cannot render HTTP 500 Server Error response, closing connection"); @@ -1983,8 +1996,6 @@ static void http_request_start_response(struct http_request *r) void http_request_response_static(struct http_request *r, int result, const char *mime_type, const char *body, uint64_t bytes) { assert(r->phase == RECEIVE); - assert(result >= 100); - assert(result < 300); assert(mime_type != NULL); assert(mime_type[0]); r->response.result_code = result; @@ -1999,8 +2010,6 @@ void http_request_response_static(struct http_request *r, int result, const char void http_request_response_generated(struct http_request *r, int result, const char *mime_type, HTTP_CONTENT_GENERATOR generator) { assert(r->phase == RECEIVE); - assert(result >= 100); - assert(result < 300); assert(mime_type != NULL); assert(mime_type[0]); r->response.result_code = result; @@ -2021,8 +2030,6 @@ void http_request_response_generated(struct http_request *r, int result, const c void http_request_simple_response(struct http_request *r, uint16_t result, const char *body) { assert(r->phase == RECEIVE); - assert(result >= 200); - assert(result < 600); strbuf h = NULL; if (body) { size_t html_len = strlen(body) + 40; diff --git a/tests/rhizomehttp b/tests/rhizomehttp index 84ac4ad0..e436e5cc 100755 --- a/tests/rhizomehttp +++ b/tests/rhizomehttp @@ -25,6 +25,7 @@ source "${0%/*}/../testdefs_rhizome.sh" shopt -s extglob setup() { + CR=' ' setup_curl 7 setup_jq 1.2 setup_servald @@ -68,7 +69,6 @@ test_AuthBasicMissing() { --dump-header http.headers \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" assertStdoutIs '401' - CR=' ' assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR$" } teardown_AuthBasicMissing() { @@ -78,18 +78,25 @@ teardown_AuthBasicMissing() { doc_AuthBasicWrong="Basic Authentication credentials must be correct" test_AuthBasicWrong() { - execute --exit-status=67 curl \ - --silent --fail --show-error \ + executeOk curl \ + --silent --show-error --write-out '%{http_code}' \ --output http.output \ --dump-header http.headers \ --basic --user fred:nurks \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" + assertStdoutIs '401' + assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR$" executeOk curl \ - --silent --fail --show-error \ + --silent --fail --show-error --write-out '%{http_code}' \ --output http.output \ --dump-header http.headers \ --basic --user ron:weasley \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" + assertStdoutIs '200' +} +teardown_AuthBasicWrong() { + tfw_cat http.headers http.output + teardown } doc_RhizomeList="Fetch full Rhizome bundle list in JSON format" From 3aa24f74079ab32c84022a9a8a60c653959ae528 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Mon, 4 Nov 2013 23:47:09 +1030 Subject: [PATCH 4/7] Refactor "rhizome list" main loop Preparing for re-use in HTTP /restful/rhizome/bundlelist.json --- rhizome.h | 13 ++ rhizome_bundle.c | 54 +++++--- rhizome_database.c | 301 ++++++++++++++++++++++++--------------------- 3 files changed, 215 insertions(+), 153 deletions(-) diff --git a/rhizome.h b/rhizome.h index 35fbf1ad..5489a73a 100644 --- a/rhizome.h +++ b/rhizome.h @@ -242,6 +242,18 @@ typedef struct rhizome_manifest */ bool_t has_author; + /* Local authorship. Useful for dividing bundle lists between "sent" and + * "inbox" views. + */ + enum rhizome_bundle_authorship { + AUTHOR_NOT_CHECKED = 0, + AUTHOR_ERROR, // author check failed, don't try again + AUTHOR_UNKNOWN, // author is not a local identity + AUTHOR_LOCAL, // author is in keyring (unlocked) but not verified + AUTHOR_IMPOSTOR, // author is a local identity but fails verification + AUTHOR_AUTHENTIC // a local identity is the verified author + } authorship; + /* time-to-live in hops of this manifest. */ int ttl; @@ -432,6 +444,7 @@ int rhizome_clean_payload(const char *fileidhex); int rhizome_store_file(rhizome_manifest *m,const unsigned char *key); int rhizome_bundle_import_files(rhizome_manifest *m, const char *manifest_path, const char *filepath); int rhizome_fill_manifest(rhizome_manifest *m, const char *filepath, const sid_t *authorSidp, rhizome_bk_t *bsk); +int rhizome_lookup_author(rhizome_manifest *m); int rhizome_manifest_verify(rhizome_manifest *m); int rhizome_manifest_check_sanity(rhizome_manifest *m_in); diff --git a/rhizome_bundle.c b/rhizome_bundle.c index 6d7816ea..477dbf9b 100644 --- a/rhizome_bundle.c +++ b/rhizome_bundle.c @@ -735,22 +735,18 @@ void _rhizome_manifest_free(struct __sourceloc __whence, rhizome_manifest *m) if (!m) return; int mid=m->manifest_record_number; - if (m!=&manifests[mid]) { - WHYF("%s(): asked to free manifest %p, which claims to be manifest slot #%d (%p), but isn't", - __FUNCTION__, m, mid, &manifests[mid] + if (m!=&manifests[mid]) + FATALF("%s(): asked to free manifest %p, which claims to be manifest slot #%d (%p), but isn't", + __FUNCTION__, m, mid, &manifests[mid] ); - exit(-1); - } - if (manifest_free[mid]) { - WHYF("%s(): asked to free manifest slot #%d (%p), which was already freed at %s:%d:%s()", - __FUNCTION__, mid, m, - manifest_free_whence[mid].file, - manifest_free_whence[mid].line, - manifest_free_whence[mid].function - ); - exit(-1); - } + if (manifest_free[mid]) + FATALF("%s(): asked to free manifest slot #%d (%p), which was already freed at %s:%d:%s()", + __FUNCTION__, mid, m, + manifest_free_whence[mid].file, + manifest_free_whence[mid].line, + manifest_free_whence[mid].function + ); /* Free variable and signature blocks. */ unsigned i; @@ -984,3 +980,33 @@ int rhizome_fill_manifest(rhizome_manifest *m, const char *filepath, const sid_t return 0; } + +int rhizome_lookup_author(rhizome_manifest *m) +{ + switch (m->authorship) { + case AUTHOR_NOT_CHECKED: + if (m->has_author) { + int cn = 0, in = 0, kp = 0; + if (keyring_find_sid(keyring, &cn, &in, &kp, &m->author)) { + m->authorship = AUTHOR_LOCAL; + return 1; + } + } + if (m->has_sender) { + int cn = 0, in = 0, kp = 0; + if (keyring_find_sid(keyring, &cn, &in, &kp, &m->sender)) { + m->author = m->sender; + m->authorship = AUTHOR_LOCAL; + return 1; + } + } + case AUTHOR_ERROR: + case AUTHOR_UNKNOWN: + case AUTHOR_IMPOSTOR: + return 0; + case AUTHOR_LOCAL: + case AUTHOR_AUTHENTIC: + return 1; + } + FATAL("m->authorship = %d", m->authorship); +} diff --git a/rhizome_database.c b/rhizome_database.c index f3024925..b9ef4f79 100644 --- a/rhizome_database.c +++ b/rhizome_database.c @@ -1408,63 +1408,158 @@ rollback: return -1; } -int rhizome_list_manifests(struct cli_context *context, const char *service, const char *name, - const char *sender_hex, const char *recipient_hex, +struct rhizome_list_cursor { + // Query parameters that narrow the set of listed bundles. + const char *service; + const char *name; + sid_t sender; + sid_t recipient; + unsigned limit; + unsigned offset; + // Set by calling the next() function. + rhizome_manifest *manifest; + size_t row; + int64_t rowid; + // Private state. + sqlite3_stmt *_statement; +}; + +/* Fill in the parameters in the cursor struct prior to calling this function. + * + * @author Andrew Bettison + */ +static int rhizome_list_open(sqlite_retry_state *retry, struct rhizome_list_cursor *cursor) +{ + IN(); + strbuf b = strbuf_alloca(1024); + strbuf_sprintf(b, "SELECT id, manifest, version, inserttime, author, rowid FROM manifests WHERE 1=1"); + if (cursor->service) + strbuf_puts(b, " AND service = @service"); + if (cursor->name) + strbuf_puts(b, " AND name like @name"); + if (!is_sid_t_any(cursor->sender)) + strbuf_puts(b, " AND sender = @sender"); + if (!is_sid_t_any(cursor->recipient)) + strbuf_puts(b, " AND recipient = @recipient"); + strbuf_puts(b, " ORDER BY inserttime DESC OFFSET @offset"); + if (strbuf_overrun(b)) + RETURN(WHYF("SQL command too long: %s", strbuf_str(b))); + cursor->_statement = sqlite_prepare_bind(retry, strbuf_str(b)); + if (!statement) + RETURN(-1); + int ret = 0; + if (cursor->service && *cursor->service && sqlite_bind(retry, statement, NAMED|STATIC_TEXT, "service", cursor->service, END) == -1) + goto failure; + if (cursor->name && *cursor->name && sqlite_bind(retry, statement, NAMED|STATIC_TEXT, "name", cursor->name, END) == -1) + goto failure; + if (!is_sid_t_any(cursor->sender) && sqlite_bind(retry, statement, NAMED|SID_T, "sender", &cursor->sender, END) == -1) + goto failure; + if (!is_sid_t_any(cursor->recipient) && sqlite_bind(retry, statement, NAMED|SID_T, "recipient", &cursor->recipient, END) == -1) + goto failure; + if (sqlite_bind(retry, statement, NAMED|INT, "offset", cursor->offset + cursor->_row, END) == -1) + goto cleanup; + cursor->manifest = NULL; + RETURN(0); + OUT(); +failure: + sqlite3_finalize(cursor->_statement); + cursor->_statement = NULL; + RETURN(-1); + OUT(); +} + +static int rhizome_list_next(sqlite_retry_state *retry, struct rhizome_list_cursor *cursor) +{ + IN(); + if (cursor->_statement == NULL && rhizome_list_open(retry, cursor) == -1) + RETURN(-1); + if (cursor->limit && cursor->_row >= cursor->limit) + RETURN(NULL); + while (sqlite_step_retry(retry, cursor->_statement) == SQLITE_ROW) { + if (cursor->manifest) { + rhizome_manifest_free(cursor->manifest); + cursor->manifest = NULL; + } + assert(sqlite3_column_count(cursor->_statement) == 6); + assert(sqlite3_column_type(cursor->_statement, 0) == SQLITE_TEXT); + assert(sqlite3_column_type(cursor->_statement, 1) == SQLITE_BLOB); + assert(sqlite3_column_type(cursor->_statement, 2) == SQLITE_INTEGER); + assert(sqlite3_column_type(cursor->_statement, 3) == SQLITE_INTEGER); + assert(sqlite3_column_type(cursor->_statement, 4) == SQLITE_TEXT || sqlite3_column_type(cursor->_statement, 4) == SQLITE_NULL); + assert(sqlite3_column_type(cursor->_statement, 5) == SQLITE_INTEGER); + rhizome_manifest *m = cursor->manifest = rhizome_new_manifest(); + if (m == NULL) + RETURN(-1); + const char *q_manifestid = (const char *) sqlite3_column_text(cursor->_statement, 0); + const char *manifestblob = (char *) sqlite3_column_blob(cursor->_statement, 1); + size_t manifestblobsize = sqlite3_column_bytes(cursor->_statement, 1); // must call after sqlite3_column_blob() + int64_t q_version = sqlite3_column_int64(cursor->_statement, 2); + int64_t q_inserttime = sqlite3_column_int64(cursor->_statement, 3); + const char *q_author = (const char *) sqlite3_column_text(cursor->_statement, 4); + int64_t rowid = sqlite3_column_int64(cursor->_statement, 5); + if (rhizome_read_manifest_file(m, manifestblob, manifestblobsize) == -1) { + WHYF("MANIFESTS row id=%s has invalid manifest blob -- skipped", q_manifestid); + continue; + } + const sid_t *author = NULL; + if (q_author) { + author = alloca(sizeof *author); + if (str_to_sid_t(author, q_author) == -1) { + WHYF("MANIFESTS row id=%s has invalid author column %s -- skipped", q_manifestid, alloca_str_toprint(q_author)); + continue; + } + } + if (rhizome_fill_manifest(m, NULL, author, NULL) == -1) { + WHYF("MANIFESTS row id=%s has invalid manifest -- skipped", q_manifestid); + continue; + } + if (m->version != q_version) { + WHYF("MANIFESTS row id=%s version=%"PRId64" does not match manifest blob version=%"PRId64" -- skipped", + q_manifestid, q_version, m->version); + continue; + } + if (cursor->service && !(m->service && strcasecmp(cursor->service, m->service) == 0)) + continue; + if (!is_sid_t_any(cursor->sender) && !(m->has_sender && cmp_sid_t(&cursor->sender, &m->sender) == 0)) + continue; + if (!is_sid_t_any(cursor->recipient) && !(m->has_recipient && cmp_sid_t(&cursor->recipient, &m->recipient) == 0)) + continue; + rhizome_lookup_author(m); + // Don't do rhizome_verify_author(m); too CPU expensive for a listing. Save that for when + // the bundle is extracted or exported. + ++cursor->_row; + RETURN(1); + } + RETURN(0); + OUT(); +} + +static void rhizome_list_release(struct rhizome_list_cursor *) +{ + if (cursor->manifest) { + rhizome_manifest_free(cursor->manifest); + cursor->manifest = NULL; + } + if (cursor->_statement) { + sqlite3_finalize(cursor->_statement); + cursor->_statement = NULL; + } +} + +int rhizome_list_manifests(struct cli_context *context, const char *service, const char *name, + const char *sender_hex, const char *recipient_hex, int limit, int offset, char count_rows) { IN(); - sid_t sender; - sid_t recipient; - strbuf b = strbuf_alloca(1024); - strbuf_sprintf(b, "SELECT id, manifest, version, inserttime, author, rowid FROM manifests WHERE 1=1"); - - if (service && *service) - strbuf_sprintf(b, " AND service = ?1"); - if (name && *name) - strbuf_sprintf(b, " AND name like ?2"); - if (sender_hex && *sender_hex) { - if (str_to_sid_t(&sender, sender_hex) == -1) - RETURN(WHYF("Invalid sender SID: %s", sender_hex)); - strbuf_sprintf(b, " AND sender = ?3"); - } - if (recipient_hex && *recipient_hex) { - if (str_to_sid_t(&recipient, recipient_hex) == -1) - RETURN(WHYF("Invalid recipient SID: %s", recipient_hex)); - strbuf_sprintf(b, " AND recipient = ?4"); - } - - strbuf_sprintf(b, " ORDER BY inserttime DESC"); - - if (offset) - strbuf_sprintf(b, " OFFSET %u", offset); - - if (strbuf_overrun(b)) - RETURN(WHYF("SQL command too long: %s", strbuf_str(b))); - + struct rhizome_list_cursor cursor; + bzero(&cursor, sizeof cursor); + if (sender_hex && *sender_hex && str_to_sid_t(&cursor->sender, sender_hex) == -1) + RETURN(WHYF("Invalid sender SID: %s", sender_hex)); + if (recipient_hex && *recipient_hex && str_to_sid_t(&cursor->recipient, recipient_hex) == -1) + RETURN(WHYF("Invalid recipient SID: %s", recipient_hex)); sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; - sqlite3_stmt *statement = sqlite_prepare(&retry, strbuf_str(b)); - if (!statement) + if (rhizome_list_open(&retry, &cursor) == -1) RETURN(-1); - - int ret = 0; - - if (service && *service) - ret = sqlite3_bind_text(statement, 1, service, -1, SQLITE_STATIC); - if (ret==SQLITE_OK && name && *name) - ret = sqlite3_bind_text(statement, 2, name, -1, SQLITE_STATIC); - if (ret==SQLITE_OK && sender_hex && *sender_hex) - ret = sqlite3_bind_text(statement, 3, sender_hex, -1, SQLITE_STATIC); - if (ret==SQLITE_OK && recipient_hex && *recipient_hex) - ret = sqlite3_bind_text(statement, 4, recipient_hex, -1, SQLITE_STATIC); - - if (ret!=SQLITE_OK){ - ret = WHYF("Failed to bind parameters: %s", sqlite3_errmsg(rhizome_db)); - goto cleanup; - } - - ret=0; - size_t rows = 0; - const char *names[]={ "_id", "service", @@ -1481,93 +1576,21 @@ int rhizome_list_manifests(struct cli_context *context, const char *service, con "name" }; cli_columns(context, 13, names); - - while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) { - ++rows; - if (limit>0 && rows>limit) - break; - if (!( sqlite3_column_count(statement) == 6 - && sqlite3_column_type(statement, 0) == SQLITE_TEXT - && sqlite3_column_type(statement, 1) == SQLITE_BLOB - && sqlite3_column_type(statement, 2) == SQLITE_INTEGER - && sqlite3_column_type(statement, 3) == SQLITE_INTEGER - && ( sqlite3_column_type(statement, 4) == SQLITE_TEXT - || sqlite3_column_type(statement, 4) == SQLITE_NULL - ) - )) { - ret = WHY("Incorrect statement column"); - break; - } - rhizome_manifest *m = rhizome_new_manifest(); - if (m == NULL) { - ret = WHY("Out of manifests"); - break; - } - const char *q_manifestid = (const char *) sqlite3_column_text(statement, 0); - const char *manifestblob = (char *) sqlite3_column_blob(statement, 1); - size_t manifestblobsize = sqlite3_column_bytes(statement, 1); // must call after sqlite3_column_blob() - int64_t q_version = sqlite3_column_int64(statement, 2); - int64_t q_inserttime = sqlite3_column_int64(statement, 3); - const char *q_author = (const char *) sqlite3_column_text(statement, 4); - int64_t rowid = sqlite3_column_int64(statement, 5); - - if (rhizome_read_manifest_file(m, manifestblob, manifestblobsize) == -1) { - WARNF("MANIFESTS row id=%s has invalid manifest blob -- skipped", q_manifestid); - } else { - - if (m->version != q_version) - WARNF("MANIFESTS row id=%s version=%"PRId64" does not match manifest blob.version=%"PRId64, - q_manifestid, q_version, m->version); - int match = 1; - - if (service && service[0] && !(m->service && strcasecmp(m->service, service) == 0)) - match = 0; - if (match && sender_hex && sender_hex[0]) { - if (!(m->has_sender && cmp_sid_t(&sender, &m->sender) == 0)) - match = 0; - } - if (match && recipient_hex && recipient_hex[0]) { - if (!(m->has_recipient && cmp_sid_t(&recipient, &m->recipient) == 0)) - match = 0; - } - - if (match) { - int from_here = 0; - - if (q_author) { - sid_t author; - if (str_to_sid_t(&author, q_author) == -1) { - WARNF("MANIFESTS row id=%s has invalid author=%s -- ignored", q_manifestid, alloca_str_toprint(q_author)); - } else { - rhizome_manifest_set_author(m, &author); - int cn = 0, in = 0, kp = 0; - from_here = keyring_find_sid(keyring, &cn, &in, &kp, &m->author); - } - } - if (!from_here && m->has_sender) { - if (config.debug.rhizome) - DEBUGF("blob.sender=%s", alloca_tohex_sid_t(m->sender)); - int cn = 0, in = 0, kp = 0; - from_here = keyring_find_sid(keyring, &cn, &in, &kp, &m->sender); - } - - cli_put_long(context, 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, q_inserttime, ":"); - cli_put_hexvalue(context, m->has_author ? m->author.binary : NULL, sizeof m->author.binary, ":"); - cli_put_long(context, from_here, ":"); - assert(m->filesize != RHIZOME_SIZE_UNSET); - 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"); - } - } - if (m) rhizome_manifest_free(m); + while (rhizome_list_next(&retry, &cursor) == 1) { + cli_put_long(context, cursor.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, q_inserttime, ":"); + cli_put_hexvalue(context, m->has_author ? m->author.binary : NULL, sizeof m->author.binary, ":"); + cli_put_long(context, from_here, ":"); + assert(m->filesize != RHIZOME_SIZE_UNSET); + 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"); } if (ret==0 && count_rows){ From a90eced6401d859acb1c4de3678b6726c9dbfb75 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Wed, 6 Nov 2013 18:20:47 +1030 Subject: [PATCH 5/7] Fix test framework: bug in assertStdoutIs() et al Could overwrite the latest stdout or stderr file produced by execute() --- testframework.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testframework.sh b/testframework.sh index 5d9fc86e..fdc998a4 100644 --- a/testframework.sh +++ b/testframework.sh @@ -920,8 +920,8 @@ _tfw_assertExpr() { _tfw_get_content() { case "$_tfw_opt_line_sed" in - '') ln -f "$1" "$_tfw_process_tmp/content" || error "ln failed";; - *) $SED -n -e "${_tfw_opt_line_sed}p" "$1" >"$_tfw_process_tmp/content" || error "sed failed";; + '') cat "$1" >|"$_tfw_process_tmp/content" || error "cat failed";; + *) $SED -n -e "${_tfw_opt_line_sed}p" "$1" >|"$_tfw_process_tmp/content" || error "sed failed";; esac } From 4aea05f445dd3bf0f6a0aec22e58a1df32bec1b8 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Wed, 6 Nov 2013 23:20:20 +1030 Subject: [PATCH 6/7] Fix prototype of bcopy() Add const to void * src argument --- os.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/os.h b/os.h index 0d9d31a3..f098518e 100644 --- a/os.h +++ b/os.h @@ -66,7 +66,7 @@ __SERVALDNA_OS_INLINE void bzero(void *buf, size_t len) { #endif #ifndef HAVE_BCOPY -__SERVALDNA_OS_INLINE void bcopy(void *src, void *dst, size_t len) { +__SERVALDNA_OS_INLINE void bcopy(const void *src, void *dst, size_t len) { memcpy(dst, src, len); } #endif From 45442d3eb4c860900b5ea857850b72e360bf6f90 Mon Sep 17 00:00:00 2001 From: Andrew Bettison Date: Tue, 5 Nov 2013 17:58:03 +1030 Subject: [PATCH 7/7] Rewrite bundle author authentication Replaced 'int has_author' manifest element with new 'enum authorship' element to record the result of author authentication, to avoid repeating expensive crypto operations. Separated the handling of bundle secret arguments from author lookup and authentication. The new rhizome_apply_bundle_secret(m,bsk) is now called at the top level to set the manifest secret key (if it validates), and thereafter there is no need to pass the 'bsk' argument to any other functions, as they can simply check the 'haveSecret' field of the manifest. Removed rhizome_extract_privatekey() which combined author lookup and bundle secret validation, and replaced it with functions that only deal with the author: rhizome_lookup_author() and rhizome_authenticate_author(). Renamed other functions to make their purpose and effect clearer. Formalised the semantics of only storing AUTHENTICATED author SIDs in the 'author' column of the MANIFESTS table, which necessitated a change to a 'rhizomeops' test case: when adding a file using a BK-less manifest, the author column is set to null, so the Rhizome list output does not show the bundle as ".fromhere" and does not give an author for that bundle. --- commandline.c | 25 +-- fdqueue.h | 1 + keyring.c | 2 +- meshms.c | 30 ++-- rhizome.c | 102 ++++++++---- rhizome.h | 96 ++++++----- rhizome_bundle.c | 203 +++++++++++++++------- rhizome_crypto.c | 380 +++++++++++++++++++++--------------------- rhizome_database.c | 152 +++++++++-------- rhizome_direct_http.c | 9 +- rhizome_fetch.c | 1 - rhizome_store.c | 30 ++-- tests/rhizomeops | 7 +- 13 files changed, 592 insertions(+), 446 deletions(-) diff --git a/commandline.c b/commandline.c index 4a4610b6..fd76d282 100644 --- a/commandline.c +++ b/commandline.c @@ -1358,20 +1358,20 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co if (journal && !m->is_journal) return WHY("Existing manifest is not a journal"); - if (!journal && m->is_journal) 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, bskhex ? &bsk : NULL)){ + if (rhizome_fill_manifest(m, filepath, *authorSidHex ? &authorSid : NULL)) { rhizome_manifest_free(m); return -1; } if (journal){ - if (rhizome_append_journal_file(m, bskhex?&bsk:NULL, 0, filepath)){ + if (rhizome_append_journal_file(m, 0, filepath)){ rhizome_manifest_free(m); return -1; } @@ -1406,15 +1406,17 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co cli_field_name(context, "manifestid", ":"); cli_put_string(context, alloca_tohex_rhizome_bid_t(mout->cryptoSignPublic), "\n"); } + assert(m->haveSecret); { char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1]; rhizome_bytes_to_hex_upper(mout->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES); cli_field_name(context, ".secret", ":"); cli_put_string(context, secret, "\n"); } - if (m->has_author) { + assert(mout->authorship != AUTHOR_LOCAL); + if (mout->authorship == AUTHOR_AUTHENTIC) { cli_field_name(context, ".author", ":"); - cli_put_string(context, alloca_tohex_sid_t(m->author), "\n"); + cli_put_string(context, alloca_tohex_sid_t(mout->author), "\n"); } if (mout->has_bundle_key) { cli_field_name(context, "BK", ":"); @@ -1670,8 +1672,9 @@ int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_context *con ret = rhizome_retrieve_manifest(&bid, m); if (ret==0){ - // ignore errors - rhizome_extract_privatekey(m, bskhex ? &bsk : NULL); + if (bskhex) + rhizome_apply_bundle_secret(m, &bsk); + rhizome_authenticate_author(m); if (m->service) { cli_field_name(context, "service", ":"); @@ -1684,6 +1687,9 @@ int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_context *con rhizome_bytes_to_hex_upper(m->cryptoSignSecret, secret, RHIZOME_BUNDLE_KEY_BYTES); cli_field_name(context, ".secret", ":"); cli_put_string(context, secret, "\n"); + } + assert(m->authorship != AUTHOR_LOCAL); + if (m->authorship == AUTHOR_AUTHENTIC) { cli_field_name(context, ".author", ":"); cli_put_string(context, alloca_tohex_sid_t(m->author), "\n"); } @@ -1707,8 +1713,7 @@ int app_rhizome_extract(const struct cli_parsed *parsed, struct cli_context *con if (ret==0 && m->filesize != 0 && filepath && *filepath){ if (extract){ // Save the file, implicitly decrypting if required. - // TODO, this may cause us to search for an author a second time if the above call to rhizome_extract_privatekey failed - retfile = rhizome_extract_file(m, filepath, bskhex?&bsk:NULL); + retfile = rhizome_extract_file(m, filepath); }else{ // Save the file without attempting to decrypt int64_t length; diff --git a/fdqueue.h b/fdqueue.h index 11596cf3..cb718331 100644 --- a/fdqueue.h +++ b/fdqueue.h @@ -88,5 +88,6 @@ void dump_stack(int log_level); #define OUT() fd_func_exit(__HERE__, &_this_call) #define RETURN(X) do { OUT(); return (X); } while (0); #define RETURNNULL do { OUT(); return (NULL); } while (0); +#define RETURNVOID do { OUT(); return; } while (0); #endif // __SERVALDNA__FDQUEUE_H diff --git a/keyring.c b/keyring.c index f9c60a6f..708df92b 100644 --- a/keyring.c +++ b/keyring.c @@ -1504,7 +1504,7 @@ unsigned char *keyring_find_sas_private(keyring_file *k, const sid_t *sidp, unsi k->contexts[cn]->identities[in]->keypairs[kp]->private_key; unsigned char *sas_public= k->contexts[cn]->identities[in]->keypairs[kp]->public_key; - if (rhizome_verify_bundle_privatekey(NULL,sas_private,sas_public)) + if (!rhizome_verify_bundle_privatekey(sas_private, sas_public)) { /* SAS key is invalid (perhaps because it was a pre 0.90 format one), so replace it */ diff --git a/meshms.c b/meshms.c index 78ff4f97..808f6422 100644 --- a/meshms.c +++ b/meshms.c @@ -87,7 +87,7 @@ static int get_my_conversation_bundle(const sid_t *my_sidp, rhizome_manifest *m) assert(m->haveSecret); if (m->haveSecret == NEW_BUNDLE_ID) { rhizome_manifest_set_service(m, RHIZOME_SERVICE_FILE); - if (rhizome_fill_manifest(m, NULL, my_sidp, NULL) == -1) + if (rhizome_fill_manifest(m, NULL, my_sidp) == -1) return WHY("Invalid manifest"); if (config.debug.meshms) { char secret[RHIZOME_BUNDLE_KEY_STRLEN + 1]; @@ -218,8 +218,9 @@ static int create_ply(const sid_t *my_sid, struct conversations *conv, rhizome_m rhizome_manifest_set_recipient(m, &conv->them); rhizome_manifest_set_filesize(m, 0); rhizome_manifest_set_tail(m, 0); - if (rhizome_fill_manifest(m, NULL, my_sid, NULL)) + if (rhizome_fill_manifest(m, NULL, my_sid)) return -1; + assert(m->haveSecret); assert(m->payloadEncryption == PAYLOAD_ENCRYPTED); conv->my_ply.bundle_id = m->cryptoSignPublic; conv->found_my_ply = 1; @@ -239,7 +240,7 @@ static int ply_read_open(struct ply_read *ply, const rhizome_bid_t *bid, rhizome DEBUGF("Opening ply %s", alloca_tohex_rhizome_bid_t(*bid)); if (rhizome_retrieve_manifest(bid, m)) return -1; - int ret = rhizome_open_decrypt_read(m, NULL, &ply->read); + int ret = rhizome_open_decrypt_read(m, &ply->read); if (ret == 1) WARNF("Payload was not found for manifest %s, %"PRId64, alloca_tohex_rhizome_bid_t(m->cryptoSignPublic), m->version); if (ret != 0) @@ -317,7 +318,8 @@ static int ply_find_next(struct ply_read *ply, char type){ } } -static int append_meshms_buffer(const sid_t *my_sid, struct conversations *conv, unsigned char *buffer, int len){ +static int append_meshms_buffer(const sid_t *my_sid, struct conversations *conv, unsigned char *buffer, int len) +{ int ret=-1; rhizome_manifest *mout = NULL; rhizome_manifest *m = rhizome_new_manifest(); @@ -327,14 +329,17 @@ static int append_meshms_buffer(const sid_t *my_sid, struct conversations *conv, if (conv->found_my_ply){ if (rhizome_retrieve_manifest(&conv->my_ply.bundle_id, m)) goto end; - if (rhizome_find_bundle_author(m)) + rhizome_authenticate_author(m); + if (!m->haveSecret || m->authorship != AUTHOR_AUTHENTIC) goto end; }else{ if (create_ply(my_sid, conv, m)) goto end; } + assert(m->haveSecret); + assert(m->authorship == AUTHOR_AUTHENTIC); - if (rhizome_append_journal_buffer(m, NULL, 0, buffer, len)) + if (rhizome_append_journal_buffer(m, 0, buffer, len)) goto end; if (rhizome_manifest_finalise(m, &mout, 1)) @@ -493,7 +498,7 @@ static int read_known_conversations(rhizome_manifest *m, const sid_t *their_sid, struct rhizome_read_buffer buff; bzero(&buff, sizeof(buff)); - int ret = rhizome_open_decrypt_read(m, NULL, &read); + int ret = rhizome_open_decrypt_read(m, &read); if (ret == -1) goto end; @@ -713,7 +718,8 @@ int app_meshms_conversations(const struct cli_parsed *parsed, struct cli_context return 0; } -int app_meshms_send_message(const struct cli_parsed *parsed, struct cli_context *context){ +int app_meshms_send_message(const struct cli_parsed *parsed, struct cli_context *context) +{ const char *my_sidhex, *their_sidhex, *message; if (cli_arg(parsed, "sender_sid", &my_sidhex, str_is_subscriber_id, "") == -1 || cli_arg(parsed, "recipient_sid", &their_sidhex, str_is_subscriber_id, "") == -1 @@ -728,9 +734,11 @@ int app_meshms_send_message(const struct cli_parsed *parsed, struct cli_context return -1; sid_t my_sid, their_sid; - fromhex(my_sid.binary, my_sidhex, sizeof(my_sid.binary)); - fromhex(their_sid.binary, their_sidhex, sizeof(their_sid.binary)); - struct conversations *conv=find_or_create_conv(&my_sid, &their_sid); + if (str_to_sid_t(&my_sid, my_sidhex) == -1) + return WHY("invalid sender SID"); + if (str_to_sid_t(&their_sid, their_sidhex) == -1) + return WHY("invalid recipient SID"); + struct conversations *conv = find_or_create_conv(&my_sid, &their_sid); if (!conv) return -1; diff --git a/rhizome.c b/rhizome.c index 6f085dc7..3db8cfc5 100644 --- a/rhizome.c +++ b/rhizome.c @@ -184,44 +184,74 @@ int rhizome_manifest_check_sanity(rhizome_manifest *m) return 0; } - -/* - A bundle can either be an ordinary manifest-payload pair, or a group description. - - - Group descriptions are manifests with no payload that have the "isagroup" variable set. They - get stored in the manifests table AND a reference is added to the grouplist table. Any - manifest, including any group manifest, may be a member of zero or one group. This allows a - nested, i.e., multi-level group hierarchy where sub-groups will only typically be discovered - by joining the parent group. -*/ - -int rhizome_manifest_bind_id(rhizome_manifest *m) +/* Sets the bundle key "BK" field of a manifest. Returns 1 if the field was set, 0 if not. + * + * This function must not be called unless the bundle secret is known. + * + * @author Andrew Bettison + */ +int rhizome_manifest_add_bundle_key(rhizome_manifest *m) { - if (rhizome_manifest_createid(m) == -1) - return -1; - /* The ID is implicit in transit, but we need to store it in the file, so that reimporting - manifests on receiver nodes works easily. We might implement something that strips the id - variable out of the manifest when sending it, or some other scheme to avoid sending all the - extra bytes. */ - if (m->has_author) { - /* Set the BK using the provided authorship information. - Serval Security Framework defines BK as being: - BK = privateKey XOR sha512(RS##BID), where BID = cryptoSignPublic, - and RS is the rhizome secret for the specified author. - The nice thing about this specification is that: - privateKey = BK XOR sha512(RS##BID), so the same function can be used - to encrypt and decrypt the BK field. */ - const unsigned char *rs; - int rs_len=0; - if (rhizome_find_secret(&m->author, &rs_len, &rs)) - return WHYF("Failed to obtain RS for %s to calculate BK", alloca_tohex_sid_t(m->author)); - rhizome_bk_t bkey; - if (!rhizome_secret2bk(&m->cryptoSignPublic, rs, rs_len, bkey.binary, m->cryptoSignSecret)) - rhizome_manifest_set_bundle_key(m, &bkey); - else - return WHY("Failed to set BK"); + IN(); + assert(m->haveSecret); + switch (m->authorship) { + case ANONYMOUS: // there can be no BK field without an author + case AUTHOR_UNKNOWN: // we already know the author is not in the keyring + case AUTHENTICATION_ERROR: // already tried and failed to get Rhizome Secret + break; + case AUTHOR_NOT_CHECKED: + case AUTHOR_LOCAL: + case AUTHOR_AUTHENTIC: + case AUTHOR_IMPOSTOR: { + /* Set the BK using the provided author. Serval Security Framework defines BK as being: + * BK = privateKey XOR sha512(RS##BID) + * where BID = cryptoSignPublic, + * RS is the rhizome secret for the specified author. + * The nice thing about this specification is that: + * privateKey = BK XOR sha512(RS##BID) + * so the same function can be used to encrypt and decrypt the BK field. + */ + const unsigned char *rs; + size_t rs_len = 0; + enum rhizome_secret_disposition d = find_rhizome_secret(&m->author, &rs_len, &rs); + switch (d) { + case FOUND_RHIZOME_SECRET: { + rhizome_bk_t bkey; + if (rhizome_secret2bk(&m->cryptoSignPublic, rs, rs_len, bkey.binary, m->cryptoSignSecret) == 0) { + rhizome_manifest_set_bundle_key(m, &bkey); + m->authorship = AUTHOR_AUTHENTIC; + RETURN(1); + } else + m->authorship = AUTHENTICATION_ERROR; + } + break; + case IDENTITY_NOT_FOUND: + m->authorship = AUTHOR_UNKNOWN; + break; + case IDENTITY_HAS_NO_RHIZOME_SECRET: + m->authorship = AUTHENTICATION_ERROR; + break; + default: + FATALF("find_rhizome_secret() returned unknown code %d", (int)d); + break; + } + } + break; + default: + FATALF("m->authorship = %d", (int)m->authorship); } - return 0; + rhizome_manifest_del_bundle_key(m); + switch (m->authorship) { + case AUTHOR_UNKNOWN: + WHYF("Cannot set BK because author=%s is not in keyring", alloca_tohex_sid_t(m->author)); + break; + case AUTHENTICATION_ERROR: + WHY("Cannot set BK due to error"); + break; + default: + break; + } + RETURN(0); } int rhizome_add_manifest(rhizome_manifest *m, int ttl) diff --git a/rhizome.h b/rhizome.h index 5489a73a..2e4125a0 100644 --- a/rhizome.h +++ b/rhizome.h @@ -238,16 +238,13 @@ typedef struct rhizome_manifest bool_t has_sender; bool_t has_recipient; - /* Set if the 'author' element is valid, ie, a SID has been assigned. - */ - bool_t has_author; - /* Local authorship. Useful for dividing bundle lists between "sent" and * "inbox" views. */ enum rhizome_bundle_authorship { - AUTHOR_NOT_CHECKED = 0, - AUTHOR_ERROR, // author check failed, don't try again + ANONYMOUS = 0, // 'author' element is not valid + AUTHOR_NOT_CHECKED, // 'author' element is valid but not checked + AUTHENTICATION_ERROR, // author check failed, don't try again AUTHOR_UNKNOWN, // author is not a local identity AUTHOR_LOCAL, // author is in keyring (unlocked) but not verified AUTHOR_IMPOSTOR, // author is a local identity but fails verification @@ -295,7 +292,8 @@ typedef struct rhizome_manifest sid_t recipient; /* Local data, not encapsulated in the bundle. The system time of the most - * recent INSERT or UPDATE of the manifest into the store. + * recent INSERT or UPDATE of the manifest into the store. Zero if the manifest + * has not been stored yet. */ time_ms_t inserttime; @@ -326,19 +324,27 @@ typedef struct rhizome_manifest * * @author Andrew Bettison */ -#define rhizome_manifest_set_id(m,v) _rhizome_manifest_set_id(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_version(m,v) _rhizome_manifest_set_version(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_filesize(m,v) _rhizome_manifest_set_filesize(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_filehash(m,v) _rhizome_manifest_set_filehash(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_tail(m,v) _rhizome_manifest_set_tail(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_bundle_key(m,v) _rhizome_manifest_set_bundle_key(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_service(m,v) _rhizome_manifest_set_service(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_name(m,v) _rhizome_manifest_set_name(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_date(m,v) _rhizome_manifest_set_date(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_sender(m,v) _rhizome_manifest_set_sender(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_recipient(m,v) _rhizome_manifest_set_recipient(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_crypt(m,v) _rhizome_manifest_set_crypt(__WHENCE__,(m),(v)) -#define rhizome_manifest_set_author(m,v) _rhizome_manifest_set_author(__WHENCE__,(m),(v)) +#define rhizome_manifest_set_id(m,v) _rhizome_manifest_set_id(__WHENCE__,(m),(v)) +#define rhizome_manifest_set_version(m,v) _rhizome_manifest_set_version(__WHENCE__,(m),(v)) +#define rhizome_manifest_set_filesize(m,v) _rhizome_manifest_set_filesize(__WHENCE__,(m),(v)) +#define rhizome_manifest_set_filehash(m,v) _rhizome_manifest_set_filehash(__WHENCE__,(m),(v)) +#define rhizome_manifest_set_tail(m,v) _rhizome_manifest_set_tail(__WHENCE__,(m),(v)) +#define rhizome_manifest_set_bundle_key(m,v) _rhizome_manifest_set_bundle_key(__WHENCE__,(m),(v)) +#define rhizome_manifest_del_bundle_key(m) _rhizome_manifest_del_bundle_key(__WHENCE__,(m)) +#define rhizome_manifest_set_service(m,v) _rhizome_manifest_set_service(__WHENCE__,(m),(v)) +#define rhizome_manifest_del_service(m) _rhizome_manifest_del_service(__WHENCE__,(m)) +#define rhizome_manifest_set_name(m,v) _rhizome_manifest_set_name(__WHENCE__,(m),(v)) +#define rhizome_manifest_del_name(m) _rhizome_manifest_del_name(__WHENCE__,(m)) +#define rhizome_manifest_set_date(m,v) _rhizome_manifest_set_date(__WHENCE__,(m),(v)) +#define rhizome_manifest_del_date(m) _rhizome_manifest_del_date(__WHENCE__,(m)) +#define rhizome_manifest_set_sender(m,v) _rhizome_manifest_set_sender(__WHENCE__,(m),(v)) +#define rhizome_manifest_del_sender(m) _rhizome_manifest_del_sender(__WHENCE__,(m)) +#define rhizome_manifest_set_recipient(m,v) _rhizome_manifest_set_recipient(__WHENCE__,(m),(v)) +#define rhizome_manifest_del_recipient(m) _rhizome_manifest_del_recipient(__WHENCE__,(m)) +#define rhizome_manifest_set_crypt(m,v) _rhizome_manifest_set_crypt(__WHENCE__,(m),(v)) +#define rhizome_manifest_set_inserttime(m,v) _rhizome_manifest_set_inserttime(__WHENCE__,(m),(v)) +#define rhizome_manifest_set_author(m,v) _rhizome_manifest_set_author(__WHENCE__,(m),(v)) +#define rhizome_manifest_del_author(m) _rhizome_manifest_del_author(__WHENCE__,(m)) void _rhizome_manifest_set_id(struct __sourceloc, rhizome_manifest *, const rhizome_bid_t *); void _rhizome_manifest_set_version(struct __sourceloc, rhizome_manifest *, int64_t); // TODO change to uint64_t @@ -346,13 +352,21 @@ void _rhizome_manifest_set_filesize(struct __sourceloc, rhizome_manifest *, uint void _rhizome_manifest_set_filehash(struct __sourceloc, rhizome_manifest *, const rhizome_filehash_t *); void _rhizome_manifest_set_tail(struct __sourceloc, rhizome_manifest *, uint64_t); void _rhizome_manifest_set_bundle_key(struct __sourceloc, rhizome_manifest *, const rhizome_bk_t *); +void _rhizome_manifest_del_bundle_key(struct __sourceloc, rhizome_manifest *); void _rhizome_manifest_set_service(struct __sourceloc, rhizome_manifest *, const char *); +void _rhizome_manifest_del_service(struct __sourceloc, rhizome_manifest *); void _rhizome_manifest_set_name(struct __sourceloc, rhizome_manifest *, const char *); +void _rhizome_manifest_del_name(struct __sourceloc, rhizome_manifest *); void _rhizome_manifest_set_date(struct __sourceloc, rhizome_manifest *, time_ms_t); +void _rhizome_manifest_del_date(struct __sourceloc, rhizome_manifest *); void _rhizome_manifest_set_sender(struct __sourceloc, rhizome_manifest *, const sid_t *); +void _rhizome_manifest_del_sender(struct __sourceloc, rhizome_manifest *); void _rhizome_manifest_set_recipient(struct __sourceloc, rhizome_manifest *, const sid_t *); +void _rhizome_manifest_del_recipient(struct __sourceloc, rhizome_manifest *); void _rhizome_manifest_set_crypt(struct __sourceloc, rhizome_manifest *, enum rhizome_manifest_crypt); +void _rhizome_manifest_set_inserttime(struct __sourceloc, rhizome_manifest *, time_ms_t); void _rhizome_manifest_set_author(struct __sourceloc, rhizome_manifest *, const sid_t *); +void _rhizome_manifest_del_author(struct __sourceloc, rhizome_manifest *); /* Supported service identifiers. These go in the 'service' field of every * manifest, and indicate which application must be used to process the bundle @@ -439,17 +453,21 @@ rhizome_manifest *_rhizome_new_manifest(struct __sourceloc __whence); int rhizome_manifest_pack_variables(rhizome_manifest *m); int rhizome_store_bundle(rhizome_manifest *m); int rhizome_remove_file_datainvalid(sqlite_retry_state *retry, const rhizome_filehash_t *hashp); -int rhizome_manifest_add_group(rhizome_manifest *m,char *groupid); -int rhizome_clean_payload(const char *fileidhex); int rhizome_store_file(rhizome_manifest *m,const unsigned char *key); int rhizome_bundle_import_files(rhizome_manifest *m, const char *manifest_path, const char *filepath); -int rhizome_fill_manifest(rhizome_manifest *m, const char *filepath, const sid_t *authorSidp, rhizome_bk_t *bsk); + +int rhizome_fill_manifest(rhizome_manifest *m, const char *filepath, const sid_t *authorSidp); + +int rhizome_apply_bundle_secret(rhizome_manifest *, const rhizome_bk_t *); +int rhizome_manifest_add_bundle_key(rhizome_manifest *); + +void rhizome_find_bundle_author_and_secret(rhizome_manifest *m); int rhizome_lookup_author(rhizome_manifest *m); +void rhizome_authenticate_author(rhizome_manifest *m); int rhizome_manifest_verify(rhizome_manifest *m); int rhizome_manifest_check_sanity(rhizome_manifest *m_in); -int rhizome_manifest_bind_id(rhizome_manifest *m_in); int rhizome_manifest_finalise(rhizome_manifest *m, rhizome_manifest **mout, int deduplicate); int rhizome_add_manifest(rhizome_manifest *m_in,int ttl); @@ -546,9 +564,9 @@ int64_t rhizome_bar_version(const unsigned char *bar); uint64_t rhizome_bar_bidprefix_ll(unsigned char *bar); int rhizome_is_bar_interesting(unsigned char *bar); int rhizome_is_manifest_interesting(rhizome_manifest *m); -int rhizome_list_manifests(struct cli_context *context, const char *service, const char *name, - const char *sender_sid, const char *recipient_sid, - int limit, int offset, char count_rows); +int rhizome_list_manifests(struct cli_context *context, const char *service, const char *name, + const char *sender_sid, const char *recipient_sid, + size_t rowlimit, size_t rowoffset, char count_rows); int rhizome_retrieve_manifest(const rhizome_bid_t *bid, rhizome_manifest *m); int rhizome_retrieve_manifest_by_prefix(const unsigned char *prefix, unsigned prefix_len, rhizome_manifest *m); int rhizome_advertise_manifest(struct subscriber *dest, rhizome_manifest *m); @@ -562,7 +580,12 @@ int rhizome_delete_file(const rhizome_filehash_t *hashp); int rhizome_fetching_get_fds(struct pollfd *fds,int *fdcount,int fdmax); int monitor_announce_bundle(rhizome_manifest *m); -int rhizome_find_secret(const sid_t *authorSidp, int *rs_len, const unsigned char **rs); +enum rhizome_secret_disposition { + FOUND_RHIZOME_SECRET = 0, + IDENTITY_NOT_FOUND, + IDENTITY_HAS_NO_RHIZOME_SECRET, +}; +enum rhizome_secret_disposition find_rhizome_secret(const sid_t *authorSidp, size_t *rs_len, const unsigned char **rs); int rhizome_bk_xor_stream( const rhizome_bid_t *bidp, const unsigned char *rs, @@ -584,13 +607,9 @@ int rhizome_secret2bk( const unsigned char secret[crypto_sign_edwards25519sha512batch_SECRETKEYBYTES] ); unsigned char *rhizome_bundle_shared_secret(rhizome_manifest *m); -int rhizome_extract_privatekey(rhizome_manifest *m, const rhizome_bk_t *bsk); -int rhizome_extract_privatekey_required(rhizome_manifest *m, rhizome_bk_t *bsk); int rhizome_sign_hash_with_key(rhizome_manifest *m,const unsigned char *sk, const unsigned char *pk,rhizome_signature *out); -int rhizome_verify_bundle_privatekey(rhizome_manifest *m, const unsigned char *sk, - const unsigned char *pk); -int rhizome_find_bundle_author(rhizome_manifest *m); +int rhizome_verify_bundle_privatekey(const unsigned char *sk, const unsigned char *pk); int rhizome_queue_ignore_manifest(unsigned char *bid_prefix, int prefix_len, int timeout); int rhizome_ignore_manifest_check(unsigned char *bid_prefix, int prefix_len); @@ -866,11 +885,10 @@ int rhizome_import_file(rhizome_manifest *m, const char *filepath); int rhizome_import_buffer(rhizome_manifest *m, unsigned char *buffer, size_t length); int rhizome_stat_file(rhizome_manifest *m, const char *filepath); int rhizome_add_file(rhizome_manifest *m, const char *filepath); -int rhizome_derive_key(rhizome_manifest *m, rhizome_bk_t *bsk); +int rhizome_derive_payload_key(rhizome_manifest *m); -int rhizome_open_write_journal(rhizome_manifest *m, rhizome_bk_t *bsk, uint64_t advance_by, uint64_t new_size); -int rhizome_append_journal_buffer(rhizome_manifest *m, rhizome_bk_t *bsk, uint64_t advance_by, unsigned char *buffer, size_t len); -int rhizome_append_journal_file(rhizome_manifest *m, rhizome_bk_t *bsk, uint64_t advance_by, const char *filename); +int rhizome_append_journal_buffer(rhizome_manifest *m, uint64_t advance_by, unsigned char *buffer, size_t len); +int rhizome_append_journal_file(rhizome_manifest *m, uint64_t advance_by, const char *filename); int rhizome_journal_pipe(struct rhizome_write *write, const rhizome_filehash_t *hashp, uint64_t start_offset, uint64_t length); int rhizome_crypt_xor_block(unsigned char *buffer, size_t buffer_size, uint64_t stream_offset, @@ -879,8 +897,8 @@ int rhizome_open_read(struct rhizome_read *read, const rhizome_filehash_t *hashp ssize_t rhizome_read(struct rhizome_read *read, unsigned char *buffer, size_t buffer_length); int rhizome_read_buffered(struct rhizome_read *read, struct rhizome_read_buffer *buffer, unsigned char *data, size_t len); int rhizome_read_close(struct rhizome_read *read); -int rhizome_open_decrypt_read(rhizome_manifest *m, rhizome_bk_t *bsk, struct rhizome_read *read_state); -int rhizome_extract_file(rhizome_manifest *m, const char *filepath, rhizome_bk_t *bsk); +int rhizome_open_decrypt_read(rhizome_manifest *m, struct rhizome_read *read_state); +int rhizome_extract_file(rhizome_manifest *m, const char *filepath); int rhizome_dump_file(const rhizome_filehash_t *hashp, const char *filepath, int64_t *length); int rhizome_read_cached(const rhizome_bid_t *bid, uint64_t version, time_ms_t timeout, uint64_t fileOffset, unsigned char *buffer, size_t length); diff --git a/rhizome_bundle.c b/rhizome_bundle.c index 477dbf9b..59aaebae 100644 --- a/rhizome_bundle.c +++ b/rhizome_bundle.c @@ -117,8 +117,21 @@ void _rhizome_manifest_set_id(struct __sourceloc __whence, rhizome_manifest *m, { const char *v = rhizome_manifest_set(m, "id", alloca_tohex_rhizome_bid_t(*bidp)); assert(v); // TODO: remove known manifest fields from vars[] - if (bidp != &m->cryptoSignPublic) + if (bidp != &m->cryptoSignPublic && cmp_rhizome_bid_t(&m->cryptoSignPublic, bidp) != 0) { m->cryptoSignPublic = *bidp; + // The BID just changed, so the secret key and bundle key are no longer valid. + if (m->haveSecret) { + m->haveSecret = SECRET_UNKNOWN; + bzero(m->cryptoSignSecret, sizeof m->cryptoSignSecret); // not strictly necessary but aids debugging + } + if (m->has_bundle_key) { + m->has_bundle_key = 0; + m->bundle_key = RHIZOME_BK_NONE; // not strictly necessary but aids debugging + } + // Any authenticated author is no longer authenticated, but is still known to be in the keyring. + if (m->authorship == AUTHOR_AUTHENTIC) + m->authorship = AUTHOR_LOCAL; + } } void _rhizome_manifest_set_version(struct __sourceloc __whence, rhizome_manifest *m, int64_t version) @@ -169,11 +182,21 @@ void _rhizome_manifest_set_bundle_key(struct __sourceloc __whence, rhizome_manif assert(v); // TODO: remove known manifest fields from vars[] m->bundle_key = *bkp; m->has_bundle_key = 1; - } else { + } else + _rhizome_manifest_del_bundle_key(__whence, m); +} + +void _rhizome_manifest_del_bundle_key(struct __sourceloc __whence, rhizome_manifest *m) +{ + if (m->has_bundle_key) { rhizome_manifest_del(m, "BK"); - m->bundle_key = RHIZOME_BK_NONE; m->has_bundle_key = 0; - } + m->bundle_key = RHIZOME_BK_NONE; // not strictly necessary, but aids debugging + } else + assert(rhizome_manifest_get(m, "BK") == NULL); + // Once there is no BK field, any authenticated authorship is no longer. + if (m->authorship == AUTHOR_AUTHENTIC) + m->authorship = AUTHOR_LOCAL; } void _rhizome_manifest_set_service(struct __sourceloc __whence, rhizome_manifest *m, const char *service) @@ -182,10 +205,17 @@ void _rhizome_manifest_set_service(struct __sourceloc __whence, rhizome_manifest const char *v = rhizome_manifest_set(m, "service", service); assert(v); // TODO: remove known manifest fields from vars[] m->service = v; - } else { - rhizome_manifest_del(m, "service"); + } else + _rhizome_manifest_del_service(__whence, m); +} + +void _rhizome_manifest_del_service(struct __sourceloc __whence, rhizome_manifest *m) +{ + if (m->service) { m->service = NULL; - } + rhizome_manifest_del(m, "service"); + } else + assert(rhizome_manifest_get(m, "service") == NULL); } void _rhizome_manifest_set_name(struct __sourceloc __whence, rhizome_manifest *m, const char *name) @@ -200,6 +230,15 @@ void _rhizome_manifest_set_name(struct __sourceloc __whence, rhizome_manifest *m } } +void _rhizome_manifest_del_name(struct __sourceloc __whence, rhizome_manifest *m) +{ + if (m->name) { + m->name = NULL; + rhizome_manifest_del(m, "name"); + } else + assert(rhizome_manifest_get(m, "name") == NULL); +} + void _rhizome_manifest_set_date(struct __sourceloc __whence, rhizome_manifest *m, time_ms_t date) { const char *v = rhizome_manifest_set_ll(m, "date", date); @@ -215,11 +254,18 @@ void _rhizome_manifest_set_sender(struct __sourceloc __whence, rhizome_manifest assert(v); // TODO: remove known manifest fields from vars[] m->sender = *sidp; m->has_sender = 1; - } else { + } else + _rhizome_manifest_del_sender(__whence, m); +} + +void _rhizome_manifest_del_sender(struct __sourceloc __whence, rhizome_manifest *m) +{ + if (m->has_sender) { rhizome_manifest_del(m, "sender"); m->sender = SID_ANY; m->has_sender = 0; - } + } else + assert(rhizome_manifest_get(m, "sender") == NULL); } void _rhizome_manifest_set_recipient(struct __sourceloc __whence, rhizome_manifest *m, const sid_t *sidp) @@ -229,11 +275,18 @@ void _rhizome_manifest_set_recipient(struct __sourceloc __whence, rhizome_manife assert(v); // TODO: remove known manifest fields from vars[] m->recipient = *sidp; m->has_recipient = 1; - } else { + } else + _rhizome_manifest_del_recipient(__whence, m); +} + +void _rhizome_manifest_del_recipient(struct __sourceloc __whence, rhizome_manifest *m) +{ + if (m->has_recipient) { rhizome_manifest_del(m, "recipient"); m->recipient = SID_ANY; m->has_recipient = 0; - } + } else + assert(rhizome_manifest_get(m, "recipient") == NULL); } void _rhizome_manifest_set_crypt(struct __sourceloc __whence, rhizome_manifest *m, enum rhizome_manifest_crypt flag) @@ -257,14 +310,31 @@ void _rhizome_manifest_set_crypt(struct __sourceloc __whence, rhizome_manifest * m->payloadEncryption = flag; } +void _rhizome_manifest_set_inserttime(struct __sourceloc __whence, rhizome_manifest *m, time_ms_t time) +{ + m->inserttime = time; +} + void _rhizome_manifest_set_author(struct __sourceloc __whence, rhizome_manifest *m, const sid_t *sidp) { if (sidp) { - m->author = *sidp; - m->has_author = 1; - } else { + if (m->authorship == ANONYMOUS || cmp_sid_t(&m->author, sidp) != 0) { + if (config.debug.rhizome_manifest) + DEBUGF("SET manifest[%d] author = %s", m->manifest_record_number, alloca_tohex_sid_t(*sidp)); + m->author = *sidp; + m->authorship = AUTHOR_NOT_CHECKED; + } + } else + _rhizome_manifest_del_author(__whence, m); +} + +void _rhizome_manifest_del_author(struct __sourceloc __whence, rhizome_manifest *m) +{ + if (m->authorship != ANONYMOUS) { + if (config.debug.rhizome_manifest) + DEBUGF("DEL manifest[%d] author", m->manifest_record_number); m->author = SID_ANY; - m->has_author = 0; + m->authorship = ANONYMOUS; } } @@ -403,7 +473,11 @@ int rhizome_manifest_parse(rhizome_manifest *m) } else { m->vars[m->var_count] = strdup(var); m->values[m->var_count] = strdup(value); - // if any of these fields are not well formed, the manifest is invalid and cannot be imported + /* The bundle ID is implicit in transit, but we need to store it in the manifest, so that + * reimporting manifests on receiver nodes works easily. We might implement something that + * strips the id variable out of the manifest when sending it, or some other scheme to avoid + * sending all the extra bytes. + */ if (strcasecmp(var, "id") == 0) { have_id = 1; if (str_to_rhizome_bid_t(&m->cryptoSignPublic, value) == -1) { @@ -455,7 +529,7 @@ int rhizome_manifest_parse(rhizome_manifest *m) if (!str_to_uint64(value, 10, &tail, NULL) || tail == RHIZOME_SIZE_UNSET) { if (config.debug.rejecteddata) DEBUGF("Invalid tail: %s", value); - m->warnings++; + m->errors++; } else { m->tail = tail; m->is_journal = 1; @@ -853,17 +927,6 @@ int rhizome_write_manifest_file(rhizome_manifest *m, const char *path, char appe return ret; } -/* - Adds a group that this bundle should be present in. If we have the means to sign - the bundle as a member of that group, then we create the appropriate signature block. - The group signature blocks, like all signature blocks, will be appended to the - manifest data during the finalisation process. - */ -int rhizome_manifest_add_group(rhizome_manifest *m,char *groupid) -{ - return WHY("Not implemented."); -} - int rhizome_manifest_dump(rhizome_manifest *m, const char *msg) { unsigned i; @@ -904,42 +967,41 @@ int rhizome_manifest_finalise(rhizome_manifest *m, rhizome_manifest **mout, int OUT(); } -int rhizome_fill_manifest(rhizome_manifest *m, const char *filepath, const sid_t *authorSidp, rhizome_bk_t *bsk) +int rhizome_fill_manifest(rhizome_manifest *m, const char *filepath, const sid_t *authorSidp) { /* Fill in a few missing manifest fields, to make it easier to use when adding new files: - - the default service is FILE - - use the current time for "date" + - use the current time for "date" and "version" - if service is file, then use the payload file's basename for "name" */ - /* Set version of manifest, either from version variable, or using current time */ + /* Set version of manifest from current time if not already set. */ if (m->version == 0) rhizome_manifest_set_version(m, gettime_ms()); - /* Set the manifest's author (not stored). This must be done before binding to a new ID (below). - * If no author was specified, then the manifest's "sender" field is used, if present. + /* Set the manifest's author. This must be done before binding to a new ID (below). If no author + * was specified, then the manifest's "sender" field is used, if present. */ if (authorSidp) rhizome_manifest_set_author(m, authorSidp); else if (m->has_sender) rhizome_manifest_set_author(m, &m->sender); - if (!m->haveSecret) { - if (rhizome_bid_t_is_zero(m->cryptoSignPublic)) { - if (config.debug.rhizome) - DEBUG("creating new bundle"); - if (rhizome_manifest_bind_id(m) == -1) - return WHY("Could not bind manifest to an ID"); - } else { - if (config.debug.rhizome) - DEBUGF("modifying existing bundle bid=%s", alloca_tohex_rhizome_bid_t(m->cryptoSignPublic)); - // Modifying an existing bundle. Make sure we can find the bundle secret. - if (rhizome_extract_privatekey_required(m, bsk) == -1) - return -1; - // TODO assert that new version > old version? - } + /* Set the bundle ID (public key) and secret key. + */ + if (!m->haveSecret && rhizome_bid_t_is_zero(m->cryptoSignPublic)) { + if (config.debug.rhizome) + DEBUG("creating new bundle"); + if (rhizome_manifest_createid(m) == -1) + return WHY("Could not bind manifest to an ID"); + if (m->authorship != ANONYMOUS) + rhizome_manifest_add_bundle_key(m); // set the BK field + } else { + if (config.debug.rhizome) + DEBUGF("modifying existing bundle bid=%s", alloca_tohex_rhizome_bid_t(m->cryptoSignPublic)); + // Modifying an existing bundle. Try to discover the bundle secret key and the author. + rhizome_authenticate_author(m); + // TODO assert that new version > old version? } - assert(m->haveSecret); if (m->service == NULL) return WHYF("missing 'service'"); @@ -981,32 +1043,49 @@ int rhizome_fill_manifest(rhizome_manifest *m, const char *filepath, const sid_t return 0; } +/* Work out the authorship status of the bundle without performing any cryptographic checks. + * Sets the 'authorship' element and returns 1 if an author was found, 0 if not. + * + * @author Andrew Bettison + */ int rhizome_lookup_author(rhizome_manifest *m) { + IN(); + int cn, in, kp; switch (m->authorship) { case AUTHOR_NOT_CHECKED: - if (m->has_author) { - int cn = 0, in = 0, kp = 0; - if (keyring_find_sid(keyring, &cn, &in, &kp, &m->author)) { - m->authorship = AUTHOR_LOCAL; - return 1; - } + if (config.debug.rhizome) + DEBUGF("manifest[%d] lookup author=%s", m->manifest_record_number, alloca_tohex_sid_t(m->author)); + cn = 0, in = 0, kp = 0; + if (keyring_find_sid(keyring, &cn, &in, &kp, &m->author)) { + if (config.debug.rhizome) + DEBUGF("found author"); + m->authorship = AUTHOR_LOCAL; + RETURN(1); } + // fall through + case ANONYMOUS: if (m->has_sender) { - int cn = 0, in = 0, kp = 0; + if (config.debug.rhizome) + DEBUGF("manifest[%d] lookup sender=%s", m->manifest_record_number, alloca_tohex_sid_t(m->sender)); + cn = 0, in = 0, kp = 0; if (keyring_find_sid(keyring, &cn, &in, &kp, &m->sender)) { - m->author = m->sender; + if (config.debug.rhizome) + DEBUGF("found sender"); + rhizome_manifest_set_author(m, &m->sender); m->authorship = AUTHOR_LOCAL; - return 1; + RETURN(1); } } - case AUTHOR_ERROR: + case AUTHENTICATION_ERROR: case AUTHOR_UNKNOWN: case AUTHOR_IMPOSTOR: - return 0; + RETURN(0); case AUTHOR_LOCAL: case AUTHOR_AUTHENTIC: - return 1; + RETURN(1); } - FATAL("m->authorship = %d", m->authorship); + FATALF("m->authorship = %d", m->authorship); + RETURN(0); + OUT(); } diff --git a/rhizome_crypto.c b/rhizome_crypto.c index 3b76c585..1fb9c28b 100644 --- a/rhizome_crypto.c +++ b/rhizome_crypto.c @@ -44,6 +44,8 @@ int rhizome_manifest_createid(rhizome_manifest *m) return WHY("Failed to create keypair for manifest ID."); rhizome_manifest_set_id(m, &m->cryptoSignPublic); m->haveSecret = NEW_BUNDLE_ID; + // A new Bundle ID and secret invalidates any existing BK field. + rhizome_manifest_del_bundle_key(m); return 0; } @@ -60,7 +62,7 @@ static int generate_keypair(const char *seed, struct signing_key *key) // The first 256 bits (32 bytes) of the hash will be used as the private key of the BID. bcopy(hash, key->Private, sizeof key->Private); - if (crypto_sign_compute_public_key(key->Private, key->Public.binary)) + if (crypto_sign_compute_public_key(key->Private, key->Public.binary) == -1) return WHY("Could not generate public key"); // The last 32 bytes of the private key should be identical to the public key. This is what // crypto_sign_edwards25519sha512batch_keypair() returns, and there is code that depends on it. @@ -81,13 +83,13 @@ int rhizome_get_bundle_from_seed(rhizome_manifest *m, const char *seed) return -1; if (ret == 1) { // manifest not retrieved - rhizome_manifest_set_id(m, &key.Public); + rhizome_manifest_set_id(m, &key.Public); // zerofills m->cryptoSignSecret m->haveSecret = NEW_BUNDLE_ID; } else { m->haveSecret = EXISTING_BUNDLE_ID; } bcopy(key.Private, m->cryptoSignSecret, sizeof m->cryptoSignSecret); - //Disabled for performance, but these asserts should always hold. + // Disabled for performance, these asserts should nevertheless always hold. //assert(cmp_rhizome_bid_t(&m->cryptoSignPublic, &key.Public) == 0); //assert(memcmp(m->cryptoSignPublic.binary, m->cryptoSignSecret + RHIZOME_BUNDLE_KEY_BYTES, sizeof m->cryptoSignPublic.binary) == 0); return ret; @@ -124,10 +126,12 @@ int rhizome_bk_xor_stream( return 0; } -/* - CryptoSign Secret Keys in cupercop-20120525 onwards have the public key as the - second half of the secret key. The public key is the BID, so this simplifies - the BK<-->SECRET conversion processes. */ +/* CryptoSign Secret Keys in cupercop-20120525 onwards have the public key as the second half of the + * secret key. The public key is the BID, so this simplifies the BK<-->SECRET conversion processes. + * + * Returns 0 if the BK decodes correctly to the bundle secret, 1 if not. Returns -1 if there is an + * error. + */ int rhizome_bk2secret(rhizome_manifest *m, const rhizome_bid_t *bidp, const unsigned char *rs, const size_t rs_len, @@ -138,21 +142,16 @@ int rhizome_bk2secret(rhizome_manifest *m, { IN(); unsigned char xor_stream[RHIZOME_BUNDLE_KEY_BYTES]; - if (rhizome_bk_xor_stream(bidp,rs,rs_len,xor_stream,RHIZOME_BUNDLE_KEY_BYTES)) + if (rhizome_bk_xor_stream(bidp, rs, rs_len, xor_stream, RHIZOME_BUNDLE_KEY_BYTES)) RETURN(WHY("rhizome_bk_xor_stream() failed")); - - int i; - /* XOR and store secret part of secret key */ - for(i = 0; i != RHIZOME_BUNDLE_KEY_BYTES; i++) + unsigned i; + for (i = 0; i != RHIZOME_BUNDLE_KEY_BYTES; ++i) secret[i] = bkin[i] ^ xor_stream[i]; - /* Copy BID as public-key part of secret key */ - for(;i!=crypto_sign_edwards25519sha512batch_SECRETKEYBYTES;++i) - secret[i] = bidp->binary[i - RHIZOME_BUNDLE_KEY_BYTES]; - bzero(xor_stream, sizeof xor_stream); - - RETURN(rhizome_verify_bundle_privatekey(m, secret, bidp->binary)); + /* Copy BID as public-key part of secret key */ + bcopy(bidp->binary, secret + RHIZOME_BUNDLE_KEY_BYTES, sizeof bidp->binary); + RETURN(rhizome_verify_bundle_privatekey(secret, bidp->binary) ? 0 : 1); OUT(); } @@ -181,243 +180,245 @@ int rhizome_secret2bk( } -/* Given the SID of a bundle's author, search for an identity in the keyring and return its - * Rhizome secret if found. +/* Given a SID, search the keyring for an identity with the same SID and return its Rhizome secret + * if found. * - * Returns -1 if an error occurs. - * Returns 0 if the author's rhizome secret is found; '*rs' is set to point to the secret key in the - * keyring, and '*rs_len' is set to the key length. - * Returns 2 if the author's identity is not in the keyring. - * Returns 3 if the author's identity is in the keyring but has no rhizome secret. + * Returns FOUND_RHIZOME_SECRET if the author's rhizome secret is found; '*rs' is set to point to + * the secret key in the keyring, and '*rs_len' is set to the key length. + * + * Returns IDENTITY_NOT_FOUND if the SID is not in the keyring. + * + * Returns IDENTITY_HAS_NO_RHIZOME_SECRET if the SID is in the keyring but has no Rhizome Secret. * * @author Andrew Bettison */ -int rhizome_find_secret(const sid_t *authorSidp, int *rs_len, const unsigned char **rs) +enum rhizome_secret_disposition find_rhizome_secret(const sid_t *authorSidp, size_t *rs_len, const unsigned char **rs) { + IN(); int cn=0, in=0, kp=0; if (!keyring_find_sid(keyring,&cn,&in,&kp, authorSidp)) { if (config.debug.rhizome) DEBUGF("identity sid=%s is not in keyring", alloca_tohex_sid_t(*authorSidp)); - return 2; + RETURN(IDENTITY_NOT_FOUND); } kp = keyring_identity_find_keytype(keyring, cn, in, KEYTYPE_RHIZOME); if (kp == -1) { - if (config.debug.rhizome) - DEBUGF("identity sid=%s has no Rhizome Secret", alloca_tohex_sid_t(*authorSidp)); - return 3; + WARNF("Identity sid=%s has no Rhizome Secret", alloca_tohex_sid_t(*authorSidp)); + RETURN(IDENTITY_HAS_NO_RHIZOME_SECRET); } int rslen = keyring->contexts[cn]->identities[in]->keypairs[kp]->private_key_len; - if (rslen < 16 || rslen > 1024) - return WHYF("identity sid=%s has invalid Rhizome Secret: length=%d", alloca_tohex_sid_t(*authorSidp), rslen); + assert(rslen >= 16); + assert(rslen <= 1024); if (rs_len) *rs_len = rslen; if (rs) *rs = keyring->contexts[cn]->identities[in]->keypairs[kp]->private_key; - return 0; + RETURN(FOUND_RHIZOME_SECRET); } -/* Given the SID of a bundle's author and the bundle ID, XOR a bundle key (private or public) with - * RS##BID where RS is the rhizome secret of the bundle's author, and BID is the bundle's public key - * (aka the Bundle ID). - * - * This will convert a manifest BK field into the bundle's private key, or vice versa. - * - * Returns -1 if an error occurs. - * Returns 0 if the author's private key is located and the XOR is performed successfully. - * Returns 2 if the author's identity is not in the keyring (this return code from - * rhizome_find_secret()). - * Returns 3 if the author's identity is in the keyring but has no rhizome secret (this return code - * from rhizome_find_secret()). - * - * Looks up the SID in the keyring, and if it is present and has a valid-looking RS, calls - * rhizome_bk_xor_rs() to perform the XOR. +/* Attempt to authenticate the authorship of the given bundle, and set the 'authorship' element + * accordingly. If the manifest has nk BK field, then no authentication can be performed. * * @author Andrew Bettison */ - -/* See if the manifest has a BK entry, and if so, use it to obtain the private key for the BID. The - * manifest's 'author' field must contain the (binary) SID of the purported author of the bundle, - * which is used to look up the author's rhizome secret in the keyring. - * - * Returns 0 if a valid private key was extracted, with the private key in the manifest - * 'cryptoSignSecret' field and the 'haveSecret' field set to EXISTING_BUNDLE_ID. - * - * Returns 1 if the manifest does not have a BK field. - * - * Returns 2 if the author is not found in the keyring (not unlocked?) -- this return code from - * rhizome_bk_xor(). - * - * Returns 3 if the author is found in the keyring but has no rhizome secret -- this return code - * from rhizome_bk_xor(). - * - * Returns 4 if the author is found in the keyring and has a rhizome secret but the private bundle - * key formed using it does not verify. - * - * Returns -1 on error. - * - * @author Andrew Bettison - - */ -int rhizome_extract_privatekey(rhizome_manifest *m, const rhizome_bk_t *bsk) +void rhizome_authenticate_author(rhizome_manifest *m) { - if (config.debug.rhizome) - DEBUGF("manifest[%d] bsk=%s", m->manifest_record_number, bsk ? alloca_tohex_rhizome_bk_t(*bsk) : "NULL"); IN(); - int result; - if (m->has_bundle_key) { - if (!m->has_author) { - result = rhizome_find_bundle_author(m); - } else { - int rs_len; - const unsigned char *rs; - result = rhizome_find_secret(&m->author, &rs_len, &rs); - if (result == 0) - result = rhizome_bk2secret(m, &m->cryptoSignPublic, rs, rs_len, m->bundle_key.binary, m->cryptoSignSecret); - } - if (result == 0 && bsk && !rhizome_is_bk_none(bsk)){ - // If a bundle secret key was supplied that does not match the secret key derived from the - // author, then warn but carry on using the author's. - if (memcmp(bsk->binary, m->cryptoSignSecret, sizeof bsk->binary) != 0) - WARNF("Supplied bundle secret key is invalid -- ignoring"); - } - }else if (bsk && !rhizome_is_bk_none(bsk)){ - bcopy(bsk->binary, m->cryptoSignSecret, sizeof bsk->binary); - bcopy(m->cryptoSignPublic.binary, m->cryptoSignSecret + sizeof bsk->binary, sizeof m->cryptoSignPublic.binary); - if (rhizome_verify_bundle_privatekey(m, m->cryptoSignSecret, m->cryptoSignPublic.binary)) - result=5; - else - result=0; - }else{ - result=1; + if (!m->has_bundle_key) + RETURNVOID; + switch (m->authorship) { + case ANONYMOUS: + rhizome_find_bundle_author_and_secret(m); + break; + case AUTHOR_NOT_CHECKED: + case AUTHOR_LOCAL: { + if (config.debug.rhizome) + DEBUGF("manifest[%d] authenticate author=%s", m->manifest_record_number, alloca_tohex_sid_t(m->author)); + size_t rs_len; + const unsigned char *rs; + enum rhizome_secret_disposition d = find_rhizome_secret(&m->author, &rs_len, &rs); + switch (d) { + case FOUND_RHIZOME_SECRET: + if (config.debug.rhizome) + DEBUGF("author has Rhizome secret"); + switch (rhizome_bk2secret(m, &m->cryptoSignPublic, rs, rs_len, m->bundle_key.binary, m->cryptoSignSecret)) { + case 0: + if (config.debug.rhizome) + DEBUGF("authentic"); + m->authorship = AUTHOR_AUTHENTIC; + if (!m->haveSecret) + m->haveSecret = EXISTING_BUNDLE_ID; + break; + case -1: + if (config.debug.rhizome) + DEBUGF("error"); + m->authorship = AUTHENTICATION_ERROR; + break; + default: + if (config.debug.rhizome) + DEBUGF("impostor"); + m->authorship = AUTHOR_IMPOSTOR; + break; + } + break; + case IDENTITY_NOT_FOUND: + if (config.debug.rhizome) + DEBUGF("author not found"); + m->authorship = AUTHOR_UNKNOWN; + break; + case IDENTITY_HAS_NO_RHIZOME_SECRET: + if (config.debug.rhizome) + DEBUGF("author has no Rhizome secret"); + m->authorship = AUTHENTICATION_ERROR; + break; + default: + FATALF("find_rhizome_secret() returned unknown code %d", (int)d); + break; + } + } + break; + case AUTHENTICATION_ERROR: + case AUTHOR_UNKNOWN: + case AUTHOR_IMPOSTOR: + case AUTHOR_AUTHENTIC: + // work has already been done, don't repeat it + break; + default: + FATALF("m->authorship = %d", (int)m->authorship); + break; } - if (result == 0) - m->haveSecret = EXISTING_BUNDLE_ID; - else { - memset(m->cryptoSignSecret, 0, sizeof m->cryptoSignSecret); - m->haveSecret = SECRET_UNKNOWN; - } - RETURN(result); OUT(); } -/* Same as rhizome_extract_privatekey, except warnings become errors and are logged */ -int rhizome_extract_privatekey_required(rhizome_manifest *m, rhizome_bk_t *bsk) +/* If the given bundle secret key corresponds to the bundle's ID (public key) then store it in the + * manifest structure and mark the secret key as known. Return 1 if the secret key was assigned, + * 0 if not. + * + * This function should only be called on a manifest that already has a public key (ID) and does + * not have a known secret key. + * + * @author Andrew Bettison + */ +int rhizome_apply_bundle_secret(rhizome_manifest *m, const rhizome_bk_t *bsk) { - int result = rhizome_extract_privatekey(m, bsk); - switch (result) { - case -1: - case 0: - return result; - case 1: - return WHY("Bundle contains no BK field, and no bundle secret supplied"); - case 2: - return WHY("Author unknown"); - case 3: - return WHY("Author does not have a Rhizome Secret"); - case 4: - return WHY("Author does not have permission to modify manifest"); - case 5: - return WHY("Bundle secret is not valid for this manifest"); - default: - return WHYF("Unknown result from rhizome_extract_privatekey(): %d", result); + IN(); + if (config.debug.rhizome) + DEBUGF("manifest[%d] bsk=%s", m->manifest_record_number, bsk ? alloca_tohex_rhizome_bk_t(*bsk) : "NULL"); + assert(m->haveSecret == SECRET_UNKNOWN); + assert(is_all_matching(m->cryptoSignSecret, sizeof m->cryptoSignSecret, 0)); + assert(!rhizome_bid_t_is_zero(m->cryptoSignPublic)); + assert(bsk != NULL); + assert(!rhizome_is_bk_none(bsk)); + if (rhizome_verify_bundle_privatekey(bsk->binary, m->cryptoSignPublic.binary)) { + if (config.debug.rhizome) + DEBUG("bundle secret verifies ok"); + bcopy(bsk->binary, m->cryptoSignSecret, sizeof bsk->binary); + bcopy(m->cryptoSignPublic.binary, m->cryptoSignSecret + sizeof bsk->binary, sizeof m->cryptoSignPublic.binary); + m->haveSecret = EXISTING_BUNDLE_ID; + RETURN(1); } + RETURN(0); + OUT(); } /* Discover if the given manifest was created (signed) by any unlocked identity currently in the * keyring. * - * Returns 0 if an identity is found with permission to alter the bundle, after setting the manifest - * 'author' field to the SID of the identity and the manifest 'cryptoSignSecret' field to the bundle - * secret key and the 'haveSecret' field to EXISTING_BUNDLE_ID. + * This function must only be called if the bundle secret is not known. If it is known, then + * use * - * Returns 1 if no identity in the keyring is the author of this bundle. + * If the authorship is already known (ie, not ANONYMOUS) then returns without changing anything. + * That means this function can be called several times on the same manifest, but will only perform + * any work the first time. * - * Returns 4 if the manifest has no BK field. + * If the manifest has no bundle key (BK) field, then it is anonymous, so leaves 'authorship' + * unchanged and returns. * - * Returns -1 if an error occurs, eg, the manifest contains an invalid BK field. + * If an identity is found in the keyring with permission to alter the bundle, then sets the + * manifest 'authorship' field to AUTHOR_AUTHENTIC, the 'author' field to the SID of the identity, + * the manifest 'cryptoSignSecret' field to the bundle secret key and the 'haveSecret' field to + * EXISTING_BUNDLE_ID. + * + * If no identity is found in the keyring that combines with the bundle key (BK) field to yield + * the bundle's secret key, then leaves the manifest 'authorship' field as ANONYMOUS. + * + * If an error occurs, eg, the keyring contains an invalid Rhizome Secret or a cryptographic + * operation fails, then sets the 'authorship' field to AUTHENTICATION_ERROR and leaves the + * 'author', 'haveSecret' and 'cryptoSignSecret' fields unchanged. * * @author Andrew Bettison */ -int rhizome_find_bundle_author(rhizome_manifest *m) +void rhizome_find_bundle_author_and_secret(rhizome_manifest *m) { IN(); - if (!m->has_bundle_key) { - if (config.debug.rhizome) - DEBUG("missing BK"); - RETURN(4); - } + if (m->authorship != ANONYMOUS) + RETURNVOID; + assert(is_sid_t_any(m->author)); + if (!m->has_bundle_key) + RETURNVOID; int cn = 0, in = 0, kp = 0; for (; keyring_next_identity(keyring, &cn, &in, &kp); ++kp) { const sid_t *authorSidp = (const sid_t *) keyring->contexts[cn]->identities[in]->keypairs[kp]->public_key; //if (config.debug.rhizome) DEBUGF("try author identity sid=%s", alloca_tohex_sid_t(*authorSidp)); int rkp = keyring_identity_find_keytype(keyring, cn, in, KEYTYPE_RHIZOME); if (rkp != -1) { - int rs_len = keyring->contexts[cn]->identities[in]->keypairs[rkp]->private_key_len; - if (rs_len < 16 || rs_len > 1024) - RETURN(WHYF("invalid Rhizome Secret: length=%d", rs_len)); + size_t rs_len = keyring->contexts[cn]->identities[in]->keypairs[rkp]->private_key_len; + if (rs_len < 16 || rs_len > 1024) { + WHYF("invalid Rhizome Secret: length=%zu", rs_len); + m->authorship = AUTHENTICATION_ERROR; + RETURNVOID; + } const unsigned char *rs = keyring->contexts[cn]->identities[in]->keypairs[rkp]->private_key; - if (rhizome_bk2secret(m, &m->cryptoSignPublic, rs, rs_len, m->bundle_key.binary, m->cryptoSignSecret) == 0) { - m->haveSecret = EXISTING_BUNDLE_ID; - if (!m->has_author || cmp_sid_t(&m->author, authorSidp) != 0){ - if (config.debug.rhizome) - DEBUGF("found bundle author sid=%s", alloca_tohex_sid_t(*authorSidp)); - rhizome_manifest_set_author(m, authorSidp); - // if this bundle is already in the database, update the author. - if (m->inserttime) - sqlite_exec_void_loglevel(LOG_LEVEL_WARN, - "UPDATE MANIFESTS SET author = ? WHERE id = ?;", - SID_T, &m->author, - RHIZOME_BID_T, &m->cryptoSignPublic, - END); - } - RETURN(0); // bingo + unsigned char *secretp = m->cryptoSignSecret; + if (m->haveSecret) + secretp = alloca(sizeof m->cryptoSignSecret); + if (rhizome_bk2secret(m, &m->cryptoSignPublic, rs, rs_len, m->bundle_key.binary, secretp) == 0) { + if (m->haveSecret) { + if (memcmp(secretp, m->cryptoSignSecret, sizeof m->cryptoSignSecret) != 0) + FATALF("Bundle secret does not match derived secret"); + } else + m->haveSecret = EXISTING_BUNDLE_ID; + if (config.debug.rhizome) + DEBUGF("found bundle author sid=%s", alloca_tohex_sid_t(*authorSidp)); + rhizome_manifest_set_author(m, authorSidp); + m->authorship = AUTHOR_AUTHENTIC; + // if this bundle is already in the database, update the author. + if (m->inserttime) + sqlite_exec_void_loglevel(LOG_LEVEL_WARN, + "UPDATE MANIFESTS SET author = ? WHERE id = ?;", + SID_T, &m->author, + RHIZOME_BID_T, &m->cryptoSignPublic, + END); + RETURNVOID; // bingo } } } + assert(m->authorship == ANONYMOUS); if (config.debug.rhizome) DEBUG("bundle author not found"); - RETURN(1); OUT(); } -/* Verify the validity of the manifest's secret key, ie, is the given manifest's 'cryptoSignSecret' - * field actually the secret key corresponding to the public key in 'cryptoSignPublic'? - * Return 0 if valid, 1 if not. Return -1 if an error occurs. +/* Verify the validity of a given secret manifest key. Return 1 if valid, 0 if not. * * There is no NaCl API to efficiently test this. We use a modified version of * crypto_sign_keypair() to accomplish this task. */ -int rhizome_verify_bundle_privatekey(rhizome_manifest *m, - const unsigned char *sk, - const unsigned char *pkin) +int rhizome_verify_bundle_privatekey(const unsigned char *sk, const unsigned char *pkin) { IN(); - unsigned char pk[32]; - int i; - crypto_sign_compute_public_key(sk,pk); - for (i = 0;i < 32;++i) - if (pkin[i] != pk[i]) { - if (m&&sk==m->cryptoSignSecret&&pkin==m->cryptoSignPublic.binary) - m->haveSecret = SECRET_UNKNOWN; - RETURN(-1); - } - if (m&&sk==m->cryptoSignSecret&&pkin==m->cryptoSignPublic.binary) { - if (config.debug.rhizome) - DEBUGF("We have the private key for this bundle."); - m->haveSecret = EXISTING_BUNDLE_ID; - } - RETURN(0); - OUT(); + rhizome_bid_t pk; + if (crypto_sign_compute_public_key(sk, pk.binary) == -1) + RETURN(0); + int ret = bcmp(pkin, pk.binary, sizeof pk.binary) == 0; + RETURN(ret); } -int rhizome_sign_hash(rhizome_manifest *m, - rhizome_signature *out) +int rhizome_sign_hash(rhizome_manifest *m, rhizome_signature *out) { IN(); - if (!m->haveSecret && rhizome_extract_privatekey_required(m, NULL)) - RETURN(-1); - - int ret=rhizome_sign_hash_with_key(m, m->cryptoSignSecret, m->cryptoSignPublic.binary, out); + assert(m->haveSecret); + int ret = rhizome_sign_hash_with_key(m, m->cryptoSignSecret, m->cryptoSignPublic.binary, out); RETURN(ret); OUT(); } @@ -627,7 +628,7 @@ int rhizome_crypt_xor_block(unsigned char *buffer, size_t buffer_size, uint64_t return 0; } -int rhizome_derive_key(rhizome_manifest *m, rhizome_bk_t *bsk) +int rhizome_derive_payload_key(rhizome_manifest *m) { // don't do anything if the manifest isn't flagged as being encrypted if (m->payloadEncryption != PAYLOAD_ENCRYPTED) @@ -655,9 +656,8 @@ int rhizome_derive_key(rhizome_manifest *m, rhizome_bk_t *bsk) bcopy(hash, m->payloadKey, RHIZOME_CRYPT_KEY_BYTES); }else{ - if (!m->haveSecret && rhizome_extract_privatekey_required(m, bsk)) - return -1; - assert(m->haveSecret); + if (!m->haveSecret) + return WHY("Cannot derive payload key because bundle secret is unknown"); unsigned char raw_key[9+crypto_sign_edwards25519sha512batch_SECRETKEYBYTES]="sasquatch"; bcopy(m->cryptoSignSecret, &raw_key[9], crypto_sign_edwards25519sha512batch_SECRETKEYBYTES); diff --git a/rhizome_database.c b/rhizome_database.c index b9ef4f79..f6791e30 100644 --- a/rhizome_database.c +++ b/rhizome_database.c @@ -167,7 +167,8 @@ void verify_bundles(){ ret=rhizome_store_bundle(m); } if (ret!=0){ - DEBUGF("Removing invalid manifest entry @%lld", rowid); + if (config.debug.rhizome) + DEBUGF("Removing invalid manifest entry @%lld", rowid); sqlite_exec_void_retry(&retry, "DELETE FROM MANIFESTS WHERE ROWID = ?;", INT64, rowid, END); } rhizome_manifest_free(m); @@ -1278,15 +1279,9 @@ int rhizome_store_bundle(rhizome_manifest *m) if (!m->finalised) return WHY("Manifest was not finalised"); - if (m->haveSecret) { - /* We used to store the secret in the database, but we don't anymore, as we use - the BK field in the manifest. So nothing to do here. */ - } else { - /* We don't have the secret for this manifest, so only allow updates if - the self-signature is valid */ - if (!m->selfSigned) - return WHY("Manifest is not signed, and I don't have the key. Manifest might be forged or corrupt."); - } + // If we don't have the secret for this manifest, only store it if its self-signature is valid + if (!m->haveSecret && !m->selfSigned) + return WHY("Manifest is not signed, and I don't have the key. Manifest might be forged or corrupt."); /* Bind BAR to data field */ unsigned char bar[RHIZOME_BAR_BYTES]; @@ -1301,6 +1296,8 @@ int rhizome_store_bundle(rhizome_manifest *m) if (sqlite_exec_void_retry(&retry, "BEGIN TRANSACTION;", END) == -1) return WHY("Failed to begin transaction"); + time_ms_t now = gettime_ms(); + sqlite3_stmt *stmt; if ((stmt = sqlite_prepare_bind(&retry, "INSERT OR REPLACE INTO MANIFESTS(" @@ -1323,11 +1320,12 @@ int rhizome_store_bundle(rhizome_manifest *m) RHIZOME_BID_T, &m->cryptoSignPublic, STATIC_BLOB, m->manifestdata, m->manifest_bytes, INT64, m->version, - INT64, (int64_t) gettime_ms(), + INT64, (int64_t) now, STATIC_BLOB, bar, RHIZOME_BAR_BYTES, INT64, m->filesize, RHIZOME_FILEHASH_T|NUL, m->filesize > 0 ? &m->filehash : NULL, - SID_T|NUL, m->has_author ? &m->author : NULL, + // Only store the author if it is known to be authentic. + SID_T|NUL, m->authorship == AUTHOR_AUTHENTIC ? &m->author : NULL, STATIC_TEXT, m->service, STATIC_TEXT|NUL, m->name, SID_T|NUL, m->has_sender ? &m->sender : NULL, @@ -1341,6 +1339,7 @@ int rhizome_store_bundle(rhizome_manifest *m) goto rollback; sqlite3_finalize(stmt); stmt = NULL; + rhizome_manifest_set_inserttime(m, now); // if (serverMode) // rhizome_sync_bundle_inserted(bar); @@ -1414,17 +1413,17 @@ struct rhizome_list_cursor { const char *name; sid_t sender; sid_t recipient; - unsigned limit; - unsigned offset; // Set by calling the next() function. - rhizome_manifest *manifest; - size_t row; int64_t rowid; + rhizome_manifest *manifest; + size_t rowcount; // Private state. sqlite3_stmt *_statement; + unsigned _offset; }; -/* Fill in the parameters in the cursor struct prior to calling this function. +/* The cursor struct must be zerofilled and the query parameters optionally filled in prior to + * calling this function. * * @author Andrew Bettison */ @@ -1441,23 +1440,22 @@ static int rhizome_list_open(sqlite_retry_state *retry, struct rhizome_list_curs strbuf_puts(b, " AND sender = @sender"); if (!is_sid_t_any(cursor->recipient)) strbuf_puts(b, " AND recipient = @recipient"); - strbuf_puts(b, " ORDER BY inserttime DESC OFFSET @offset"); + strbuf_puts(b, " ORDER BY inserttime DESC LIMIT -1 OFFSET @offset"); if (strbuf_overrun(b)) RETURN(WHYF("SQL command too long: %s", strbuf_str(b))); - cursor->_statement = sqlite_prepare_bind(retry, strbuf_str(b)); - if (!statement) + cursor->_statement = sqlite_prepare(retry, strbuf_str(b)); + if (cursor->_statement == NULL) RETURN(-1); - int ret = 0; - if (cursor->service && *cursor->service && sqlite_bind(retry, statement, NAMED|STATIC_TEXT, "service", cursor->service, END) == -1) + if (sqlite_bind(retry, cursor->_statement, NAMED|INT, "@offset", cursor->_offset, END) == -1) goto failure; - if (cursor->name && *cursor->name && sqlite_bind(retry, statement, NAMED|STATIC_TEXT, "name", cursor->name, END) == -1) + if (cursor->service && sqlite_bind(retry, cursor->_statement, NAMED|STATIC_TEXT, "@service", cursor->service, END) == -1) goto failure; - if (!is_sid_t_any(cursor->sender) && sqlite_bind(retry, statement, NAMED|SID_T, "sender", &cursor->sender, END) == -1) + if (cursor->name && sqlite_bind(retry, cursor->_statement, NAMED|STATIC_TEXT, "@name", cursor->name, END) == -1) goto failure; - if (!is_sid_t_any(cursor->recipient) && sqlite_bind(retry, statement, NAMED|SID_T, "recipient", &cursor->recipient, END) == -1) + if (!is_sid_t_any(cursor->sender) && sqlite_bind(retry, cursor->_statement, NAMED|SID_T, "@sender", &cursor->sender, END) == -1) + goto failure; + if (!is_sid_t_any(cursor->recipient) && sqlite_bind(retry, cursor->_statement, NAMED|SID_T, "@recipient", &cursor->recipient, END) == -1) goto failure; - if (sqlite_bind(retry, statement, NAMED|INT, "offset", cursor->offset + cursor->_row, END) == -1) - goto cleanup; cursor->manifest = NULL; RETURN(0); OUT(); @@ -1473,9 +1471,8 @@ static int rhizome_list_next(sqlite_retry_state *retry, struct rhizome_list_curs IN(); if (cursor->_statement == NULL && rhizome_list_open(retry, cursor) == -1) RETURN(-1); - if (cursor->limit && cursor->_row >= cursor->limit) - RETURN(NULL); while (sqlite_step_retry(retry, cursor->_statement) == SQLITE_ROW) { + ++cursor->_offset; if (cursor->manifest) { rhizome_manifest_free(cursor->manifest); cursor->manifest = NULL; @@ -1487,21 +1484,14 @@ static int rhizome_list_next(sqlite_retry_state *retry, struct rhizome_list_curs assert(sqlite3_column_type(cursor->_statement, 3) == SQLITE_INTEGER); assert(sqlite3_column_type(cursor->_statement, 4) == SQLITE_TEXT || sqlite3_column_type(cursor->_statement, 4) == SQLITE_NULL); assert(sqlite3_column_type(cursor->_statement, 5) == SQLITE_INTEGER); - rhizome_manifest *m = cursor->manifest = rhizome_new_manifest(); - if (m == NULL) - RETURN(-1); const char *q_manifestid = (const char *) sqlite3_column_text(cursor->_statement, 0); const char *manifestblob = (char *) sqlite3_column_blob(cursor->_statement, 1); size_t manifestblobsize = sqlite3_column_bytes(cursor->_statement, 1); // must call after sqlite3_column_blob() int64_t q_version = sqlite3_column_int64(cursor->_statement, 2); int64_t q_inserttime = sqlite3_column_int64(cursor->_statement, 3); const char *q_author = (const char *) sqlite3_column_text(cursor->_statement, 4); - int64_t rowid = sqlite3_column_int64(cursor->_statement, 5); - if (rhizome_read_manifest_file(m, manifestblob, manifestblobsize) == -1) { - WHYF("MANIFESTS row id=%s has invalid manifest blob -- skipped", q_manifestid); - continue; - } - const sid_t *author = NULL; + cursor->rowid = sqlite3_column_int64(cursor->_statement, 5); + sid_t *author = NULL; if (q_author) { author = alloca(sizeof *author); if (str_to_sid_t(author, q_author) == -1) { @@ -1509,8 +1499,11 @@ static int rhizome_list_next(sqlite_retry_state *retry, struct rhizome_list_curs continue; } } - if (rhizome_fill_manifest(m, NULL, author, NULL) == -1) { - WHYF("MANIFESTS row id=%s has invalid manifest -- skipped", q_manifestid); + rhizome_manifest *m = cursor->manifest = rhizome_new_manifest(); + if (m == NULL) + RETURN(-1); + if (rhizome_read_manifest_file(m, manifestblob, manifestblobsize) == -1) { + WHYF("MANIFESTS row id=%s has invalid manifest blob -- skipped", q_manifestid); continue; } if (m->version != q_version) { @@ -1518,23 +1511,25 @@ static int rhizome_list_next(sqlite_retry_state *retry, struct rhizome_list_curs q_manifestid, q_version, m->version); continue; } + if (author) + rhizome_manifest_set_author(m, author); + rhizome_manifest_set_inserttime(m, q_inserttime); if (cursor->service && !(m->service && strcasecmp(cursor->service, m->service) == 0)) continue; if (!is_sid_t_any(cursor->sender) && !(m->has_sender && cmp_sid_t(&cursor->sender, &m->sender) == 0)) continue; if (!is_sid_t_any(cursor->recipient) && !(m->has_recipient && cmp_sid_t(&cursor->recipient, &m->recipient) == 0)) continue; - rhizome_lookup_author(m); // Don't do rhizome_verify_author(m); too CPU expensive for a listing. Save that for when // the bundle is extracted or exported. - ++cursor->_row; + ++cursor->rowcount; RETURN(1); } RETURN(0); OUT(); } -static void rhizome_list_release(struct rhizome_list_cursor *) +static void rhizome_list_release(struct rhizome_list_cursor *cursor) { if (cursor->manifest) { rhizome_manifest_free(cursor->manifest); @@ -1548,14 +1543,16 @@ static void rhizome_list_release(struct rhizome_list_cursor *) int rhizome_list_manifests(struct cli_context *context, const char *service, const char *name, const char *sender_hex, const char *recipient_hex, - int limit, int offset, char count_rows) + size_t rowlimit, size_t rowoffset, char count_rows) { IN(); struct rhizome_list_cursor cursor; bzero(&cursor, sizeof cursor); - if (sender_hex && *sender_hex && str_to_sid_t(&cursor->sender, sender_hex) == -1) + cursor.service = service && service[0] ? service : NULL; + cursor.name = name && name[0] ? name : NULL; + if (sender_hex && *sender_hex && str_to_sid_t(&cursor.sender, sender_hex) == -1) RETURN(WHYF("Invalid sender SID: %s", sender_hex)); - if (recipient_hex && *recipient_hex && str_to_sid_t(&cursor->recipient, recipient_hex) == -1) + if (recipient_hex && *recipient_hex && str_to_sid_t(&cursor.recipient, recipient_hex) == -1) RETURN(WHYF("Invalid recipient SID: %s", recipient_hex)); sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; if (rhizome_list_open(&retry, &cursor) == -1) @@ -1575,33 +1572,42 @@ int rhizome_list_manifests(struct cli_context *context, const char *service, con "recipient", "name" }; - cli_columns(context, 13, names); + cli_columns(context, NELS(names), names); while (rhizome_list_next(&retry, &cursor) == 1) { - cli_put_long(context, cursor.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, q_inserttime, ":"); - cli_put_hexvalue(context, m->has_author ? m->author.binary : NULL, sizeof m->author.binary, ":"); - cli_put_long(context, from_here, ":"); + rhizome_manifest *m = cursor.manifest; assert(m->filesize != RHIZOME_SIZE_UNSET); - 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"); + if (cursor.rowcount < rowoffset) + continue; + if (rowlimit == 0 || cursor.rowcount <= rowlimit) { + rhizome_lookup_author(m); + cli_put_long(context, cursor.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"); + } else if (!count_rows) + break; } - - if (ret==0 && count_rows){ - while (sqlite_step_retry(&retry, statement) == SQLITE_ROW) - ++rows; - } - cli_row_count(context, rows); - -cleanup: - sqlite3_finalize(statement); - RETURN(ret); + rhizome_list_release(&cursor); + cli_row_count(context, cursor.rowcount); + RETURN(0); OUT(); } @@ -1703,9 +1709,9 @@ int rhizome_find_duplicate(const rhizome_manifest *m, rhizome_manifest **found) rhizome_manifest_set_author(blob_m, &author); } // check that we can re-author this manifest - if (rhizome_extract_privatekey(blob_m, NULL)){ + rhizome_authenticate_author(blob_m); + if (m->authorship != AUTHOR_AUTHENTIC) goto next; - } *found = blob_m; if (config.debug.rhizome) DEBUGF("Found duplicate payload, %s", q_manifestid); @@ -1738,7 +1744,7 @@ static int unpack_manifest_row(rhizome_manifest *m, sqlite3_stmt *statement) } if (m->version != q_version) WARNF("Version mismatch, manifest is %"PRId64", database is %"PRId64, m->version, q_version); - m->inserttime = q_inserttime; + rhizome_manifest_set_inserttime(m, q_inserttime); return 0; } diff --git a/rhizome_direct_http.c b/rhizome_direct_http.c index f52779d0..400ab40a 100644 --- a/rhizome_direct_http.c +++ b/rhizome_direct_http.c @@ -222,13 +222,12 @@ static int rhizome_direct_addfile_end(struct http_request *hr) return 0; } // If manifest template did not specify a service field, then by default it is "file". + if (!rhizome_is_bk_none(&config.rhizome.api.addfile.bundle_secret_key)) + rhizome_apply_bundle_secret(m, &config.rhizome.api.addfile.bundle_secret_key); if (m->service == NULL) rhizome_manifest_set_service(m, RHIZOME_SERVICE_FILE); - sid_t *author = NULL; - if (!is_sid_t_any(config.rhizome.api.addfile.default_author)) - author = &config.rhizome.api.addfile.default_author; - rhizome_bk_t bsk = config.rhizome.api.addfile.bundle_secret_key; - if (rhizome_fill_manifest(m, r->data_file_name, author, &bsk)) { + const sid_t *author = is_sid_t_any(config.rhizome.api.addfile.default_author) ? NULL : &config.rhizome.api.addfile.default_author; + if (rhizome_fill_manifest(m, r->data_file_name, author)) { rhizome_manifest_free(m); rhizome_direct_clear_temporary_files(r); http_request_simple_response(&r->http, 500, "Internal Error: Could not fill manifest"); diff --git a/rhizome_fetch.c b/rhizome_fetch.c index 9a8ab8ff..ebb2aa90 100644 --- a/rhizome_fetch.c +++ b/rhizome_fetch.c @@ -114,7 +114,6 @@ struct rhizome_fetch_candidate queue3[4]; struct rhizome_fetch_candidate queue4[2]; struct rhizome_fetch_candidate queue5[2]; -#define NELS(a) (sizeof (a) / sizeof *(a)) #define slotno(slot) (int)((struct rhizome_fetch_queue *)(slot) - &rhizome_fetch_queues[0]) /* Static allocation of the queue structures. Must be in order of ascending log_size_threshold. diff --git a/rhizome_store.c b/rhizome_store.c index 3bc6301f..53a6907e 100644 --- a/rhizome_store.c +++ b/rhizome_store.c @@ -637,13 +637,13 @@ int rhizome_stat_file(rhizome_manifest *m, const char *filepath) return 0; } -static int rhizome_write_derive_key(rhizome_manifest *m, rhizome_bk_t *bsk, struct rhizome_write *write) +static int rhizome_write_derive_key(rhizome_manifest *m, struct rhizome_write *write) { if (m->payloadEncryption != PAYLOAD_ENCRYPTED) return 0; // if the manifest specifies encryption, make sure we can generate the payload key and encrypt the contents as we go - if (rhizome_derive_key(m, bsk)) + if (rhizome_derive_payload_key(m)) return -1; if (config.debug.rhizome) @@ -664,7 +664,7 @@ int rhizome_write_open_manifest(struct rhizome_write *write, rhizome_manifest *m if (rhizome_open_write(write, NULL, m->filesize, RHIZOME_PRIORITY_DEFAULT)) return -1; - if (rhizome_write_derive_key(m, NULL, write)) + if (rhizome_write_derive_key(m, write)) return -1; return 0; } @@ -1083,13 +1083,13 @@ static int write_file(struct rhizome_read *read, const char *filepath){ return ret; } -static int read_derive_key(rhizome_manifest *m, rhizome_bk_t *bsk, struct rhizome_read *read_state) +static int read_derive_key(rhizome_manifest *m, struct rhizome_read *read_state) { read_state->crypt = m->payloadEncryption == PAYLOAD_ENCRYPTED; if (read_state->crypt){ // if the manifest specifies encryption, make sure we can generate the payload key and encrypt // the contents as we go - if (rhizome_derive_key(m, bsk)) { + if (rhizome_derive_payload_key(m)) { rhizome_read_close(read_state); return WHY("Unable to decrypt bundle, valid key not found"); } @@ -1103,11 +1103,11 @@ static int read_derive_key(rhizome_manifest *m, rhizome_bk_t *bsk, struct rhizom return 0; } -int rhizome_open_decrypt_read(rhizome_manifest *m, rhizome_bk_t *bsk, struct rhizome_read *read_state) +int rhizome_open_decrypt_read(rhizome_manifest *m, struct rhizome_read *read_state) { int ret = rhizome_open_read(read_state, &m->filehash); if (ret == 0) - ret = read_derive_key(m, bsk, read_state); + ret = read_derive_key(m, read_state); return ret; } @@ -1116,11 +1116,11 @@ int rhizome_open_decrypt_read(rhizome_manifest *m, rhizome_bk_t *bsk, struct rhi * * Returns -1 on error, 0 if extracted successfully, 1 if not found. */ -int rhizome_extract_file(rhizome_manifest *m, const char *filepath, rhizome_bk_t *bsk) +int rhizome_extract_file(rhizome_manifest *m, const char *filepath) { struct rhizome_read read_state; bzero(&read_state, sizeof read_state); - int ret = rhizome_open_decrypt_read(m, bsk, &read_state); + int ret = rhizome_open_decrypt_read(m, &read_state); if (ret == 0) ret = write_file(&read_state, filepath); rhizome_read_close(&read_state); @@ -1185,7 +1185,7 @@ int rhizome_journal_pipe(struct rhizome_write *write, const rhizome_filehash_t * } // open an existing journal bundle, advance the head pointer, duplicate the existing content and get ready to add more. -int rhizome_write_open_journal(struct rhizome_write *write, rhizome_manifest *m, rhizome_bk_t *bsk, uint64_t advance_by, uint64_t new_size) +int rhizome_write_open_journal(struct rhizome_write *write, rhizome_manifest *m, uint64_t advance_by, uint64_t new_size) { int ret = 0; @@ -1213,7 +1213,7 @@ int rhizome_write_open_journal(struct rhizome_write *write, rhizome_manifest *m, goto failure; } - ret = rhizome_write_derive_key(m, bsk, write); + ret = rhizome_write_derive_key(m, write); if (ret) goto failure; @@ -1225,12 +1225,12 @@ failure: return ret; } -int rhizome_append_journal_buffer(rhizome_manifest *m, rhizome_bk_t *bsk, uint64_t advance_by, unsigned char *buffer, size_t len) +int rhizome_append_journal_buffer(rhizome_manifest *m, uint64_t advance_by, unsigned char *buffer, size_t len) { struct rhizome_write write; bzero(&write, sizeof write); - int ret = rhizome_write_open_journal(&write, m, bsk, advance_by, (uint64_t) len); + int ret = rhizome_write_open_journal(&write, m, advance_by, (uint64_t) len); if (ret) return -1; @@ -1253,7 +1253,7 @@ failure: return ret; } -int rhizome_append_journal_file(rhizome_manifest *m, rhizome_bk_t *bsk, uint64_t advance_by, const char *filename) +int rhizome_append_journal_file(rhizome_manifest *m, uint64_t advance_by, const char *filename) { struct stat stat; if (lstat(filename,&stat)) @@ -1261,7 +1261,7 @@ int rhizome_append_journal_file(rhizome_manifest *m, rhizome_bk_t *bsk, uint64_t struct rhizome_write write; bzero(&write, sizeof write); - int ret = rhizome_write_open_journal(&write, m, bsk, advance_by, stat.st_size); + int ret = rhizome_write_open_journal(&write, m, advance_by, stat.st_size); if (ret) return -1; diff --git a/tests/rhizomeops b/tests/rhizomeops index 2bb4346b..62892c2c 100755 --- a/tests/rhizomeops +++ b/tests/rhizomeops @@ -651,7 +651,7 @@ test_AddUpdateNoAuthor() { execute $servald rhizome add file $SIDB1 file1_2 file1_2.manifest tfw_cat --stderr assertExitStatus '!=' 0 - # Rhizome store contents have old payload. + # Rhizome store contents have old payload, with the original author. executeOk_servald rhizome list assert_rhizome_list --fromhere=1 --author=$SIDB1 file1 file2 } @@ -664,9 +664,10 @@ test_AddUpdateNoAuthorWithSecret() { tfw_cat -v file1_2.manifest executeOk_servald rhizome add file $SIDB1 file1_2 file1_2.manifest "$file1_secret" tfw_cat --stderr - # Rhizome store contents have new payload. + # Rhizome store contents have new payload, but it has lost its author (no BK + # field any more). executeOk_servald rhizome list - assert_rhizome_list --fromhere=1 --author=$SIDB1 file1_2 file2 + assert_rhizome_list --fromhere=0 file1_2 --fromhere=1 --author=$SIDB1 file2 } doc_AddUpdateAutoVersion="Add new payload to existing manifest with automatic version"