/* * Off-the-Record Messaging library * Copyright (C) 2004-2009 Ian Goldberg, Chris Alexander, Willy Lew, * Nikita Borisov * * * This library is free software; you can redistribute it and/or * modify it under the terms of version 2.1 of the GNU Lesser General * Public License as published by the Free Software Foundation. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* system headers */ #include #include #include #include /* libgcrypt headers */ #include /* libotr headers */ #include "privkey.h" #include "serial.h" /* Convert a 20-byte hash value to a 45-byte human-readable value */ void otrl_privkey_hash_to_human(char human[45], const unsigned char hash[20]) { int word, byte; char *p = human; for(word=0; word<5; ++word) { for(byte=0; byte<4; ++byte) { sprintf(p, "%02X", hash[word*4+byte]); p += 2; } *(p++) = ' '; } /* Change that last ' ' to a '\0' */ --p; *p = '\0'; } /* Calculate a human-readable hash of our DSA public key. Return it in * the passed fingerprint buffer. Return NULL on error, or a pointer to * the given buffer on success. */ char *otrl_privkey_fingerprint(OtrlUserState us, char fingerprint[45], const char *accountname, const char *protocol) { unsigned char hash[20]; OtrlPrivKey *p = otrl_privkey_find(us, accountname, protocol); if (p) { /* Calculate the hash */ gcry_md_hash_buffer(GCRY_MD_SHA1, hash, p->pubkey_data, p->pubkey_datalen); /* Now convert it to a human-readable format */ otrl_privkey_hash_to_human(fingerprint, hash); } else { return NULL; } return fingerprint; } /* Calculate a raw hash of our DSA public key. Return it in the passed * fingerprint buffer. Return NULL on error, or a pointer to the given * buffer on success. */ unsigned char *otrl_privkey_fingerprint_raw(OtrlUserState us, unsigned char hash[20], const char *accountname, const char *protocol) { OtrlPrivKey *p = otrl_privkey_find(us, accountname, protocol); if (p) { /* Calculate the hash */ gcry_md_hash_buffer(GCRY_MD_SHA1, hash, p->pubkey_data, p->pubkey_datalen); } else { return NULL; } return hash; } /* Create a public key block from a private key */ static gcry_error_t make_pubkey(unsigned char **pubbufp, size_t *publenp, gcry_sexp_t privkey) { gcry_mpi_t p,q,g,y; gcry_sexp_t dsas,ps,qs,gs,ys; size_t np,nq,ng,ny; enum gcry_mpi_format format = GCRYMPI_FMT_USG; unsigned char *bufp; size_t lenp; *pubbufp = NULL; *publenp = 0; /* Extract the public parameters */ dsas = gcry_sexp_find_token(privkey, "dsa", 0); if (dsas == NULL) { return gcry_error(GPG_ERR_UNUSABLE_SECKEY); } ps = gcry_sexp_find_token(dsas, "p", 0); qs = gcry_sexp_find_token(dsas, "q", 0); gs = gcry_sexp_find_token(dsas, "g", 0); ys = gcry_sexp_find_token(dsas, "y", 0); gcry_sexp_release(dsas); if (!ps || !qs || !gs || !ys) { gcry_sexp_release(ps); gcry_sexp_release(qs); gcry_sexp_release(gs); gcry_sexp_release(ys); return gcry_error(GPG_ERR_UNUSABLE_SECKEY); } p = gcry_sexp_nth_mpi(ps, 1, GCRYMPI_FMT_USG); gcry_sexp_release(ps); q = gcry_sexp_nth_mpi(qs, 1, GCRYMPI_FMT_USG); gcry_sexp_release(qs); g = gcry_sexp_nth_mpi(gs, 1, GCRYMPI_FMT_USG); gcry_sexp_release(gs); y = gcry_sexp_nth_mpi(ys, 1, GCRYMPI_FMT_USG); gcry_sexp_release(ys); if (!p || !q || !g || !y) { gcry_mpi_release(p); gcry_mpi_release(q); gcry_mpi_release(g); gcry_mpi_release(y); return gcry_error(GPG_ERR_UNUSABLE_SECKEY); } *publenp = 0; gcry_mpi_print(format, NULL, 0, &np, p); *publenp += np + 4; gcry_mpi_print(format, NULL, 0, &nq, q); *publenp += nq + 4; gcry_mpi_print(format, NULL, 0, &ng, g); *publenp += ng + 4; gcry_mpi_print(format, NULL, 0, &ny, y); *publenp += ny + 4; *pubbufp = malloc(*publenp); if (*pubbufp == NULL) { gcry_mpi_release(p); gcry_mpi_release(q); gcry_mpi_release(g); gcry_mpi_release(y); return gcry_error(GPG_ERR_ENOMEM); } bufp = *pubbufp; lenp = *publenp; write_mpi(p,np,"P"); write_mpi(q,nq,"Q"); write_mpi(g,ng,"G"); write_mpi(y,ny,"Y"); gcry_mpi_release(p); gcry_mpi_release(q); gcry_mpi_release(g); gcry_mpi_release(y); return gcry_error(GPG_ERR_NO_ERROR); } /* Read a sets of private DSA keys from a file on disk into the given * OtrlUserState. */ gcry_error_t otrl_privkey_read(OtrlUserState us, const char *filename) { FILE *privf; gcry_error_t err; /* Open the privkey file. We use rb mode so that on WIN32, fread() * reads the same number of bytes that fstat() indicates are in the * file. */ privf = fopen(filename, "rb"); if (!privf) { err = gcry_error_from_errno(errno); return err; } err = otrl_privkey_read_FILEp(us, privf); fclose(privf); return err; } /* Read a sets of private DSA keys from a FILE* into the given * OtrlUserState. The FILE* must be open for reading. */ gcry_error_t otrl_privkey_read_FILEp(OtrlUserState us, FILE *privf) { int privfd; struct stat st; char *buf; const char *token; size_t tokenlen; gcry_error_t err; gcry_sexp_t allkeys; size_t i; if (!privf) return gcry_error(GPG_ERR_NO_ERROR); /* Release any old ideas we had about our keys */ otrl_privkey_forget_all(us); /* Load the data into a buffer */ privfd = fileno(privf); if (fstat(privfd, &st)) { err = gcry_error_from_errno(errno); return err; } buf = malloc(st.st_size); if (!buf && st.st_size > 0) { return gcry_error(GPG_ERR_ENOMEM); } if (fread(buf, st.st_size, 1, privf) != 1) { err = gcry_error_from_errno(errno); free(buf); return err; } err = gcry_sexp_new(&allkeys, buf, st.st_size, 0); free(buf); if (err) { return err; } token = gcry_sexp_nth_data(allkeys, 0, &tokenlen); if (tokenlen != 8 || strncmp(token, "privkeys", 8)) { gcry_sexp_release(allkeys); return gcry_error(GPG_ERR_UNUSABLE_SECKEY); } /* Get each account */ for(i=1; iaccountname = name; p->protocol = proto; p->pubkey_type = OTRL_PUBKEY_TYPE_DSA; p->privkey = privs; p->next = us->privkey_root; if (p->next) { p->next->tous = &(p->next); } p->tous = &(us->privkey_root); us->privkey_root = p; err = make_pubkey(&(p->pubkey_data), &(p->pubkey_datalen), p->privkey); if (err) { gcry_sexp_release(allkeys); otrl_privkey_forget(p); return gcry_error(GPG_ERR_UNUSABLE_SECKEY); } } gcry_sexp_release(allkeys); return gcry_error(GPG_ERR_NO_ERROR); } static OtrlPendingPrivKey *pending_find(OtrlUserState us, const char *accountname, const char *protocol) { OtrlPendingPrivKey *search = us->pending_root; while (search) { if (!strcmp(search->accountname, accountname) && !strcmp(search->protocol, protocol)) { /* Found it */ return search; } search = search->next; } return NULL; } /* Insert an account/protocol pair into the pending privkey list of the * given OtrlUserState and return a pointer to the new * OtrlPendingPrivKey, or return NULL if it's already there. */ static OtrlPendingPrivKey *pending_insert(OtrlUserState us, const char *accountname, const char *protocol) { /* See if it's already there */ OtrlPendingPrivKey *search = pending_find(us, accountname, protocol); if (search) { /* It is */ return NULL; } /* We'll insert it at the beginning of the list */ search = malloc(sizeof(*search)); if (!search) return NULL; search->accountname = strdup(accountname); search->protocol = strdup(protocol); search->next = us->pending_root; us->pending_root = search; if (search->next) { search->next->tous = &(search->next); } search->tous = &(us->pending_root); return search; } static void pending_forget(OtrlPendingPrivKey *ppk) { if (ppk) { free(ppk->accountname); free(ppk->protocol); /* Re-link the list */ *(ppk->tous) = ppk->next; if (ppk->next) { ppk->next->tous = ppk->tous; } free(ppk); } } /* Free the memory associated with the pending privkey list */ void otrl_privkey_pending_forget_all(OtrlUserState us) { while(us->pending_root) { pending_forget(us->pending_root); } } static gcry_error_t sexp_write(FILE *privf, gcry_sexp_t sexp) { size_t buflen; char *buf; buflen = gcry_sexp_sprint(sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0); buf = malloc(buflen); if (buf == NULL && buflen > 0) { return gcry_error(GPG_ERR_ENOMEM); } gcry_sexp_sprint(sexp, GCRYSEXP_FMT_ADVANCED, buf, buflen); fprintf(privf, "%s", buf); free(buf); return gcry_error(GPG_ERR_NO_ERROR); } static gcry_error_t account_write(FILE *privf, const char *accountname, const char *protocol, gcry_sexp_t privkey) { gcry_error_t err; gcry_sexp_t names, protos; fprintf(privf, " (account\n"); err = gcry_sexp_build(&names, NULL, "(name %s)", accountname); if (!err) { err = sexp_write(privf, names); gcry_sexp_release(names); } if (!err) err = gcry_sexp_build(&protos, NULL, "(protocol %s)", protocol); if (!err) { err = sexp_write(privf, protos); gcry_sexp_release(protos); } if (!err) err = sexp_write(privf, privkey); fprintf(privf, " )\n"); return err; } struct s_pending_privkey_calc { char *accountname; char *protocol; gcry_sexp_t privkey; }; /* Begin a private key generation that will potentially take place in * a background thread. This routine must be called from the main * thread. It will set *newkeyp, which you can pass to * otrl_privkey_generate_calculate in a background thread. If it * returns gcry_error(GPG_ERR_EEXIST), then a privkey creation for * this accountname/protocol is already in progress, and *newkeyp will * be set to NULL. */ gcry_error_t otrl_privkey_generate_start(OtrlUserState us, const char *accountname, const char *protocol, void **newkeyp) { OtrlPendingPrivKey *found = pending_find(us, accountname, protocol); struct s_pending_privkey_calc *ppc; if (found) { if (newkeyp) *newkeyp = NULL; return gcry_error(GPG_ERR_EEXIST); } /* We're not already creating this key. Mark it as in progress. */ pending_insert(us, accountname, protocol); /* Allocate the working structure */ ppc = malloc(sizeof(*ppc)); ppc->accountname = strdup(accountname); ppc->protocol = strdup(protocol); ppc->privkey = NULL; *newkeyp = ppc; return gcry_error(GPG_ERR_NO_ERROR); } /* Do the private key generation calculation. You may call this from a * background thread. When it completes, call * otrl_privkey_generate_finish from the _main_ thread. */ gcry_error_t otrl_privkey_generate_calculate(void *newkey) { struct s_pending_privkey_calc *ppc = (struct s_pending_privkey_calc *)newkey; gcry_error_t err; gcry_sexp_t key, parms; static const char *parmstr = "(genkey (dsa (nbits 4:1024)))"; /* Create a DSA key */ err = gcry_sexp_new(&parms, parmstr, strlen(parmstr), 0); if (err) { return err; } err = gcry_pk_genkey(&key, parms); gcry_sexp_release(parms); if (err) { return err; } /* Extract the privkey */ ppc->privkey = gcry_sexp_find_token(key, "private-key", 0); gcry_sexp_release(key); return gcry_error(GPG_ERR_NO_ERROR); } static FILE* privkey_fopen(const char *filename, gcry_error_t *errp) { FILE *privf; #ifndef WIN32 mode_t oldmask; #endif #ifndef WIN32 oldmask = umask(077); #endif privf = fopen(filename, "w+b"); if (!privf && errp) { *errp = gcry_error_from_errno(errno); } #ifndef WIN32 umask(oldmask); #endif return privf; } /* Call this from the main thread only, in the event that the background * thread generating the key is cancelled. The newkey is deallocated, * and must not be used further. */ void otrl_privkey_generate_cancel(OtrlUserState us, void *newkey) { struct s_pending_privkey_calc *ppc = (struct s_pending_privkey_calc *)newkey; if (us) { pending_forget(pending_find(us, ppc->accountname, ppc->protocol)); } /* Deallocate ppc */ free(ppc->accountname); free(ppc->protocol); gcry_sexp_release(ppc->privkey); free(ppc); } /* Call this from the main thread only. It will write the newly created * private key into the given file and store it in the OtrlUserState. */ gcry_error_t otrl_privkey_generate_finish(OtrlUserState us, void *newkey, const char *filename) { gcry_error_t err; FILE *privf = privkey_fopen(filename, &err); if (!privf) { return err; } err = otrl_privkey_generate_finish_FILEp(us, newkey, privf); fclose(privf); return err; } /* Call this from the main thread only. It will write the newly created * private key into the given FILE* (which must be open for reading and * writing) and store it in the OtrlUserState. */ gcry_error_t otrl_privkey_generate_finish_FILEp(OtrlUserState us, void *newkey, FILE *privf) { struct s_pending_privkey_calc *ppc = (struct s_pending_privkey_calc *)newkey; gcry_error_t ret = gcry_error(GPG_ERR_INV_VALUE); if (ppc && us && privf) { OtrlPrivKey *p; /* Output the other keys we know */ fprintf(privf, "(privkeys\n"); for (p=us->privkey_root; p; p=p->next) { /* Skip this one if our new key replaces it */ if (!strcmp(p->accountname, ppc->accountname) && !strcmp(p->protocol, ppc->protocol)) { continue; } account_write(privf, p->accountname, p->protocol, p->privkey); } account_write(privf, ppc->accountname, ppc->protocol, ppc->privkey); fprintf(privf, ")\n"); fseek(privf, 0, SEEK_SET); ret = otrl_privkey_read_FILEp(us, privf); } otrl_privkey_generate_cancel(us, newkey); return ret; } /* Generate a private DSA key for a given account, storing it into a * file on disk, and loading it into the given OtrlUserState. Overwrite any * previously generated keys for that account in that OtrlUserState. */ gcry_error_t otrl_privkey_generate(OtrlUserState us, const char *filename, const char *accountname, const char *protocol) { gcry_error_t err; FILE *privf = privkey_fopen(filename, &err); if (!privf) { return err; } err = otrl_privkey_generate_FILEp(us, privf, accountname, protocol); fclose(privf); return err; } /* Generate a private DSA key for a given account, storing it into a * FILE*, and loading it into the given OtrlUserState. Overwrite any * previously generated keys for that account in that OtrlUserState. * The FILE* must be open for reading and writing. */ gcry_error_t otrl_privkey_generate_FILEp(OtrlUserState us, FILE *privf, const char *accountname, const char *protocol) { void *newkey = NULL; gcry_error_t err; err = otrl_privkey_generate_start(us, accountname, protocol, &newkey); if (newkey) { otrl_privkey_generate_calculate(newkey); err = otrl_privkey_generate_finish_FILEp(us, newkey, privf); } return err; } /* Convert a hex character to a value */ static unsigned int ctoh(char c) { if (c >= '0' && c <= '9') return c-'0'; if (c >= 'a' && c <= 'f') return c-'a'+10; if (c >= 'A' && c <= 'F') return c-'A'+10; return 0; /* Unknown hex char */ } /* Read the fingerprint store from a file on disk into the given * OtrlUserState. Use add_app_data to add application data to each * ConnContext so created. */ gcry_error_t otrl_privkey_read_fingerprints(OtrlUserState us, const char *filename, void (*add_app_data)(void *data, ConnContext *context), void *data) { gcry_error_t err; FILE *storef; storef = fopen(filename, "rb"); if (!storef) { err = gcry_error_from_errno(errno); return err; } err = otrl_privkey_read_fingerprints_FILEp(us, storef, add_app_data, data); fclose(storef); return err; } /* Read the fingerprint store from a FILE* into the given * OtrlUserState. Use add_app_data to add application data to each * ConnContext so created. The FILE* must be open for reading. */ gcry_error_t otrl_privkey_read_fingerprints_FILEp(OtrlUserState us, FILE *storef, void (*add_app_data)(void *data, ConnContext *context), void *data) { ConnContext *context; char storeline[1000]; unsigned char fingerprint[20]; size_t maxsize = sizeof(storeline); if (!storef) return gcry_error(GPG_ERR_NO_ERROR); while(fgets(storeline, maxsize, storef)) { char *username; char *accountname; char *protocol; char *hex; char *trust; char *tab; char *eol; Fingerprint *fng; int i, j; /* Parse the line, which should be of the form: * username\taccountname\tprotocol\t40_hex_nybbles\n */ username = storeline; tab = strchr(username, '\t'); if (!tab) continue; *tab = '\0'; accountname = tab + 1; tab = strchr(accountname, '\t'); if (!tab) continue; *tab = '\0'; protocol = tab + 1; tab = strchr(protocol, '\t'); if (!tab) continue; *tab = '\0'; hex = tab + 1; tab = strchr(hex, '\t'); if (!tab) { eol = strchr(hex, '\r'); if (!eol) eol = strchr(hex, '\n'); if (!eol) continue; *eol = '\0'; trust = NULL; } else { *tab = '\0'; trust = tab + 1; eol = strchr(trust, '\r'); if (!eol) eol = strchr(trust, '\n'); if (!eol) continue; *eol = '\0'; } if (strlen(hex) != 40) continue; for(j=0, i=0; i<40; i+=2) { fingerprint[j++] = (ctoh(hex[i]) << 4) + (ctoh(hex[i+1])); } /* Get the context for this user, adding if not yet present */ context = otrl_context_find(us, username, accountname, protocol, 0, 1, NULL, add_app_data, data); /* Add the fingerprint if not already there */ fng = otrl_context_find_fingerprint(context, fingerprint, 1, NULL); otrl_context_set_trust(fng, trust); } return gcry_error(GPG_ERR_NO_ERROR); } /* Write the fingerprint store from a given OtrlUserState to a file on disk. */ gcry_error_t otrl_privkey_write_fingerprints(OtrlUserState us, const char *filename) { gcry_error_t err; FILE *storef; storef = fopen(filename, "wb"); if (!storef) { err = gcry_error_from_errno(errno); return err; } err = otrl_privkey_write_fingerprints_FILEp(us, storef); fclose(storef); return err; } /* Write the fingerprint store from a given OtrlUserState to a FILE*. * The FILE* must be open for writing. */ gcry_error_t otrl_privkey_write_fingerprints_FILEp(OtrlUserState us, FILE *storef) { ConnContext *context; Fingerprint *fprint; if (!storef) return gcry_error(GPG_ERR_NO_ERROR); for(context = us->context_root; context; context = context->next) { /* Don't both with the first (fingerprintless) entry. */ for (fprint = context->fingerprint_root.next; fprint; fprint = fprint->next) { int i; fprintf(storef, "%s\t%s\t%s\t", context->username, context->accountname, context->protocol); for(i=0;i<20;++i) { fprintf(storef, "%02x", fprint->fingerprint[i]); } fprintf(storef, "\t%s\n", fprint->trust ? fprint->trust : ""); } } return gcry_error(GPG_ERR_NO_ERROR); } /* Fetch the private key from the given OtrlUserState associated with * the given account */ OtrlPrivKey *otrl_privkey_find(OtrlUserState us, const char *accountname, const char *protocol) { OtrlPrivKey *p; if (!accountname || !protocol) return NULL; for(p=us->privkey_root; p; p=p->next) { if (!strcmp(p->accountname, accountname) && !strcmp(p->protocol, protocol)) { return p; } } return NULL; } /* Forget a private key */ void otrl_privkey_forget(OtrlPrivKey *privkey) { free(privkey->accountname); free(privkey->protocol); gcry_sexp_release(privkey->privkey); free(privkey->pubkey_data); /* Re-link the list */ *(privkey->tous) = privkey->next; if (privkey->next) { privkey->next->tous = privkey->tous; } /* Free the privkey struct */ free(privkey); } /* Forget all private keys in a given OtrlUserState. */ void otrl_privkey_forget_all(OtrlUserState us) { while (us->privkey_root) { otrl_privkey_forget(us->privkey_root); } } /* Sign data using a private key. The data must be small enough to be * signed (i.e. already hashed, if necessary). The signature will be * returned in *sigp, which the caller must free(). Its length will be * returned in *siglenp. */ gcry_error_t otrl_privkey_sign(unsigned char **sigp, size_t *siglenp, OtrlPrivKey *privkey, const unsigned char *data, size_t len) { gcry_mpi_t r,s, datampi; gcry_sexp_t dsas, rs, ss, sigs, datas; size_t nr, ns; const enum gcry_mpi_format format = GCRYMPI_FMT_USG; if (privkey->pubkey_type != OTRL_PUBKEY_TYPE_DSA) return gcry_error(GPG_ERR_INV_VALUE); *sigp = malloc(40); if (sigp == NULL) return gcry_error(GPG_ERR_ENOMEM); *siglenp = 40; if (len) { gcry_mpi_scan(&datampi, GCRYMPI_FMT_USG, data, len, NULL); } else { datampi = gcry_mpi_set_ui(NULL, 0); } gcry_sexp_build(&datas, NULL, "(%m)", datampi); gcry_mpi_release(datampi); gcry_pk_sign(&sigs, datas, privkey->privkey); gcry_sexp_release(datas); dsas = gcry_sexp_find_token(sigs, "dsa", 0); gcry_sexp_release(sigs); rs = gcry_sexp_find_token(dsas, "r", 0); ss = gcry_sexp_find_token(dsas, "s", 0); gcry_sexp_release(dsas); r = gcry_sexp_nth_mpi(rs, 1, GCRYMPI_FMT_USG); gcry_sexp_release(rs); s = gcry_sexp_nth_mpi(ss, 1, GCRYMPI_FMT_USG); gcry_sexp_release(ss); gcry_mpi_print(format, NULL, 0, &nr, r); gcry_mpi_print(format, NULL, 0, &ns, s); memset(*sigp, 0, 40); gcry_mpi_print(format, (*sigp)+(20-nr), nr, NULL, r); gcry_mpi_print(format, (*sigp)+20+(20-ns), ns, NULL, s); gcry_mpi_release(r); gcry_mpi_release(s); return gcry_error(GPG_ERR_NO_ERROR); } /* Verify a signature on data using a public key. The data must be * small enough to be signed (i.e. already hashed, if necessary). */ gcry_error_t otrl_privkey_verify(const unsigned char *sigbuf, size_t siglen, unsigned short pubkey_type, gcry_sexp_t pubs, const unsigned char *data, size_t len) { gcry_error_t err; gcry_mpi_t datampi,r,s; gcry_sexp_t datas, sigs; if (pubkey_type != OTRL_PUBKEY_TYPE_DSA || siglen != 40) return gcry_error(GPG_ERR_INV_VALUE); if (len) { gcry_mpi_scan(&datampi, GCRYMPI_FMT_USG, data, len, NULL); } else { datampi = gcry_mpi_set_ui(NULL, 0); } gcry_sexp_build(&datas, NULL, "(%m)", datampi); gcry_mpi_release(datampi); gcry_mpi_scan(&r, GCRYMPI_FMT_USG, sigbuf, 20, NULL); gcry_mpi_scan(&s, GCRYMPI_FMT_USG, sigbuf+20, 20, NULL); gcry_sexp_build(&sigs, NULL, "(sig-val (dsa (r %m)(s %m)))", r, s); gcry_mpi_release(r); gcry_mpi_release(s); err = gcry_pk_verify(sigs, datas, pubs); gcry_sexp_release(datas); gcry_sexp_release(sigs); return err; } /* Fetch the instance tag from the given OtrlUserState associated with * the given account */ OtrlInsTag * otrl_instag_find(OtrlUserState us, const char *accountname, const char *protocol) { OtrlInsTag *p; if (!accountname || !protocol) return NULL; for(p=us->instag_root; p; p=p->next) { if (!strcmp(p->accountname, accountname) && !strcmp(p->protocol, protocol)) { return p; } } return NULL; } /* Read our instance tag from a file on disk into the given * OtrlUserState. */ gcry_error_t otrl_instag_read(OtrlUserState us, const char *filename) { gcry_error_t err; FILE *instf; /* Open the instance tag file. */ instf = fopen(filename, "rb"); if (!instf) { return gcry_error_from_errno(errno); } err = otrl_instag_read_FILEp(us, instf); fclose(instf); return err; } /* Read our instance tag from a file on disk into the given * OtrlUserState. The FILE* must be open for reading. */ gcry_error_t otrl_instag_read_FILEp(OtrlUserState us, FILE *instf) { if (!instf) return gcry_error(GPG_ERR_NO_ERROR); OtrlInsTag *p; char storeline[1000]; size_t maxsize = sizeof(storeline); while(fgets(storeline, maxsize, instf)) { char *prevpos; char *pos; unsigned int instag = 0; int i; p = malloc(sizeof(*p)); if (!p) { return gcry_error(GPG_ERR_ENOMEM); } /* Parse the line, which should be of the form: * accountname\tprotocol\t40_hex_nybbles\n */ prevpos = storeline; pos = strchr(prevpos, '\t'); if (!pos) { free(p); continue; } *pos = '\0'; pos++; p->accountname = malloc(pos - prevpos); memmove(p->accountname, prevpos, pos - prevpos); prevpos = pos; pos = strchr(prevpos, '\t'); if (!pos) { free(p); continue; } *pos = '\0'; pos++; p->protocol = malloc(pos - prevpos); memmove(p->protocol, prevpos, pos - prevpos); prevpos = pos; pos = strchr(prevpos, '\r'); if (!pos) pos = strchr(prevpos, '\n'); if (!pos) { free(p); continue; } *pos = '\0'; pos++; // hex str of length 8 if (strlen(prevpos) != 8) { free(p); continue; } for(i=0; i<8; i+=1) { instag = (instag << 4) + ctoh(prevpos[i]); } p->instag = instag; /* Link it up */ p->next = us->instag_root; if (p->next) { p->next->tous = &(p->next); } p->tous = &(us->instag_root); us->instag_root = p; } return gcry_error(GPG_ERR_NO_ERROR); } /* Generate a new instance tag for the given account and write to file */ gcry_error_t otrl_instag_generate(OtrlUserState us, const char *filename, const char *accountname, const char *protocol) { gcry_error_t err; FILE *instf; /* Open the instance tag file. */ instf = fopen(filename, "wb"); if (!instf) { return gcry_error_from_errno(errno); } err = otrl_instag_generate_FILEp(us, instf, accountname, protocol); fclose(instf); return err; } /* Generate a new instance tag for the given account and write to file * The FILE* must be open for writing. */ gcry_error_t otrl_instag_generate_FILEp(OtrlUserState us, FILE *instf, const char *accountname, const char *protocol) { OtrlInsTag *p; if (!accountname || !protocol) return gcry_error(GPG_ERR_NO_ERROR); p = (OtrlInsTag *)malloc(sizeof(OtrlInsTag)); p->accountname = malloc(sizeof(accountname)); p->protocol = malloc(sizeof(protocol)); strcpy(p->accountname, accountname); strcpy(p->protocol, protocol); p->instag = *((unsigned int *)gcry_random_bytes(4, GCRY_STRONG_RANDOM)); // Add to our list in OtrlUserState p->next = us->instag_root; if (p->next) { p->next->tous = &(p->next); } p->tous = &(us->instag_root); us->instag_root = p; otrl_instag_write_FILEp(us, instf); return gcry_error(GPG_ERR_NO_ERROR); } /* Write our instance tags to a file on disk. */ gcry_error_t otrl_instag_write(OtrlUserState us, const char *filename) { gcry_error_t err; FILE *instf; /* Open the instance tag file. */ instf = fopen(filename, "wb"); if (!instf) { return gcry_error_from_errno(errno); } err = otrl_instag_write_FILEp(us, instf); fclose(instf); return err; } /* Write our instance tags to a file on disk. * The FILE* must be open for writing. */ gcry_error_t otrl_instag_write_FILEp(OtrlUserState us, FILE *instf) { OtrlInsTag *p; for(p=us->instag_root; p; p=p->next) { fprintf(instf, "%s\t%s\t%08x\n", p->accountname, p->protocol, p->instag); } return gcry_error(GPG_ERR_NO_ERROR); }