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"