diff --git a/commandline.c b/commandline.c index 76e20de0..21e37b7f 100644 --- a/commandline.c +++ b/commandline.c @@ -1370,22 +1370,24 @@ int app_rhizome_add_file(const struct cli_parsed *parsed, struct cli_context *co keyring_free(keyring); return WHY("Existing manifest is not a journal"); } - if (!journal && m->is_journal){ + if (!journal && m->is_journal) { rhizome_manifest_free(m); keyring_free(keyring); 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); keyring_free(keyring); 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); keyring_free(keyring); return -1; @@ -1424,15 +1426,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", ":"); @@ -1705,8 +1709,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", ":"); @@ -1719,6 +1724,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"); } @@ -1742,8 +1750,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/http_server.c b/http_server.c index 20da718c..272c7418 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; @@ -556,6 +627,150 @@ 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 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)) { + 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) + 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]; @@ -743,6 +958,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,56 +979,41 @@ 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=") && (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); @@ -826,6 +1033,27 @@ 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); + return 0; + } + 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; @@ -858,7 +1086,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 +1099,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 +1139,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 +1218,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 +1296,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 +1333,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 +1355,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 +1370,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 +1451,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 +1469,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); @@ -1427,6 +1700,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; @@ -1538,18 +1814,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 @@ -1559,24 +1823,32 @@ static strbuf strbuf_append_quoted_string(strbuf sb, const char *str) 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 >= 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) { + 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]); @@ -1603,6 +1875,17 @@ 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) { + 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"); + } strbuf_puts(sb, "\r\n"); if (strbuf_overrun(sb)) return 0; @@ -1656,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]); @@ -1674,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); @@ -1681,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"); @@ -1706,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; @@ -1722,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; @@ -1744,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; @@ -1755,8 +2039,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 a647c30e..20e8d439 100644 --- a/http_server.h +++ b/http_server.h @@ -56,13 +56,35 @@ 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_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; - 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]; + struct http_client_authorization authorization; }; struct http_response_headers { @@ -71,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 *); @@ -94,11 +117,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 +146,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 +196,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/keyring.c b/keyring.c index 6e95de19..030a553c 100644 --- a/keyring.c +++ b/keyring.c @@ -1506,7 +1506,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 e4c4acb1..8c621255 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; @@ -717,7 +722,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 @@ -734,10 +740,12 @@ int app_meshms_send_message(const struct cli_parsed *parsed, struct cli_context } 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 (!conv){ + 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) { keyring_free(keyring); return -1; } 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 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 c1e79b57..93981aab 100644 --- a/rhizome.h +++ b/rhizome.h @@ -238,9 +238,18 @@ 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. + /* Local authorship. Useful for dividing bundle lists between "sent" and + * "inbox" views. */ - bool_t has_author; + enum rhizome_bundle_authorship { + 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 + AUTHOR_AUTHENTIC // a local identity is the verified author + } authorship; /* time-to-live in hops of this manifest. */ int ttl; @@ -283,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; @@ -314,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 @@ -334,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 @@ -427,16 +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); @@ -533,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); @@ -549,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, @@ -571,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); @@ -853,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, @@ -866,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); ssize_t 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 6d7816ea..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; @@ -735,22 +809,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; @@ -857,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; @@ -908,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'"); @@ -984,3 +1042,50 @@ 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 (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) { + 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)) { + if (config.debug.rhizome) + DEBUGF("found sender"); + rhizome_manifest_set_author(m, &m->sender); + m->authorship = AUTHOR_LOCAL; + RETURN(1); + } + } + case AUTHENTICATION_ERROR: + case AUTHOR_UNKNOWN: + case AUTHOR_IMPOSTOR: + RETURN(0); + case AUTHOR_LOCAL: + case AUTHOR_AUTHENTIC: + RETURN(1); + } + 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 f3024925..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); @@ -1408,63 +1407,156 @@ 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, - int limit, int offset, char count_rows) -{ - IN(); +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; + // Set by calling the next() function. + int64_t rowid; + rhizome_manifest *manifest; + size_t rowcount; + // Private state. + sqlite3_stmt *_statement; + unsigned _offset; +}; + +/* The cursor struct must be zerofilled and the query parameters optionally filled in 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 (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 (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 LIMIT -1 OFFSET @offset"); if (strbuf_overrun(b)) RETURN(WHYF("SQL command too long: %s", strbuf_str(b))); - - sqlite_retry_state retry = SQLITE_RETRY_STATE_DEFAULT; - sqlite3_stmt *statement = sqlite_prepare(&retry, strbuf_str(b)); - if (!statement) + cursor->_statement = sqlite_prepare(retry, strbuf_str(b)); + if (cursor->_statement == NULL) 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; + if (sqlite_bind(retry, cursor->_statement, NAMED|INT, "@offset", cursor->_offset, END) == -1) + goto failure; + if (cursor->service && sqlite_bind(retry, cursor->_statement, NAMED|STATIC_TEXT, "@service", cursor->service, END) == -1) + goto failure; + if (cursor->name && sqlite_bind(retry, cursor->_statement, NAMED|STATIC_TEXT, "@name", cursor->name, END) == -1) + goto failure; + 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; + 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); + while (sqlite_step_retry(retry, cursor->_statement) == SQLITE_ROW) { + ++cursor->_offset; + 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); + 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); + 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) { + WHYF("MANIFESTS row id=%s has invalid author column %s -- skipped", q_manifestid, alloca_str_toprint(q_author)); + continue; + } + } + 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) { + WHYF("MANIFESTS row id=%s version=%"PRId64" does not match manifest blob version=%"PRId64" -- skipped", + 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; + // Don't do rhizome_verify_author(m); too CPU expensive for a listing. Save that for when + // the bundle is extracted or exported. + ++cursor->rowcount; + RETURN(1); } - - ret=0; - size_t rows = 0; - + RETURN(0); + OUT(); +} + +static void rhizome_list_release(struct rhizome_list_cursor *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, + size_t rowlimit, size_t rowoffset, char count_rows) +{ + IN(); + struct rhizome_list_cursor cursor; + bzero(&cursor, sizeof cursor); + cursor.service = service && service[0] ? service : NULL; + cursor.name = name && name[0] ? name : NULL; + if (sender_hex && *sender_hex && 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; + if (rhizome_list_open(&retry, &cursor) == -1) + RETURN(-1); const char *names[]={ "_id", "service", @@ -1480,105 +1572,42 @@ int rhizome_list_manifests(struct cli_context *context, const char *service, con "recipient", "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; + cli_columns(context, NELS(names), names); + while (rhizome_list_next(&retry, &cursor) == 1) { + rhizome_manifest *m = cursor.manifest; + assert(m->filesize != RHIZOME_SIZE_UNSET); + 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; } - 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); + 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(); } @@ -1680,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); @@ -1715,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 8e4d7c04..400ab40a 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 @@ -222,13 +222,12 @@ 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"); @@ -271,14 +270,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 +301,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 +324,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 +343,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 +360,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 +393,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/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_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/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/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; 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 } diff --git a/tests/rhizomehttp b/tests/rhizomehttp index 8fa74c77..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 @@ -41,8 +42,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,30 +63,40 @@ 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' + 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" 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" 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"