/* 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 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((ofsmanifest_bytes)&&(m->manifestdata[ofs])) { int i; char line[1024],var[1024],value[1024]; while((ofsmanifest_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_countvars[m->var_count]=strdup(var); m->values[m->var_count]=strdup(value); m->var_count++; } } else { char buf[80]; if (0) WARNF("Skipping malformed line in manifest file %s: %s", bufferP ? "" : filename, catv(line, buf, sizeof buf)); } } /* The null byte gets included in the check sum */ if (ofsmanifest_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(ofsmanifest_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 != RHIZOME_FILEHASH_STRLEN; ++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 == RHIZOME_FILEHASH_STRLEN && 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); str_toupper_inplace(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;ivar_count;i++) if (!strcmp(m->vars[i],var)) { if (out) { for(j=0;(jvalues[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;ivar_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;ivar_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); } 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) { 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;imanifest_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]=""; manifest_free_functions[manifest_first_free]=""; manifest_free_lines[manifest_first_free]=-1; /* Work out where next free manifest record lives */ for(;manifest_first_freemanifest_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;ivar_count;i++) { free(m->vars[i]); free(m->values[i]); m->vars[i]=NULL; m->values[i]=NULL; } for(i=0;isig_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 (midvar_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 (debug & DEBUG_RHIZOME) DEBUGF("write manifest (%d bytes) to %s", m->manifest_all_bytes, 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"); if (f == NULL) { WHY_perror("fopen"); return WHYF("Cannot write manifest to %s", filename); } int r1 = fwrite(m->manifestdata, m->manifest_all_bytes, 1, f); int r2 = fclose(f); if (r1 != 1) return WHYF("fwrite(%s) returned %d", filename, r1); if (r2 == EOF) return WHYF("fclose(%s) returned %d", filename, r2); 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;ivar_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; }