serval-dna/rhizome_bundle.c
Andrew Bettison f4293e8ab3 Add "rhizome hash file" command
Also fixed a file descriptor leak in rhizome_hash_file() -- missing fclose()
2012-05-21 14:42:53 +09:30

519 lines
15 KiB
C

/*
Serval Distributed Numbering Architecture (DNA)
Copyright (C) 2010 Paul Gardner-Stephen
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
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 "serval.h"
#include "rhizome.h"
#include <stdlib.h>
rhizome_manifest *rhizome_read_manifest_file(const char *filename, int bufferP, int flags)
{
if (bufferP>MAX_MANIFEST_BYTES) return NULL;
rhizome_manifest *m = rhizome_new_manifest();
if (!m) return NULL;
if (bufferP) {
m->manifest_bytes=bufferP;
memcpy(m->manifestdata, filename, m->manifest_bytes);
} else {
FILE *f=fopen(filename,"r");
if (!f) {
WHYF("Could not open manifest file %s for reading.", filename);
rhizome_manifest_free(m);
return NULL;
}
m->manifest_bytes = fread(m->manifestdata,1,MAX_MANIFEST_BYTES,f);
fclose(f);
}
m->manifest_all_bytes=m->manifest_bytes;
/* Parse out variables, signature etc */
int ofs=0;
while((ofs<m->manifest_bytes)&&(m->manifestdata[ofs]))
{
int i;
char line[1024],var[1024],value[1024];
while((ofs<m->manifest_bytes)&&
(m->manifestdata[ofs]==0x0a||
m->manifestdata[ofs]==0x09||
m->manifestdata[ofs]==0x20||
m->manifestdata[ofs]==0x0d)) ofs++;
for(i=0;(i<(m->manifest_bytes-ofs))
&&(i<1023)
&&(m->manifestdata[ofs+i]!=0x00)
&&(m->manifestdata[ofs+i]!=0x0d)
&&(m->manifestdata[ofs+i]!=0x0a);i++)
line[i]=m->manifestdata[ofs+i];
ofs+=i;
line[i]=0;
/* Ignore blank lines */
if (line[0]==0) continue;
/* Ignore comment lines */
if (line[0] == '#' || line[0] == '!') continue;
/* Parse property lines */
/* This could be improved to parse Java's Properties.store() output, by handling backlash
escapes and continuation lines */
if (sscanf(line,"%[^=]=%[^\n\r]", var, value)==2) {
if (rhizome_manifest_get(m,var,NULL,0)) {
WARNF("Ill formed manifest file, duplicate variable \"%s\"-- keeping first value)", var);
m->errors++;
} else if (m->var_count<MAX_MANIFEST_VARS) {
/*`
if (debug & DEBUG_RHIZOME) {
char buf[80];
DEBUGF("read manifest line: %s=%s", var, catv(value, buf, sizeof buf));
}
*/
m->vars[m->var_count]=strdup(var);
m->values[m->var_count]=strdup(value);
m->var_count++;
}
} else {
char buf[80];
WARNF("Skipping malformed line in manifest file %s: %s", bufferP ? "<buffer>" : filename, catv(line, buf, sizeof buf));
}
}
/* The null byte gets included in the check sum */
if (ofs<m->manifest_bytes) ofs++;
/* Remember where the text ends */
int end_of_text=ofs;
if (flags&RHIZOME_VERIFY) {
/* Calculate hash of the text part of the file, as we need to couple this with
each signature block to */
crypto_hash_sha512(m->manifesthash,m->manifestdata,end_of_text);
/* Read signature blocks from file. */
while(ofs<m->manifest_bytes) {
if (debug & DEBUG_RHIZOME) DEBUGF("ofs=0x%x, m->manifest_bytes=0x%x", ofs,m->manifest_bytes);
rhizome_manifest_extract_signature(m,&ofs);
}
if (m->sig_count==0) {
m->errors++;
}
/* Make sure that id variable is correct */
{
char *id=rhizome_manifest_get(m,"id",NULL,0);
if (!id) {
WARN("Manifest lacks 'id' field");
m->errors++;
}
else {
unsigned char manifest_bytes[crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES];
rhizome_hex_to_bytes(id,manifest_bytes,
crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES*2);
if ((m->sig_count==0)||
memcmp(&m->signatories[0][0],manifest_bytes,
crypto_sign_edwards25519sha512batch_PUBLICKEYBYTES))
{
if (debug&DEBUG_RHIZOME) {
if (m->sig_count>0) {
DEBUGF("Manifest id variable does not match first signature block (signature key is %s)",
/* XXX bit of a hack that relies on SIDs and signing public keys being the same length */
overlay_render_sid(&m->signatories[0][0])
);
} else {
DEBUG("Manifest has no signature blocks, but should have self-signature block");
}
}
m->errors++;
m->selfSigned=0;
} else m->selfSigned=1;
}
}
if (debug&DEBUG_RHIZOME)
DEBUG("Group membership determination not implemented (see which signatories are groups? what about manifests signed by groups we don't yet know about?)");
}
m->manifest_bytes=end_of_text;
return m;
}
int rhizome_strn_is_file_hash(const char *text)
{
int i;
for (i = 0; i != SHA512_DIGEST_LENGTH * 2; ++i)
if (!isxdigit(text[i]))
return 0;
return 1;
}
int rhizome_str_is_file_hash(const char *text)
{
size_t len = strlen(text);
return len == SHA512_DIGEST_LENGTH * 2 && rhizome_strn_is_file_hash(text);
}
int rhizome_hash_file(const char *filename,char *hash_out)
{
/* Gnarf! NaCl's crypto_hash() function needs the whole file passed in in one
go. Trouble is, we need to run Serval DNA on filesystems that lack mmap(),
and may be very resource constrained. Thus we need a streamable SHA-512
implementation.
*/
FILE *f = fopen(filename, "r");
if (!f) {
WHY_perror("fopen");
return WHYF("Could not open %s to calculate SHA512 hash.", filename);
}
SHA512_CTX context;
SHA512_Init(&context);
while (!feof(f)) {
unsigned char buffer[8192];
int r = fread(buffer, 1, 8192, f);
if (r == -1) {
WHY_perror("fread");
fclose(f);
return WHYF("Error reading %s to calculate SHA512 hash", filename);
}
if (r > 0)
SHA512_Update(&context, buffer, r);
}
SHA512_End(&context, (char *)hash_out);
fclose(f);
return 0;
}
char *rhizome_manifest_get(const rhizome_manifest *m, const char *var, char *out, int maxlen)
{
int i,j;
if (!m) return NULL;
for(i=0;i<m->var_count;i++)
if (!strcmp(m->vars[i],var)) {
if (out) {
for(j=0;(j<maxlen);j++) {
out[j]=m->values[i][j];
if (!out[j]) break;
}
}
return m->values[i];
}
return NULL;
}
long long rhizome_manifest_get_ll(rhizome_manifest *m, const char *var)
{
if (!m)
return -1;
int i;
for (i = 0;i != m->var_count; ++i)
if (!strcmp(m->vars[i], var)) {
char *vp = m->values[i];
char *ep = vp;
long long val = strtoll(vp, &ep, 10);
return (ep != vp && *ep == '\0') ? val : -1;
}
return -1;
}
double rhizome_manifest_get_double(rhizome_manifest *m,char *var,double default_value)
{
int i;
if (!m) return default_value;
for(i=0;i<m->var_count;i++)
if (!strcmp(m->vars[i],var))
return strtod(m->values[i],NULL);
return default_value;
}
int rhizome_manifest_set(rhizome_manifest *m, const char *var, const char *value)
{
int i;
if (!m) return -1;
for(i=0;i<m->var_count;i++)
if (!strcmp(m->vars[i],var)) {
free(m->values[i]);
m->values[i]=strdup(value);
m->finalised=0;
return 0;
}
if (m->var_count>=MAX_MANIFEST_VARS) return -1;
m->vars[m->var_count]=strdup(var);
m->values[m->var_count]=strdup(value);
m->var_count++;
m->finalised=0;
return 0;
}
int rhizome_manifest_set_ll(rhizome_manifest *m,char *var,long long value)
{
char svalue[100];
snprintf(svalue,100,"%lld",value);
return rhizome_manifest_set(m,var,svalue);
}
#define MAX_RHIZOME_MANIFESTS 16
rhizome_manifest manifests[MAX_RHIZOME_MANIFESTS];
char manifest_free[MAX_RHIZOME_MANIFESTS];
int manifest_first_free=-1;
const char *manifest_alloc_sourcefiles[MAX_RHIZOME_MANIFESTS];
const char *manifest_alloc_functions[MAX_RHIZOME_MANIFESTS];
int manifest_alloc_lines[MAX_RHIZOME_MANIFESTS];
const char *manifest_free_sourcefiles[MAX_RHIZOME_MANIFESTS];
const char *manifest_free_functions[MAX_RHIZOME_MANIFESTS];
int manifest_free_lines[MAX_RHIZOME_MANIFESTS];
rhizome_manifest *_rhizome_new_manifest(const char *filename,const char *funcname,
int line)
{
if (manifest_first_free<0) {
/* Setup structures */
int i;
for(i=0;i<MAX_RHIZOME_MANIFESTS;i++) {
manifest_alloc_sourcefiles[i]="<never allocated>";
manifest_alloc_functions[i]="<never allocated>";
manifest_alloc_lines[i]=-1;
manifest_free_sourcefiles[i]="<never freed>";
manifest_free_functions[i]="<never freed>";
manifest_free_lines[i]=-1;
manifest_free[i]=1;
}
manifest_first_free=0;
}
/* No free manifests */
if (manifest_first_free>=MAX_RHIZOME_MANIFESTS)
{
int i;
fprintf(stderr,"%s:%d:%s() call to rhizome_new_manifest() could not be serviced.\n (no free manifest records, this probably indicates a memory leak.)\n",
filename,line,funcname);
fprintf(stderr," Manifest Slot# | Last allocated by\n");
for(i=0;i<MAX_RHIZOME_MANIFESTS;i++) {
fprintf(stderr," %-14d | %s:%d in %s()\n",
i,
manifest_alloc_sourcefiles[i],
manifest_alloc_lines[i],
manifest_alloc_functions[i]);
}
return NULL;
}
rhizome_manifest *m=&manifests[manifest_first_free];
bzero(m,sizeof(rhizome_manifest));
m->manifest_record_number=manifest_first_free;
/* Indicate where manifest was allocated, and that it is no longer
free. */
manifest_alloc_sourcefiles[manifest_first_free]=filename;
manifest_alloc_lines[manifest_first_free]=line;
manifest_alloc_functions[manifest_first_free]=funcname;
manifest_free[manifest_first_free]=0;
manifest_free_sourcefiles[manifest_first_free]="<not freed>";
manifest_free_functions[manifest_first_free]="<not freed>";
manifest_free_lines[manifest_first_free]=-1;
/* Work out where next free manifest record lives */
for(;manifest_first_free<MAX_RHIZOME_MANIFESTS
&&(!manifest_free[manifest_first_free]);
manifest_first_free++)
continue;
return m;
}
void _rhizome_manifest_free(const char *sourcefile,const char *funcname,int line,
rhizome_manifest *m)
{
if (!m) return;
int i;
int mid=m->manifest_record_number;
if (m!=&manifests[mid]) {
fprintf(stderr,"%s:%d:%s() called rhizome_manifest_free() and asked to free"
" manifest %p, which claims to be manifest slot #%d (%p), but isn't.\n",
sourcefile,line,funcname,m,mid,&manifests[mid]);
exit(-1);
}
if (manifest_free[mid]) {
fprintf(stderr,"%s:%d:%s() called rhizome_manifest_free() and asked to free"
" manifest slot #%d (%p), which has already been freed at %s:%d:%s().\n",
sourcefile,line,funcname,mid,m,
manifest_free_sourcefiles[mid],
manifest_free_lines[mid],
manifest_free_functions[mid]);
exit(-1);
}
/* Free variable and signature blocks.
XXX These should be moved to malloc-free storage eventually */
for(i=0;i<m->var_count;i++)
{ free(m->vars[i]); free(m->values[i]);
m->vars[i]=NULL; m->values[i]=NULL; }
for(i=0;i<m->sig_count;i++)
{ free(m->signatories[i]);
m->signatories[i]=NULL;
}
if (m->dataFileName) free(m->dataFileName);
m->dataFileName=NULL;
manifest_free[mid]=1;
manifest_free_sourcefiles[mid]=sourcefile;
manifest_free_functions[mid]=funcname;
manifest_free_lines[mid]=line;
if (mid<manifest_first_free) manifest_first_free=mid;
return;
}
/* Convert variable list to string, complaining if it ends up
too long.
Signatures etc will be added later. */
int rhizome_manifest_pack_variables(rhizome_manifest *m)
{
int i,ofs=0;
for(i=0;i<m->var_count;i++)
{
if ((ofs+strlen(m->vars[i])+1+strlen(m->values[i])+1+1)>MAX_MANIFEST_BYTES)
return WHY("Manifest variables too long in total to fit in MAX_MANIFEST_BYTES");
snprintf((char *)&m->manifestdata[ofs],MAX_MANIFEST_BYTES-ofs,"%s=%s\n",
m->vars[i],m->values[i]);
ofs+=strlen((char *)&m->manifestdata[ofs]);
}
m->manifestdata[ofs++]=0x00;
m->manifest_bytes=ofs;
if (debug&DEBUG_RHIZOME) DEBUG("Repacked variables in manifest.");
m->manifest_all_bytes=ofs;
/* Recalculate hash */
crypto_hash_sha512(m->manifesthash,m->manifestdata,m->manifest_bytes);
return 0;
}
/* Sign this manifest using our own private CryptoSign key.
We may have multiple identities at any given point, so tell
which one we should be using. */
int rhizome_manifest_sign(rhizome_manifest *m,const char *author)
{
rhizome_signature *sig=rhizome_sign_hash(m,author);
if (!sig) return WHY("rhizome_sign_hash() failed.");
/* Append signature to end of manifest data */
if (sig->signatureLength+m->manifest_bytes>MAX_MANIFEST_BYTES) {
free(sig);
return WHY("Manifest plus signatures is too long.");
}
bcopy(&sig->signature[0],&m->manifestdata[m->manifest_bytes],sig->signatureLength);
m->manifest_bytes+=sig->signatureLength;
m->manifest_all_bytes=m->manifest_bytes;
free(sig);
return 0;
}
int rhizome_write_manifest_file(rhizome_manifest *m, const char *filename)
{
if (!m) return WHY("Manifest is null.");
if (!m->finalised) return WHY("Manifest must be finalised before it can be written.");
FILE *f=fopen(filename,"w");
int r=fwrite(m->manifestdata,m->manifest_all_bytes,1,f);
fclose(f);
if (r!=1) return WHY("Failed to fwrite() manifest file.");
return 0;
}
/*
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,char *msg)
{
int i;
fprintf(stderr,"Dumping manifest %s:\n",msg);
for(i=0;i<m->var_count;i++)
fprintf(stderr,"[%s]=[%s]\n",m->vars[i],m->values[i]);
return 0;
}
int rhizome_manifest_finalise(rhizome_manifest *m,int signP,const char *author)
{
/* set fileHexHash */
if (!m->fileHashedP) {
if (rhizome_hash_file(m->dataFileName,m->fileHexHash))
return WHY("rhizome_hash_file() failed during finalisation of manifest.");
m->fileHashedP=1;
/* set fileLength */
struct stat stat;
if (lstat(m->dataFileName,&stat)) {
return WHY("Could not stat() associated file");
}
m->fileLength=stat.st_size;
}
/* Set file hash and size information */
rhizome_manifest_set(m,"filehash",m->fileHexHash);
rhizome_manifest_set_ll(m,"filesize",m->fileLength);
/* set fileHighestPriority based on group associations.
XXX - Should probably be set as groups are added */
/* set version of manifest, either from version variable, or using current time */
if (rhizome_manifest_get(m,"version",NULL,0)==NULL)
{
/* No version set */
m->version = gettime_ms();
rhizome_manifest_set_ll(m,"version",m->version);
}
else
m->version = rhizome_manifest_get_ll(m,"version");
/* Convert to final form for signing and writing to disk */
rhizome_manifest_pack_variables(m);
/* Sign it */
if (signP) rhizome_manifest_sign(m,author);
/* mark manifest as finalised */
m->finalised=1;
return 0;
}