mirror of
https://github.com/servalproject/serval-dna.git
synced 2024-12-20 21:53:12 +00:00
1524 lines
51 KiB
C
1524 lines
51 KiB
C
/*
|
|
Serval DNA - Rhizome manifest operations
|
|
Copyright (C) 2010 Paul Gardner-Stephen
|
|
Copyright (C) 2013-2014 Serval Project Inc.
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation; either version 2
|
|
of the License, or (at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <sys/uio.h>
|
|
#include "serval.h"
|
|
#include "conf.h"
|
|
#include "crypto.h"
|
|
#include "rhizome.h"
|
|
#include "str.h"
|
|
#include "numeric_str.h"
|
|
#include "mem.h"
|
|
#include "keyring.h"
|
|
#include "dataformats.h"
|
|
|
|
static const char *rhizome_manifest_get(const rhizome_manifest *m, const char *var)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < m->var_count; ++i)
|
|
if (strcmp(m->vars[i], var) == 0)
|
|
return m->values[i];
|
|
return NULL;
|
|
}
|
|
|
|
/* Remove the field with the given label from the manifest
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
static int _rhizome_manifest_del(struct __sourceloc __whence, rhizome_manifest *m, const char *var)
|
|
{
|
|
DEBUGF(rhizome_manifest, "DEL manifest %p %s", m, var);
|
|
int ret = 0;
|
|
unsigned i;
|
|
for (i = 0; i < m->var_count; ++i)
|
|
if (strcmp(m->vars[i], var) == 0) {
|
|
free((char *) m->vars[i]);
|
|
free((char *) m->values[i]);
|
|
--m->var_count;
|
|
ret = 1;
|
|
break;
|
|
}
|
|
for (; i < m->var_count; ++i) {
|
|
m->vars[i] = m->vars[i + 1];
|
|
m->values[i] = m->values[i + 1];
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
#define rhizome_manifest_set(m,var,value) _rhizome_manifest_set(__WHENCE__, (m), (var), (value))
|
|
#define rhizome_manifest_set_ui64(m,var,value) _rhizome_manifest_set_ui64(__WHENCE__, (m), (var), (value))
|
|
#define rhizome_manifest_del(m,var) _rhizome_manifest_del(__WHENCE__, (m), (var))
|
|
|
|
static const char *_rhizome_manifest_set(struct __sourceloc __whence, rhizome_manifest *m, const char *var, const char *value)
|
|
{
|
|
DEBUGF(rhizome_manifest, "SET manifest %p %s = %s", m, var, alloca_str_toprint(value));
|
|
unsigned i;
|
|
for(i=0;i<m->var_count;i++)
|
|
if (strcmp(m->vars[i],var) == 0) {
|
|
const char *ret = str_edup(value);
|
|
if (ret == NULL)
|
|
return NULL;
|
|
free((char *)m->values[i]);
|
|
m->values[i] = ret;
|
|
return ret;
|
|
}
|
|
if (m->var_count >= NELS(m->vars)) {
|
|
WHY("no more manifest vars");
|
|
return NULL;
|
|
}
|
|
if ((m->vars[m->var_count] = str_edup(var)) == NULL)
|
|
return NULL;
|
|
const char *ret = m->values[m->var_count] = str_edup(value);
|
|
if (ret == NULL) {
|
|
free((char *)m->vars[i]);
|
|
m->vars[i] = NULL;
|
|
return NULL;
|
|
}
|
|
m->var_count++;
|
|
return ret;
|
|
}
|
|
|
|
static const char *_rhizome_manifest_set_ui64(struct __sourceloc __whence, rhizome_manifest *m, char *var, uint64_t value)
|
|
{
|
|
char str[50];
|
|
snprintf(str, sizeof str, "%" PRIu64, value);
|
|
return rhizome_manifest_set(m, var, str);
|
|
}
|
|
|
|
void _rhizome_manifest_set_id(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_bid_t *bidp)
|
|
{
|
|
if (bidp) {
|
|
if (m->has_id && (bidp == &m->keypair.public_key || cmp_rhizome_bid_t(&m->keypair.public_key, bidp) == 0))
|
|
return; // unchanged
|
|
const char *v = rhizome_manifest_set(m, "id", alloca_tohex_rhizome_bid_t(*bidp));
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->keypair.public_key = *bidp;
|
|
m->has_id = 1;
|
|
} else if (m->has_id) {
|
|
bzero(&m->keypair.public_key, sizeof m->keypair.public_key); // not strictly necessary but aids debugging
|
|
m->has_id = 0;
|
|
} else
|
|
return; // unchanged
|
|
// The BID has changed.
|
|
m->finalised = 0;
|
|
// Any existing secret key and bundle key are no longer valid.
|
|
if (m->haveSecret) {
|
|
m->haveSecret = SECRET_UNKNOWN;
|
|
bzero(m->keypair.private_key.binary, sizeof m->keypair.private_key.binary); // 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, uint64_t version)
|
|
{
|
|
if (version) {
|
|
const char *v = rhizome_manifest_set_ui64(m, "version", version);
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
} else
|
|
rhizome_manifest_del(m, "version");
|
|
m->version = version;
|
|
m->finalised = 0;
|
|
}
|
|
|
|
void _rhizome_manifest_del_version(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
_rhizome_manifest_set_version(__whence, m, 0);
|
|
}
|
|
|
|
void _rhizome_manifest_set_filesize(struct __sourceloc __whence, rhizome_manifest *m, uint64_t size)
|
|
{
|
|
if (size == RHIZOME_SIZE_UNSET) {
|
|
rhizome_manifest_del(m, "filesize");
|
|
} else {
|
|
const char *v = rhizome_manifest_set_ui64(m, "filesize", size);
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
}
|
|
m->filesize = size;
|
|
m->finalised = 0;
|
|
}
|
|
|
|
void _rhizome_manifest_del_filesize(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
_rhizome_manifest_set_filesize(__whence, m, RHIZOME_SIZE_UNSET);
|
|
}
|
|
|
|
/* Must always set file size before setting the file hash, to avoid assertion failures.
|
|
*/
|
|
void _rhizome_manifest_set_filehash(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_filehash_t *hash)
|
|
{
|
|
if (hash) {
|
|
const char *v = rhizome_manifest_set(m, "filehash", alloca_tohex_rhizome_filehash_t(*hash));
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->filehash = *hash;
|
|
m->has_filehash = 1;
|
|
} else {
|
|
rhizome_manifest_del(m, "filehash");
|
|
m->filehash = RHIZOME_FILEHASH_NONE;
|
|
m->has_filehash = 0;
|
|
}
|
|
m->finalised = 0;
|
|
}
|
|
|
|
void _rhizome_manifest_del_filehash(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
_rhizome_manifest_set_filehash(__whence, m, NULL);
|
|
}
|
|
|
|
void _rhizome_manifest_set_tail(struct __sourceloc __whence, rhizome_manifest *m, uint64_t tail)
|
|
{
|
|
if (tail == RHIZOME_SIZE_UNSET) {
|
|
rhizome_manifest_del(m, "tail");
|
|
m->is_journal = 0;
|
|
} else {
|
|
const char *v = rhizome_manifest_set_ui64(m, "tail", tail);
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->is_journal = 1;
|
|
}
|
|
m->tail = tail;
|
|
m->finalised = 0;
|
|
}
|
|
|
|
void _rhizome_manifest_set_bundle_key(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_bk_t *bkp)
|
|
{
|
|
if (bkp) {
|
|
const char *v = rhizome_manifest_set(m, "BK", alloca_tohex_rhizome_bk_t(*bkp));
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->bundle_key = *bkp;
|
|
m->has_bundle_key = 1;
|
|
m->finalised = 0;
|
|
} 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->has_bundle_key = 0;
|
|
m->bundle_key = RHIZOME_BK_NONE; // not strictly necessary, but aids debugging
|
|
m->finalised = 0;
|
|
} 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)
|
|
{
|
|
if (service) {
|
|
assert(rhizome_str_is_manifest_service(service));
|
|
const char *v = rhizome_manifest_set(m, "service", service);
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->service = v;
|
|
m->finalised = 0;
|
|
} else
|
|
_rhizome_manifest_del_service(__whence, m);
|
|
}
|
|
|
|
void _rhizome_manifest_del_service(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
if (m->service) {
|
|
m->service = NULL;
|
|
m->finalised = 0;
|
|
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)
|
|
{
|
|
m->finalised = 0;
|
|
if (name) {
|
|
assert(rhizome_str_is_manifest_name(name));
|
|
const char *v = rhizome_manifest_set(m, "name", name);
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->name = v;
|
|
} else {
|
|
rhizome_manifest_del(m, "name");
|
|
m->name = NULL;
|
|
}
|
|
}
|
|
|
|
void _rhizome_manifest_del_name(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
if (m->name) {
|
|
m->name = NULL;
|
|
m->finalised = 0;
|
|
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_ui64(m, "date", (uint64_t)date);
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->date = date;
|
|
m->has_date = 1;
|
|
m->finalised = 0;
|
|
}
|
|
|
|
void _rhizome_manifest_del_date(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
if (m->has_date) {
|
|
m->has_date = 0;
|
|
m->finalised = 0;
|
|
rhizome_manifest_del(m, "date");
|
|
} else
|
|
assert(rhizome_manifest_get(m, "date") == NULL);
|
|
}
|
|
|
|
void _rhizome_manifest_set_sender(struct __sourceloc __whence, rhizome_manifest *m, const sid_t *sidp)
|
|
{
|
|
if (sidp) {
|
|
const char *v = rhizome_manifest_set(m, "sender", alloca_tohex_sid_t(*sidp));
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->sender = *sidp;
|
|
m->has_sender = 1;
|
|
m->finalised = 0;
|
|
} 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;
|
|
m->finalised = 0;
|
|
} else
|
|
assert(rhizome_manifest_get(m, "sender") == NULL);
|
|
}
|
|
|
|
void _rhizome_manifest_set_recipient(struct __sourceloc __whence, rhizome_manifest *m, const sid_t *sidp)
|
|
{
|
|
if (sidp) {
|
|
const char *v = rhizome_manifest_set(m, "recipient", alloca_tohex_sid_t(*sidp));
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
m->recipient = *sidp;
|
|
m->has_recipient = 1;
|
|
m->finalised = 0;
|
|
} 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;
|
|
m->finalised = 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)
|
|
{
|
|
switch (flag) {
|
|
case PAYLOAD_CRYPT_UNKNOWN:
|
|
rhizome_manifest_del(m, "crypt");
|
|
break;
|
|
case PAYLOAD_CLEAR: {
|
|
const char *v = rhizome_manifest_set(m, "crypt", "0");
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
break;
|
|
}
|
|
case PAYLOAD_ENCRYPTED: {
|
|
const char *v = rhizome_manifest_set(m, "crypt", "1");
|
|
assert(v); // TODO: remove known manifest fields from vars[]
|
|
break;
|
|
}
|
|
default: abort();
|
|
}
|
|
m->payloadEncryption = flag;
|
|
m->finalised = 0;
|
|
}
|
|
|
|
void _rhizome_manifest_set_rowid(struct __sourceloc __whence, rhizome_manifest *m, uint64_t rowid)
|
|
{
|
|
DEBUGF(rhizome_manifest, "SET manifest %p rowid = %"PRIu64, m, rowid);
|
|
m->rowid = rowid;
|
|
}
|
|
|
|
void _rhizome_manifest_set_inserttime(struct __sourceloc __whence, rhizome_manifest *m, time_ms_t time)
|
|
{
|
|
DEBUGF(rhizome_manifest, "SET manifest %p inserttime = %"PRItime_ms_t, m, time);
|
|
m->inserttime = time;
|
|
}
|
|
|
|
void _rhizome_manifest_set_author(struct __sourceloc __whence, rhizome_manifest *m, const keyring_identity *id, const sid_t *sidp)
|
|
{
|
|
if (id) {
|
|
if (m->author_identity == id)
|
|
return;
|
|
sidp = id->box_pk;
|
|
} else if (sidp) {
|
|
if (m->authorship != ANONYMOUS && cmp_sid_t(&m->author, sidp) == 0)
|
|
return;
|
|
} else {
|
|
_rhizome_manifest_del_author(__whence, m);
|
|
return;
|
|
}
|
|
|
|
DEBUGF(rhizome_manifest, "SET manifest %p author = %s", m, alloca_tohex_sid_t(*sidp));
|
|
m->author = *sidp;
|
|
m->author_identity = id;
|
|
m->authorship = AUTHOR_NOT_CHECKED;
|
|
}
|
|
|
|
void _rhizome_manifest_del_author(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
if (m->authorship != ANONYMOUS) {
|
|
DEBUGF(rhizome_manifest, "DEL manifest %p author", m);
|
|
m->author = SID_ANY;
|
|
m->author_identity = NULL;
|
|
m->authorship = ANONYMOUS;
|
|
}
|
|
}
|
|
|
|
/* Compute the hash of the manifest's body, including the NUL byte that separates the body from
|
|
* the signature block, and verify that a signature is present and is correct.
|
|
*
|
|
* If the manifest signature is valid, ie, the signature is a self-signature using the
|
|
* manifest's own private key, then sets the m->selfSigned flag and returns 1.
|
|
*
|
|
* If there are no signatures or if the signature block does not verify, then clears the
|
|
* m->selfSigned flag and returns 0.
|
|
*
|
|
* Only call this function on manifests for which rhizome_manifest_validate(m) has returned true
|
|
* (ie, m->finalised is set).
|
|
*/
|
|
int rhizome_manifest_verify(rhizome_manifest *m)
|
|
{
|
|
assert(m->finalised);
|
|
assert(m->manifest_body_bytes > 0);
|
|
assert(m->manifest_all_bytes > 0);
|
|
assert(m->manifest_body_bytes <= m->manifest_all_bytes);
|
|
assert(m->sig_count == 0);
|
|
if (m->manifest_body_bytes == m->manifest_all_bytes)
|
|
assert(m->manifestdata[m->manifest_body_bytes - 1] == '\0');
|
|
// Hash the body
|
|
crypto_hash_sha512(m->manifesthash.binary, m->manifestdata, m->manifest_body_bytes);
|
|
// Read signature blocks
|
|
unsigned ofs = m->manifest_body_bytes;
|
|
while (ofs < m->manifest_all_bytes) {
|
|
if (rhizome_manifest_extract_signature(m, &ofs) == -1)
|
|
break;
|
|
}
|
|
assert(ofs <= m->manifest_all_bytes);
|
|
// Make sure the first signatory's public key is the bundle ID
|
|
assert(m->has_id);
|
|
if (m->sig_count == 0) {
|
|
DEBUG(rhizome_manifest, "Manifest has no signature blocks, but should have self-signature block");
|
|
m->selfSigned = 0;
|
|
return 0;
|
|
}
|
|
if (memcmp(m->signatories[0], m->keypair.public_key.binary, sizeof m->keypair.public_key.binary) != 0) {
|
|
DEBUGF(rhizome_manifest, "Manifest id does not match first signature block (signature key is %s)",
|
|
alloca_tohex(m->signatories[0], crypto_sign_PUBLICKEYBYTES)
|
|
);
|
|
m->selfSigned = 0;
|
|
return 0;
|
|
}
|
|
m->selfSigned = 1;
|
|
return 1;
|
|
}
|
|
|
|
static void rhizome_manifest_clear(rhizome_manifest *m)
|
|
{
|
|
while (m->var_count) {
|
|
--m->var_count;
|
|
free((char *) m->vars[m->var_count]);
|
|
free((char *) m->values[m->var_count]);
|
|
m->vars[m->var_count] = m->values[m->var_count] = NULL;
|
|
}
|
|
while (m->sig_count) {
|
|
--m->sig_count;
|
|
free(m->signatories[m->sig_count]);
|
|
m->signatories[m->sig_count] = NULL;
|
|
}
|
|
m->malformed = NULL;
|
|
m->has_id = 0;
|
|
m->has_filehash = 0;
|
|
m->is_journal = 0;
|
|
m->filesize = RHIZOME_SIZE_UNSET;
|
|
m->tail = RHIZOME_SIZE_UNSET;
|
|
m->version = 0;
|
|
// TODO initialise more fields
|
|
}
|
|
|
|
int rhizome_manifest_inspect(const char *buf, size_t len, struct rhizome_manifest_summary *summ)
|
|
{
|
|
const char *const end = buf + len;
|
|
int has_bid = 0;
|
|
int has_version = 0;
|
|
const char *begin = buf;
|
|
const char *eol = NULL;
|
|
enum { Label, Value, Error } state = Label;
|
|
const char *p;
|
|
for (p = buf; state != Error && p < end && *p; ++p)
|
|
switch (state) {
|
|
case Label:
|
|
if (*p == '=') {
|
|
if (!rhizome_manifest_field_label_is_valid(begin, p - begin))
|
|
state = Error; // bad field name
|
|
else {
|
|
int *has = NULL;
|
|
if (p == begin + 2 && strncmp(begin, "id", 2) == 0)
|
|
has = &has_bid;
|
|
else if (p == begin + 7 && strncmp(begin, "version", 7) == 0)
|
|
has = &has_version;
|
|
state = Value;
|
|
if (has) {
|
|
if (*has)
|
|
state = Error; // duplicate
|
|
else {
|
|
*has = 1;
|
|
begin = p + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case Value:
|
|
if (*p == '\r' && !eol)
|
|
eol = p;
|
|
else if (*p == '\n') {
|
|
if (!eol)
|
|
eol = p;
|
|
if (has_bid == 1) {
|
|
const char *e;
|
|
if (parse_rhizome_bid_t(&summ->bid, begin, eol - begin, &e) == 0 && e == eol)
|
|
has_bid = 2;
|
|
else
|
|
state = Error; // invalid "id" field
|
|
} else if (has_version == 1) {
|
|
const char *e;
|
|
if (str_to_uint64(begin, 10, &summ->version, &e) && e == eol)
|
|
has_version = 2;
|
|
else
|
|
state = Error; // invalid "version" field
|
|
}
|
|
if (state == Value) {
|
|
state = Label;
|
|
begin = p + 1;
|
|
eol = NULL;
|
|
}
|
|
} else if (eol)
|
|
state = Error; // CR not followed by LF
|
|
break;
|
|
default:
|
|
abort();
|
|
}
|
|
if (p < end && *p == '\0')
|
|
++p;
|
|
summ->body_len = p - buf;
|
|
return state == Label && has_bid == 2 && has_version == 2;
|
|
}
|
|
|
|
/* Parse a Rhizome text manifest from its internal buffer up to and including the terminating NUL
|
|
* character which marks the start of the signature block.
|
|
*
|
|
* Prior to calling, the caller must set up m->manifest_all_bytes to the length of the manifest
|
|
* text, including the signature block, and set m->manifestdata[0..m->manifest_all_bytes-1] to
|
|
* contain the manifest text and signature block to be parsed.
|
|
*
|
|
* A "well formed" manifest consists of a series of zero or more lines with the form:
|
|
*
|
|
* LABEL "=" VALUE [ CR ] LF
|
|
*
|
|
* where LABEL matches the regular expression [A-Za-z][A-Za-z0-9]* (identifier without underscore)
|
|
* VALUE is any value that does not contain NUL, CR or LF (leading and trailing spaces are
|
|
* not stripped from VALUE)
|
|
* NUL is ASCII 0
|
|
* CR is ASCII 13
|
|
* LF is ASCII 10
|
|
*
|
|
* Unpacks all parsed field labels and string values into the m->vars[] and m->values[] arrays, as
|
|
* pointers to malloc(3)ed NUL terminated strings, in the order they appear, and sets m->var_count
|
|
* to the number of fields unpacked. Sets m->manifest_body_bytes to the number of bytes in the text
|
|
* portion up to and including the optional NUL that starts the signature block (if present).
|
|
*
|
|
* Returns 1 if the manifest is not well formed (syntax violation), any essential field is
|
|
* malformed, or if there are any duplicate fields. In this case the m->vars[] and m->values[]
|
|
* arrays are not set and the manifest is returned to the state it was in prior to calling.
|
|
*
|
|
* Returns 0 if the manifest is well formed, if there are no duplicate fields, and if all essential
|
|
* fields are valid. Counts invalid non-essential fields and unrecognised fields in m->malformed.
|
|
*
|
|
* Returns -1 if there is an unrecoverable error (eg, malloc(3) returns NULL, out of memory).
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
int rhizome_manifest_parse(rhizome_manifest *m)
|
|
{
|
|
IN();
|
|
assert(m->manifest_all_bytes <= sizeof m->manifestdata);
|
|
assert(m->manifest_body_bytes == 0);
|
|
assert(m->var_count == 0);
|
|
assert(!m->finalised);
|
|
assert(m->malformed == NULL);
|
|
assert(!m->has_id);
|
|
assert(!m->has_filehash);
|
|
assert(!m->is_journal);
|
|
assert(m->filesize == RHIZOME_SIZE_UNSET);
|
|
assert(m->tail == RHIZOME_SIZE_UNSET);
|
|
assert(m->version == 0);
|
|
assert(!m->has_date);
|
|
assert(!m->has_sender);
|
|
assert(!m->has_recipient);
|
|
assert(m->payloadEncryption == PAYLOAD_CRYPT_UNKNOWN);
|
|
unsigned invalid = 0;
|
|
unsigned has_invalid_core = 0;
|
|
unsigned has_duplicate = 0;
|
|
const char *const end = (const char *)m->manifestdata + m->manifest_all_bytes;
|
|
const char *p;
|
|
unsigned line_number = 0;
|
|
for (p = (const char *)m->manifestdata; !invalid && p < end && *p; ++p) {
|
|
++line_number;
|
|
const char *const plabel = p++;
|
|
while (p < end && *p && *p != '=' && *p != '\n')
|
|
++p;
|
|
if (p == end || *p != '=') {
|
|
DEBUGF(rhizome_manifest, "Invalid manifest line %u: %s", line_number, alloca_toprint(-1, plabel, p - plabel + 1));
|
|
++invalid;
|
|
break;
|
|
}
|
|
assert(p < end);
|
|
assert(*p == '=');
|
|
const char *const pvalue = ++p;
|
|
while (p < end && *p && *p != '\n')
|
|
++p;
|
|
if (p >= end || *p != '\n') {
|
|
DEBUGF(rhizome_manifest, "Missing manifest newline at line %u: %s", line_number, alloca_toprint(-1, plabel, p - plabel));
|
|
++invalid;
|
|
break;
|
|
}
|
|
const char *const eol = (p > pvalue && p[-1] == '\r') ? p - 1 : p;
|
|
enum rhizome_manifest_parse_status status = rhizome_manifest_parse_field(m, plabel, pvalue - plabel - 1, pvalue, eol - pvalue);
|
|
int status_ok = 0;
|
|
switch (status) {
|
|
case RHIZOME_MANIFEST_ERROR:
|
|
RETURN(-1);
|
|
case RHIZOME_MANIFEST_OK:
|
|
status_ok = 1;
|
|
break;
|
|
case RHIZOME_MANIFEST_SYNTAX_ERROR:
|
|
status_ok = 1;
|
|
++invalid;
|
|
break;
|
|
case RHIZOME_MANIFEST_DUPLICATE_FIELD:
|
|
status_ok = 1;
|
|
++has_duplicate;
|
|
break;
|
|
case RHIZOME_MANIFEST_INVALID:
|
|
status_ok = 1;
|
|
++has_invalid_core;
|
|
break;
|
|
case RHIZOME_MANIFEST_MALFORMED:
|
|
status_ok = 1;
|
|
m->malformed = "Invalid field";
|
|
break;
|
|
case RHIZOME_MANIFEST_OVERFLOW:
|
|
status_ok = 1;
|
|
++invalid;
|
|
break;
|
|
}
|
|
if (!status_ok)
|
|
FATALF("status = %d", status);
|
|
assert(p < end);
|
|
assert(*p == '\n');
|
|
}
|
|
if ((p < end && *p) || invalid || has_invalid_core || has_duplicate) {
|
|
rhizome_manifest_clear(m);
|
|
RETURN(1);
|
|
}
|
|
// The null byte is included in the body (and checksum), not the signature block
|
|
if (p < end) {
|
|
assert(*p == '\0');
|
|
++p;
|
|
}
|
|
m->manifest_body_bytes = p - (const char *)m->manifestdata;
|
|
RETURN(0);
|
|
OUT();
|
|
}
|
|
|
|
typedef int MANIFEST_FIELD_TESTER(const rhizome_manifest *);
|
|
typedef void MANIFEST_FIELD_UNSETTER(struct __sourceloc, rhizome_manifest *);
|
|
typedef void MANIFEST_FIELD_COPIER(struct __sourceloc, rhizome_manifest *, const rhizome_manifest *);
|
|
typedef int MANIFEST_FIELD_PARSER(rhizome_manifest *, const char *);
|
|
|
|
static int _rhizome_manifest_test_id(const rhizome_manifest *m)
|
|
{
|
|
return m->has_id;
|
|
}
|
|
static void _rhizome_manifest_unset_id(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_set_id(m, NULL);
|
|
}
|
|
static void _rhizome_manifest_copy_id(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_id(m, srcm->has_id ? &srcm->keypair.public_key : NULL);
|
|
}
|
|
static int _rhizome_manifest_parse_id(rhizome_manifest *m, const char *text)
|
|
{
|
|
rhizome_bid_t bid;
|
|
if (str_to_rhizome_bid_t(&bid, text) == -1)
|
|
return 0;
|
|
rhizome_manifest_set_id(m, &bid);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_version(const rhizome_manifest *m)
|
|
{
|
|
return m->version != 0;
|
|
}
|
|
static void _rhizome_manifest_unset_version(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_del_version(m);
|
|
}
|
|
static void _rhizome_manifest_copy_version(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_version(m, srcm->version);
|
|
}
|
|
static int _rhizome_manifest_parse_version(rhizome_manifest *m, const char *text)
|
|
{
|
|
uint64_t version;
|
|
if (!str_to_uint64(text, 10, &version, NULL) || version == 0)
|
|
return 0;
|
|
rhizome_manifest_set_version(m, version);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_filehash(const rhizome_manifest *m)
|
|
{
|
|
return m->has_filehash;
|
|
}
|
|
static void _rhizome_manifest_unset_filehash(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_set_filehash(m, NULL);
|
|
}
|
|
static void _rhizome_manifest_copy_filehash(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_filehash(m, srcm->has_filehash ? &srcm->filehash : NULL);
|
|
}
|
|
static int _rhizome_manifest_parse_filehash(rhizome_manifest *m, const char *text)
|
|
{
|
|
rhizome_filehash_t hash;
|
|
if (str_to_rhizome_filehash_t(&hash, text) == -1)
|
|
return 0;
|
|
rhizome_manifest_set_filehash(m, &hash);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_filesize(const rhizome_manifest *m)
|
|
{
|
|
return m->filesize != RHIZOME_SIZE_UNSET;
|
|
}
|
|
static void _rhizome_manifest_unset_filesize(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_set_filesize(m, RHIZOME_SIZE_UNSET);
|
|
}
|
|
static void _rhizome_manifest_copy_filesize(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_filesize(m, srcm->filesize);
|
|
}
|
|
static int _rhizome_manifest_parse_filesize(rhizome_manifest *m, const char *text)
|
|
{
|
|
uint64_t size;
|
|
if (!str_to_uint64(text, 10, &size, NULL) || size == RHIZOME_SIZE_UNSET)
|
|
return 0;
|
|
rhizome_manifest_set_filesize(m, size);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_tail(const rhizome_manifest *m)
|
|
{
|
|
return m->is_journal;
|
|
}
|
|
static void _rhizome_manifest_unset_tail(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_set_tail(m, RHIZOME_SIZE_UNSET);
|
|
}
|
|
static void _rhizome_manifest_copy_tail(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_tail(m, srcm->tail);
|
|
}
|
|
static int _rhizome_manifest_parse_tail(rhizome_manifest *m, const char *text)
|
|
{
|
|
uint64_t tail;
|
|
if (!str_to_uint64(text, 10, &tail, NULL) || tail == RHIZOME_SIZE_UNSET)
|
|
return 0;
|
|
rhizome_manifest_set_tail(m, tail);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_BK(const rhizome_manifest *m)
|
|
{
|
|
return m->has_bundle_key;
|
|
}
|
|
static void _rhizome_manifest_unset_BK(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_del_bundle_key(m);
|
|
}
|
|
static void _rhizome_manifest_copy_BK(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_bundle_key(m, srcm->has_bundle_key ? &srcm->bundle_key : NULL);
|
|
}
|
|
static int _rhizome_manifest_parse_BK(rhizome_manifest *m, const char *text)
|
|
{
|
|
rhizome_bk_t bk;
|
|
if (str_to_rhizome_bk_t(&bk, text) == -1)
|
|
return 0;
|
|
rhizome_manifest_set_bundle_key(m, &bk);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_service(const rhizome_manifest *m)
|
|
{
|
|
return m->service != NULL;
|
|
}
|
|
static void _rhizome_manifest_unset_service(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_del_service(m);
|
|
}
|
|
static void _rhizome_manifest_copy_service(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_service(m, srcm->service);
|
|
}
|
|
static int _rhizome_manifest_parse_service(rhizome_manifest *m, const char *text)
|
|
{
|
|
if (!rhizome_str_is_manifest_service(text))
|
|
return 0;
|
|
rhizome_manifest_set_service(m, text);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_date(const rhizome_manifest *m)
|
|
{
|
|
return m->has_date;
|
|
}
|
|
static void _rhizome_manifest_unset_date(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_del_date(m);
|
|
}
|
|
static void _rhizome_manifest_copy_date(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
if (srcm->has_date)
|
|
rhizome_manifest_set_date(m, srcm->date);
|
|
else
|
|
rhizome_manifest_del_date(m);
|
|
}
|
|
static int _rhizome_manifest_parse_date(rhizome_manifest *m, const char *text)
|
|
{
|
|
int64_t date;
|
|
if (!str_to_int64(text, 10, &date, NULL))
|
|
return 0;
|
|
rhizome_manifest_set_date(m, date);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_sender(const rhizome_manifest *m)
|
|
{
|
|
return m->has_sender;
|
|
}
|
|
static void _rhizome_manifest_unset_sender(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_set_sender(m, NULL);
|
|
}
|
|
static void _rhizome_manifest_copy_sender(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_sender(m, srcm->has_sender ? &srcm->sender : NULL);
|
|
}
|
|
static int _rhizome_manifest_parse_sender(rhizome_manifest *m, const char *text)
|
|
{
|
|
sid_t sid;
|
|
if (str_to_sid_t(&sid, text) == -1)
|
|
return 0;
|
|
rhizome_manifest_set_sender(m, &sid);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_recipient(const rhizome_manifest *m)
|
|
{
|
|
return m->has_recipient;
|
|
}
|
|
static void _rhizome_manifest_unset_recipient(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_set_recipient(m, NULL);
|
|
}
|
|
static void _rhizome_manifest_copy_recipient(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_recipient(m, srcm->has_recipient ? &srcm->recipient : NULL);
|
|
}
|
|
static int _rhizome_manifest_parse_recipient(rhizome_manifest *m, const char *text)
|
|
{
|
|
sid_t sid;
|
|
if (str_to_sid_t(&sid, text) == -1)
|
|
return 0;
|
|
rhizome_manifest_set_recipient(m, &sid);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_name(const rhizome_manifest *m)
|
|
{
|
|
return m->name != NULL;
|
|
}
|
|
static void _rhizome_manifest_unset_name(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_del_name(m);
|
|
}
|
|
static void _rhizome_manifest_copy_name(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_name(m, srcm->name);
|
|
}
|
|
static int _rhizome_manifest_parse_name(rhizome_manifest *m, const char *text)
|
|
{
|
|
rhizome_manifest_set_name(m, text);
|
|
return 1;
|
|
}
|
|
|
|
static int _rhizome_manifest_test_crypt(const rhizome_manifest *m)
|
|
{
|
|
return m->payloadEncryption != PAYLOAD_CRYPT_UNKNOWN;
|
|
}
|
|
static void _rhizome_manifest_unset_crypt(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
rhizome_manifest_set_crypt(m, PAYLOAD_CRYPT_UNKNOWN);
|
|
}
|
|
static void _rhizome_manifest_copy_crypt(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
rhizome_manifest_set_crypt(m, srcm->payloadEncryption);
|
|
}
|
|
static int _rhizome_manifest_parse_crypt(rhizome_manifest *m, const char *text)
|
|
{
|
|
if (!(strcmp(text, "0") == 0 || strcmp(text, "1") == 0))
|
|
return 0;
|
|
rhizome_manifest_set_crypt(m, (text[0] == '1') ? PAYLOAD_ENCRYPTED : PAYLOAD_CLEAR);
|
|
return 1;
|
|
}
|
|
|
|
static struct rhizome_manifest_field_descriptor {
|
|
const char *label;
|
|
int core;
|
|
MANIFEST_FIELD_TESTER *test;
|
|
MANIFEST_FIELD_UNSETTER *unset;
|
|
MANIFEST_FIELD_COPIER *copy;
|
|
MANIFEST_FIELD_PARSER *parse;
|
|
}
|
|
rhizome_manifest_fields[] = {
|
|
#define FIELD(CORE, NAME) \
|
|
{ #NAME, CORE, _rhizome_manifest_test_ ## NAME, _rhizome_manifest_unset_ ## NAME, _rhizome_manifest_copy_ ## NAME, _rhizome_manifest_parse_ ## NAME }
|
|
FIELD(1, id),
|
|
FIELD(1, version),
|
|
FIELD(1, filehash),
|
|
FIELD(1, filesize),
|
|
FIELD(1, tail),
|
|
FIELD(0, BK),
|
|
FIELD(0, service),
|
|
FIELD(0, date),
|
|
FIELD(0, sender),
|
|
FIELD(0, recipient),
|
|
FIELD(0, name),
|
|
FIELD(0, crypt),
|
|
#undef FIELD
|
|
};
|
|
|
|
static struct rhizome_manifest_field_descriptor *get_rhizome_manifest_field_descriptor(const char *label)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < NELS(rhizome_manifest_fields); ++i)
|
|
if (strcasecmp(label, rhizome_manifest_fields[i].label) == 0)
|
|
return &rhizome_manifest_fields[i];
|
|
return NULL;
|
|
}
|
|
|
|
/* Overwrite a Rhizome manifest with fields from another. Used in the "add bundle" application API
|
|
* when the application supplies a partial manifest to override or add to existing manifest fields.
|
|
*
|
|
* Returns -1 if a field in the destination manifest cannot be overwritten for an unrecoverable
|
|
* reason, eg, out of memory or too many variables, leaving the destination manifest in an undefined
|
|
* state.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
int _rhizome_manifest_overwrite(struct __sourceloc __whence, rhizome_manifest *m, const rhizome_manifest *srcm)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; i < NELS(rhizome_manifest_fields); ++i) {
|
|
struct rhizome_manifest_field_descriptor *desc = &rhizome_manifest_fields[i];
|
|
if (desc->test(srcm)) {
|
|
DEBUGF(rhizome_manifest, "COPY manifest %p %s to:", srcm, desc->label);
|
|
desc->copy(__whence, m, srcm);
|
|
}
|
|
}
|
|
for (i = 0; i < srcm->var_count; ++i) {
|
|
struct rhizome_manifest_field_descriptor *desc = get_rhizome_manifest_field_descriptor(srcm->vars[i]);
|
|
if (!desc)
|
|
if (_rhizome_manifest_set(__whence, m, srcm->vars[i], srcm->values[i]) == NULL)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int rhizome_manifest_field_label_is_valid(const char *field_label, size_t field_label_len)
|
|
{
|
|
if (field_label_len == 0 || field_label_len > MAX_MANIFEST_FIELD_LABEL_LEN)
|
|
return 0;
|
|
if (!isalpha(field_label[0]))
|
|
return 0;
|
|
unsigned i;
|
|
for (i = 1; i != field_label_len; ++i)
|
|
if (!isalnum(field_label[i]))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
int rhizome_manifest_field_value_is_valid(const char *field_value, size_t field_value_len)
|
|
{
|
|
if (field_value_len >= MAX_MANIFEST_BYTES)
|
|
return 0;
|
|
unsigned i;
|
|
for (i = 0; i < field_value_len; ++i)
|
|
if (field_value[i] == '\0' || field_value[i] == '\r' || field_value[i] == '\n')
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
/* Parse a single Rhizome manifest field. Used for incremental construction or modification of
|
|
* manifests.
|
|
*
|
|
* If the supplied field_label is invalid (does not conform to the syntax for field names) or the
|
|
* field_value string is too long or contains a NUL (ASCII 0), CR (ASCII 13) or LF (ASCII 10), then
|
|
* returns RHIZOME_MANIFEST_SYNTAX_ERROR and leaves the manifest unchanged.
|
|
*
|
|
* If a field with the given label already exists in the manifest, then returns
|
|
* RHIZOME_MANIFEST_DUPLICATE_FIELD without modifying the manifest. (To overwrite an existing
|
|
* field, first remove it by calling rhizome_manifest_remove_field() then call
|
|
* rhizome_manifest_parse_field().)
|
|
*
|
|
* If the maximum number of fields are already occupied in the manifest, then returns
|
|
* RHIZOME_MANIFEST_OVERFLOW and leaves the manifest unchanged.
|
|
*
|
|
* If the supplied field_value is invalid (does not parse according to the field's syntax, eg,
|
|
* unsigned integer) then returns RHIZOME_MANIFEST_INVALID if it is a core field, otherwise returns
|
|
* RHIZOME_MANIFEST_MALFORMED and leaves the manifest unchanged. Unsupported fields are not parsed;
|
|
* their value string is simply stored, so they cannot evoke a MALFORMED result.
|
|
*
|
|
* Otherwise, sets the relevant element(s) of the manifest structure and appends the field_label and
|
|
* field_value strings into the m->vars[] and m->values[] arrays, as pointers to malloc(3)ed NUL
|
|
* terminated strings, and increments m->var_count. Returns RHIZOME_MANIFEST_OK.
|
|
*
|
|
* Returns -1 (RHIZOME_MANIFEST_ERROR) if there is an unrecoverable error (eg, malloc(3) returns
|
|
* NULL, out of memory).
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
enum rhizome_manifest_parse_status
|
|
rhizome_manifest_parse_field(rhizome_manifest *m, const char *field_label, size_t field_label_len, const char *field_value, size_t field_value_len)
|
|
{
|
|
// Syntax check on field label.
|
|
if (!rhizome_manifest_field_label_is_valid(field_label, field_label_len)) {
|
|
DEBUGF(rhizome_manifest, "Invalid manifest field name: %s", alloca_toprint(100, field_label, field_label_len));
|
|
return RHIZOME_MANIFEST_SYNTAX_ERROR;
|
|
}
|
|
const char *label = alloca_strndup(field_label, field_label_len);
|
|
// Sanity and syntax check on field value.
|
|
if (!rhizome_manifest_field_value_is_valid(field_value, field_value_len)) {
|
|
DEBUGF(rhizome_manifest, "Invalid manifest field value: %s=%s", label, alloca_toprint(100, field_value, field_value_len));
|
|
return RHIZOME_MANIFEST_SYNTAX_ERROR;
|
|
}
|
|
const char *value = alloca_strndup(field_value, field_value_len);
|
|
struct rhizome_manifest_field_descriptor *desc = get_rhizome_manifest_field_descriptor(label);
|
|
enum rhizome_manifest_parse_status status = RHIZOME_MANIFEST_OK;
|
|
assert(m->var_count <= NELS(m->vars));
|
|
if (desc ? desc->test(m) : rhizome_manifest_get(m, label) != NULL) {
|
|
DEBUGF(rhizome_manifest, "Duplicate field at %s=%s", label, alloca_toprint(100, field_value, field_value_len));
|
|
status = RHIZOME_MANIFEST_DUPLICATE_FIELD;
|
|
} else if (m->var_count == NELS(m->vars)) {
|
|
DEBUGF(rhizome_manifest, "Manifest field limit reached at %s=%s", label, alloca_toprint(100, field_value, field_value_len));
|
|
status = RHIZOME_MANIFEST_OVERFLOW;
|
|
} else if (desc) {
|
|
if (!desc->parse(m, value)) {
|
|
DEBUGF(rhizome_manifest, "Manifest field parse failed at %s=%s", label, alloca_toprint(100, field_value, field_value_len));
|
|
status = desc->core ? RHIZOME_MANIFEST_INVALID : RHIZOME_MANIFEST_MALFORMED;
|
|
}
|
|
} else if (rhizome_manifest_set(m, label, value) == NULL)
|
|
status = RHIZOME_MANIFEST_ERROR;
|
|
if (status != RHIZOME_MANIFEST_OK) {
|
|
DEBUGF(rhizome_manifest, "SKIP manifest %p %s = %s (status=%d)", m, label, alloca_str_toprint(value), status);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/* Remove the field with the given label from the manifest.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
int rhizome_manifest_remove_field(rhizome_manifest *m, const char *field_label, size_t field_label_len)
|
|
{
|
|
if (!rhizome_manifest_field_label_is_valid(field_label, field_label_len)) {
|
|
DEBUGF(rhizome_manifest, "Invalid manifest field name: %s", alloca_toprint(100, field_label, field_label_len));
|
|
return 0;
|
|
}
|
|
const char *label = alloca_strndup(field_label, field_label_len);
|
|
struct rhizome_manifest_field_descriptor *desc = NULL;
|
|
unsigned i;
|
|
for (i = 0; desc == NULL && i < NELS(rhizome_manifest_fields); ++i)
|
|
if (strcasecmp(label, rhizome_manifest_fields[i].label) == 0)
|
|
desc = &rhizome_manifest_fields[i];
|
|
if (!desc)
|
|
return rhizome_manifest_del(m, label);
|
|
if (!desc->test(m))
|
|
return 0;
|
|
desc->unset(__WHENCE__, m);
|
|
return 1;
|
|
}
|
|
|
|
/* If all essential (transport) fields are present and well formed then sets the m->finalised field
|
|
* and returns 1, otherwise returns 0.
|
|
*
|
|
* Sets m->malformed if any non-essential fields are missing or invalid. It is up to the caller to
|
|
* check m->malformed and decide whether or not to process a malformed manifest.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
int rhizome_manifest_validate(rhizome_manifest *m)
|
|
{
|
|
return (m->finalised || rhizome_manifest_validate_reason(m) == NULL) ? 1 : 0;
|
|
}
|
|
|
|
/* If all essential (transport) fields are present and well formed then sets the m->finalised field
|
|
* and returns NULL, otherwise returns a pointer to a static string (not malloc(3)ed) describing the
|
|
* problem.
|
|
*
|
|
* If any non-essential fields are missing or invalid, then sets m->malformed to point to a static
|
|
* string describing the problem. It is up to the caller to check m->malformed and decide whether
|
|
* or not to process a malformed manifest.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
const char *rhizome_manifest_validate_reason(rhizome_manifest *m)
|
|
{
|
|
const char *reason = NULL;
|
|
if (!m->has_id)
|
|
reason = "Missing 'id' field";
|
|
else if (m->version == 0)
|
|
reason = "Missing 'version' field";
|
|
else if (m->filesize == RHIZOME_SIZE_UNSET)
|
|
reason = "Missing 'filesize' field";
|
|
else if (m->filesize == 0 && m->has_filehash)
|
|
reason = "Spurious 'filehash' field";
|
|
else if (m->filesize != 0 && !m->has_filehash)
|
|
reason = "Missing 'filehash' field";
|
|
if (reason)
|
|
DEBUG(rhizome_manifest, reason);
|
|
if (m->service == NULL)
|
|
m->malformed = "Missing 'service' field";
|
|
else if (strcmp(m->service, RHIZOME_SERVICE_FILE) == 0) {
|
|
if (m->name == NULL)
|
|
m->malformed = "Manifest with service='" RHIZOME_SERVICE_FILE "' missing 'name' field";
|
|
} else if (strcmp(m->service, RHIZOME_SERVICE_MESHMS) == 0
|
|
|| strcmp(m->service, RHIZOME_SERVICE_MESHMS2) == 0
|
|
) {
|
|
if (!m->has_recipient)
|
|
m->malformed = "Manifest missing 'recipient' field";
|
|
else if (!m->has_sender)
|
|
m->malformed = "Manifest missing 'sender' field";
|
|
}
|
|
else if (!rhizome_str_is_manifest_service(m->service))
|
|
m->malformed = "Manifest invalid 'service' field";
|
|
else if (!m->has_date)
|
|
m->malformed = "Missing 'date' field";
|
|
if (m->malformed)
|
|
DEBUG(rhizome_manifest, m->malformed);
|
|
m->finalised = (reason == NULL);
|
|
return reason;
|
|
}
|
|
|
|
int rhizome_read_manifest_from_file(rhizome_manifest *m, const char *filename)
|
|
{
|
|
ssize_t bytes = read_whole_file(filename, m->manifestdata, sizeof m->manifestdata);
|
|
if (bytes == -1)
|
|
return -1;
|
|
m->manifest_all_bytes = (size_t) bytes;
|
|
return rhizome_manifest_parse(m);
|
|
}
|
|
|
|
rhizome_manifest *_rhizome_new_manifest(struct __sourceloc __whence)
|
|
{
|
|
rhizome_manifest *m=emalloc_zero(sizeof(rhizome_manifest));
|
|
if (m){
|
|
DEBUGF(rhizome_manifest, "NEW manifest %p", m);
|
|
|
|
// Set global defaults for a manifest (which are not zero)
|
|
rhizome_manifest_clear(m);
|
|
}
|
|
return m;
|
|
}
|
|
|
|
void _rhizome_manifest_free(struct __sourceloc __whence, rhizome_manifest *m)
|
|
{
|
|
if (!m) return;
|
|
DEBUGF(rhizome_manifest, "FREE manifest %p", m);
|
|
|
|
/* Free variable and signature blocks. */
|
|
rhizome_manifest_clear(m);
|
|
|
|
return;
|
|
}
|
|
|
|
/* Converts the variable list into manifest text body and computes the hash. Does not sign.
|
|
* Returns 0 if successful, -1 if the result exceeds the manifest size limit.
|
|
*/
|
|
static struct rhizome_bundle_result rhizome_manifest_pack_variables(rhizome_manifest *m)
|
|
{
|
|
assert(m->var_count <= NELS(m->vars));
|
|
strbuf sb = strbuf_local_buf(m->manifestdata);
|
|
unsigned i;
|
|
for (i = 0; i < m->var_count; ++i) {
|
|
strbuf_puts(sb, m->vars[i]);
|
|
strbuf_putc(sb, '=');
|
|
strbuf_puts(sb, m->values[i]);
|
|
strbuf_putc(sb, '\n');
|
|
}
|
|
if (strbuf_overrun(sb)) {
|
|
return rhizome_bundle_result_sprintf(
|
|
RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG,
|
|
"Manifest too big: body of %zu bytes exceeds limit of %zu",
|
|
strbuf_count(sb) + 1, sizeof m->manifestdata);
|
|
}
|
|
m->manifest_body_bytes = strbuf_len(sb) + 1;
|
|
DEBUGF(rhizome, "Repacked variables into manifest: %zu bytes", m->manifest_body_bytes);
|
|
m->manifest_all_bytes = m->manifest_body_bytes;
|
|
m->selfSigned = 0;
|
|
return rhizome_bundle_result(RHIZOME_BUNDLE_STATUS_NEW);
|
|
}
|
|
|
|
/* Sign this manifest using it's own BID secret key. Manifest must not already be signed.
|
|
* Manifest body hash must already be computed.
|
|
*/
|
|
static struct rhizome_bundle_result rhizome_manifest_selfsign(rhizome_manifest *m)
|
|
{
|
|
assert(m->manifest_body_bytes > 0);
|
|
assert(m->manifest_body_bytes <= sizeof m->manifestdata);
|
|
assert(m->manifestdata[m->manifest_body_bytes - 1] == '\0');
|
|
assert(m->manifest_body_bytes == m->manifest_all_bytes); // no signature yet
|
|
if (!m->haveSecret)
|
|
return rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_READONLY, "Missing bundle secret");
|
|
|
|
size_t sigLen = 1 + crypto_sign_BYTES + crypto_sign_PUBLICKEYBYTES;
|
|
if (sizeof m->manifestdata - m->manifest_body_bytes < sigLen)
|
|
return rhizome_bundle_result_sprintf(RHIZOME_BUNDLE_STATUS_MANIFEST_TOO_BIG,
|
|
"Manifest too big: body of %zu + signature of %zu bytes exceeds limit of %zu",
|
|
m->manifest_body_bytes,
|
|
sigLen,
|
|
sizeof m->manifestdata);
|
|
|
|
crypto_hash_sha512(m->manifesthash.binary, m->manifestdata, m->manifest_body_bytes);
|
|
uint8_t *p = &m->manifestdata[m->manifest_body_bytes];
|
|
*p++ = 0x17; // CryptoSign
|
|
if (crypto_sign_detached(p, NULL, m->manifesthash.binary, sizeof m->manifesthash.binary, m->keypair.binary))
|
|
return rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_ERROR, "crypto_sign_detached() failed");
|
|
p+=crypto_sign_BYTES;
|
|
bcopy(m->keypair.public_key.binary, p, crypto_sign_BYTES);
|
|
m->manifest_all_bytes = m->manifest_body_bytes + sigLen;
|
|
m->selfSigned = 1;
|
|
return rhizome_bundle_result(RHIZOME_BUNDLE_STATUS_NEW);
|
|
}
|
|
|
|
int rhizome_write_manifest_file(rhizome_manifest *m, const char *path, char append)
|
|
{
|
|
DEBUGF(rhizome, "write manifest (%zd bytes) to %s", m->manifest_all_bytes, path);
|
|
assert(m->finalised);
|
|
int fd = open(path, O_WRONLY | O_CREAT | (append ? O_APPEND : 0), 0666);
|
|
if (fd == -1)
|
|
return WHYF_perror("open(%s,O_WRONLY|O_CREAT%s,0666)", alloca_str_toprint(path), append ? "|O_APPEND" : "");
|
|
int ret = 0;
|
|
unsigned char marker[4];
|
|
struct iovec iov[2];
|
|
int iovcnt = 1;
|
|
iov[0].iov_base = m->manifestdata;
|
|
iov[0].iov_len = m->manifest_all_bytes;
|
|
if (append) {
|
|
write_uint16(marker, m->manifest_all_bytes);
|
|
marker[2] = 0x41;
|
|
marker[3] = 0x10;
|
|
iov[1].iov_base = marker;
|
|
iov[1].iov_len = sizeof marker;
|
|
iovcnt = 2;
|
|
}
|
|
if (writev_all(fd, iov, iovcnt) == -1)
|
|
ret = -1;
|
|
if (close(fd) == -1)
|
|
ret = WHY_perror("close");
|
|
return ret;
|
|
}
|
|
|
|
int rhizome_manifest_dump(rhizome_manifest *m, const char *msg)
|
|
{
|
|
unsigned i;
|
|
WHYF("Dumping manifest %s:", msg);
|
|
for(i=0;i<m->var_count;i++)
|
|
WHYF("[%s]=[%s]\n", m->vars[i], m->values[i]);
|
|
return 0;
|
|
}
|
|
|
|
struct rhizome_bundle_result rhizome_manifest_finalise(rhizome_manifest *m, rhizome_manifest **mout, int deduplicate)
|
|
{
|
|
IN();
|
|
assert(*mout == NULL);
|
|
if (!m->finalised) {
|
|
const char *reason = rhizome_manifest_validate_reason(m);
|
|
if (reason)
|
|
RETURN(rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, reason));
|
|
}
|
|
// The duplicate detection logic exists to filter out files repeatedly added with no existing
|
|
// manifest (ie, "de-bounce" for the "Add File" user interface action).
|
|
// 1. If a manifest was supplied with a bundle ID, don't check for a duplicate.
|
|
// 2. Never perform duplicate detection on journals (the first append does not supply a bundle ID,
|
|
// but all subsequent appends supply a bundle ID, so are caught by case (1)).
|
|
if (deduplicate && m->haveSecret != EXISTING_BUNDLE_ID && !m->is_journal) {
|
|
enum rhizome_bundle_status status = rhizome_find_duplicate(m, mout);
|
|
switch (status) {
|
|
case RHIZOME_BUNDLE_STATUS_DUPLICATE:
|
|
assert(*mout != NULL);
|
|
assert(*mout != m);
|
|
RETURN(rhizome_bundle_result(status));
|
|
case RHIZOME_BUNDLE_STATUS_ERROR:
|
|
if (*mout != NULL && *mout != m) {
|
|
rhizome_manifest_free(*mout);
|
|
*mout = NULL;
|
|
}
|
|
RETURN(rhizome_bundle_result(status));
|
|
case RHIZOME_BUNDLE_STATUS_NEW:
|
|
break;
|
|
default:
|
|
FATALF("rhizome_find_duplicate() returned %d", status);
|
|
}
|
|
}
|
|
assert(*mout == NULL);
|
|
*mout = m;
|
|
|
|
/* Convert to final form for signing and writing to disk */
|
|
struct rhizome_bundle_result result = rhizome_manifest_pack_variables(m);
|
|
if (result.status != RHIZOME_BUNDLE_STATUS_NEW)
|
|
RETURN(result);
|
|
rhizome_bundle_result_free(&result);
|
|
|
|
/* Sign it */
|
|
assert(!m->selfSigned);
|
|
result = rhizome_manifest_selfsign(m);
|
|
if (result.status == RHIZOME_BUNDLE_STATUS_NEW) {
|
|
assert(m->selfSigned);
|
|
rhizome_bundle_result_free(&result);
|
|
/* mark manifest as finalised */
|
|
result.status = rhizome_add_manifest_to_store(m, mout);
|
|
}
|
|
|
|
RETURN(result);
|
|
OUT();
|
|
}
|
|
|
|
/* Returns 1 if the name was successfully set, 0 if not.
|
|
*/
|
|
int rhizome_manifest_set_name_from_path(rhizome_manifest *m, const char *filepath)
|
|
{
|
|
const char *name = strrchr(filepath, '/');
|
|
if (name)
|
|
++name; // skip '/'
|
|
else
|
|
name = filepath;
|
|
if (!rhizome_str_is_manifest_name(name)) {
|
|
WARNF("invalid rhizome name %s -- not used", alloca_str_toprint(name));
|
|
return 0;
|
|
}
|
|
rhizome_manifest_set_name(m, name);
|
|
return 1;
|
|
}
|
|
|
|
/* Fill in a few missing manifest fields, to make it easier to use when adding new files:
|
|
* - use the current time for "date" and "version"
|
|
* - use the given author SID, or the 'sender' if present, as the author
|
|
* - create an ID if there is none, otherwise authenticate the existing one
|
|
* - if service is file, then use the payload file's basename for "name"
|
|
*
|
|
* Return a rhizome_bundle_status code together with a pointer to a text string describing the
|
|
* reason for the failure (always an internal/unrecoverable error). The string is accompanied by a
|
|
* pointer to a "free" method (eg, free(3)) that must be called to release the string before the
|
|
* pointer is discarded.
|
|
*/
|
|
struct rhizome_bundle_result rhizome_fill_manifest(rhizome_manifest *m, const char *filepath)
|
|
{
|
|
/* Set version of manifest from current time if not already set. */
|
|
if (m->version == 0)
|
|
rhizome_manifest_set_version(m, gettime_ms());
|
|
|
|
/* Fill in the bundle secret and bundle ID.
|
|
*/
|
|
switch (m->haveSecret) {
|
|
case SECRET_UNKNOWN:
|
|
// If the Bundle Id is already known, then derive the bundle secret from BK if known.
|
|
if (m->has_id) {
|
|
DEBUGF(rhizome, "discover secret for bundle bid=%s", alloca_tohex_rhizome_bid_t(m->keypair.public_key));
|
|
rhizome_authenticate_author(m);
|
|
break;
|
|
}
|
|
// If there is no Bundle Id, then create a new bundle Id and secret from scratch.
|
|
DEBUG(rhizome, "creating new bundle");
|
|
if (rhizome_manifest_createid(m) == -1) {
|
|
return rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_ERROR, "Could not bind manifest to an ID");
|
|
}
|
|
// fall through to set the BK field...
|
|
case NEW_BUNDLE_ID:
|
|
assert(m->has_id);
|
|
// If no 'authorSidp' parameter was supplied but the manifest has a 'sender' field, then use the
|
|
// sender as the author.
|
|
if (m->authorship == ANONYMOUS && m->has_sender)
|
|
rhizome_manifest_set_author(m, &m->sender);
|
|
// If we know the author then set the BK field.
|
|
if (m->authorship != ANONYMOUS) {
|
|
DEBUGF(rhizome, "set BK field for bid=%s", alloca_tohex_rhizome_bid_t(m->keypair.public_key));
|
|
rhizome_manifest_add_bundle_key(m);
|
|
}
|
|
break;
|
|
case EXISTING_BUNDLE_ID:
|
|
// If modifying an existing bundle, try to discover the bundle secret key and the author.
|
|
assert(m->has_id);
|
|
DEBUGF(rhizome, "modifying existing bundle bid=%s", alloca_tohex_rhizome_bid_t(m->keypair.public_key));
|
|
rhizome_authenticate_author(m);
|
|
// TODO assert that new version > old version?
|
|
break;
|
|
default:
|
|
FATALF("haveSecret = %d", m->haveSecret);
|
|
}
|
|
|
|
switch (m->authorship) {
|
|
case ANONYMOUS:
|
|
case AUTHOR_AUTHENTIC:
|
|
break; // all good
|
|
case AUTHOR_UNKNOWN:
|
|
return rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_READONLY, "Author is not in keyring");
|
|
case AUTHOR_IMPOSTOR:
|
|
return rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_READONLY, "Incorrect author");
|
|
case AUTHENTICATION_ERROR:
|
|
return rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_ERROR, "Error authenticating author");
|
|
default:
|
|
FATALF("m->authorship = %d", (int)m->authorship);
|
|
}
|
|
|
|
/* Service field must already be set.
|
|
*/
|
|
if (m->service == NULL)
|
|
return rhizome_bundle_result_static(RHIZOME_BUNDLE_STATUS_INVALID, "Missing 'service' field");
|
|
|
|
DEBUGF(rhizome, "manifest contains service=%s", m->service);
|
|
|
|
/* Fill in 'date' field to current time unless already set.
|
|
*/
|
|
if (!m->has_date) {
|
|
rhizome_manifest_set_date(m, (int64_t) gettime_ms());
|
|
DEBUGF(rhizome, "missing 'date', set default date=%"PRItime_ms_t, m->date);
|
|
}
|
|
|
|
/* Fill in 'name' field if service=file.
|
|
*/
|
|
if (strcasecmp(RHIZOME_SERVICE_FILE, m->service) == 0) {
|
|
if (m->name) {
|
|
DEBUGF(rhizome, "manifest already contains name=%s", alloca_str_toprint(m->name));
|
|
} else if (filepath)
|
|
rhizome_manifest_set_name_from_path(m, filepath);
|
|
else
|
|
DEBUGF(rhizome, "manifest missing 'name'");
|
|
}
|
|
|
|
/* Fill in 'crypt' field. Anything sent from one person to another should be considered private
|
|
* and encrypted by default.
|
|
*/
|
|
if ( m->payloadEncryption == PAYLOAD_CRYPT_UNKNOWN
|
|
&& m->has_recipient
|
|
&& !is_sid_t_broadcast(m->recipient)
|
|
) {
|
|
DEBUGF(rhizome, "Implicitly adding payload encryption due to presense of recipient field");
|
|
rhizome_manifest_set_crypt(m, PAYLOAD_ENCRYPTED);
|
|
}
|
|
|
|
return rhizome_bundle_result(RHIZOME_BUNDLE_STATUS_NEW);
|
|
}
|
|
|
|
/* Work out the authorship status of the bundle without performing expensive cryptographic checks.
|
|
* Sets the 'authorship' element and returns 1 if an author was found, 0 if not.
|
|
*
|
|
* @author Andrew Bettison <andrew@servalproject.com>
|
|
*/
|
|
int rhizome_lookup_author(rhizome_manifest *m)
|
|
{
|
|
IN();
|
|
switch (m->authorship) {
|
|
case AUTHOR_LOCAL:
|
|
case AUTHOR_AUTHENTIC:
|
|
case AUTHOR_REMOTE:
|
|
RETURN(1);
|
|
case AUTHOR_NOT_CHECKED:
|
|
DEBUGF(rhizome, "manifest %p lookup author=%s", m, alloca_tohex_sid_t(m->author));
|
|
if (keyring_find_identity_sid(keyring, &m->author)) {
|
|
DEBUGF(rhizome, "found author");
|
|
m->authorship = AUTHOR_LOCAL;
|
|
RETURN(1);
|
|
}
|
|
// fall through
|
|
case ANONYMOUS:
|
|
if (m->has_sender) {
|
|
DEBUGF(rhizome, "manifest %p lookup sender=%s", m, alloca_tohex_sid_t(m->sender));
|
|
if (keyring_find_identity_sid(keyring, &m->sender)) {
|
|
DEBUGF(rhizome, "found sender");
|
|
rhizome_manifest_set_author(m, &m->sender);
|
|
m->authorship = AUTHOR_LOCAL;
|
|
RETURN(1);
|
|
} else if(crypto_ismatching_sign_sid(&m->keypair.public_key, &m->sender)) {
|
|
// if the author matches the bundle id...
|
|
DEBUGF(rhizome, "sender matches manifest signature");
|
|
m->author = m->sender;
|
|
m->authorship = AUTHOR_REMOTE;
|
|
RETURN(1);
|
|
}
|
|
}
|
|
// fall through
|
|
case AUTHENTICATION_ERROR:
|
|
case AUTHOR_UNKNOWN:
|
|
case AUTHOR_IMPOSTOR:
|
|
RETURN(0);
|
|
}
|
|
FATALF("m->authorship = %d", m->authorship);
|
|
RETURN(0);
|
|
OUT();
|
|
}
|