/* This file is in the public domain. */ #include #include #include #include #include #include #include #include extern const char *__progname; #include "b64.h" #include "errf.h" #include "panic.h" #include "client.h" #include "config.h" #include "nested.h" #include "keyfiles.h" #include "interact.h" #include "stdio-util.h" #include "hkdb.h" typedef struct keylist KEYLIST; typedef struct bloblist BLOBLIST; struct keylist { KEYLIST *link; char *key; } ; struct bloblist { BLOBLIST *link; HKALG *alg; void *data; int len; } ; static const char *hostkeydb(void) { static char *def = 0; const char *p; p = config_str("host-key-db"); if (p) return(p); free(def); asprintf(&def,"%s/known-hosts",get_sshdir("host key database")); return(def); } typedef enum { SCAN_DONE = 1, SCAN_SKIP, SCAN_READ, SCAN_CONT } SCAN_OP; static void scan_hkdb( FILE *f, SCAN_OP (*key)(const char *), SCAN_OP (*val)(HKALG *, const void *, int)) { void *b64; int c; char *algname; HKALG *alg; char *buf; int len; int wantvals; FILE *g; wantvals = 0; while <"lines"> (1) { do <"flushline"> { c = getc(f); switch (c) { case EOF: break <"lines">; case '#': case ';': default: break <"flushline">; case 'K': g = fopen_alloc(&buf,0); while (1) { c = getc(f); if ((c == '\n') || (c == EOF)) break; putc(c,g); } putc('\0',g); fclose(g); switch ((*key)(buf)) { default: panic("bad status"); break; case SCAN_DONE: free(buf); break <"lines">; case SCAN_SKIP: wantvals = 0; break; case SCAN_READ: wantvals = 1; break; } free(buf); break; case 'V': if (! wantvals) break <"flushline">; g = fopen_alloc(&algname,0); while <"algname"> (1) { c = getc(f); switch (c) { case EOF: fclose(g); free(algname); break <"lines">; case ' ': break <"algname">; case '\n': fclose(g); free(algname); break <"flushline">; } putc(c,g); } putc('\0',g); fclose(g); alg = (*at_hk.find_c)(algname); free(algname); if (alg == 0) break <"flushline">; b64 = b64r_init(&b64_stdio_get,f); g = fopen_alloc(&buf,&len); while <"b64"> (1) { c = b64r_get(b64); switch (c) { case B64R_EOF: break <"b64">; case B64R_ERR: b64r_done(b64); fclose(g); free(buf); break <"flushline">; } putc(c,g); } fclose(g); if (! (*alg->checkpub)(buf,len)) { free(buf); break <"flushline">; } c = getc(f); if (c != '\n') { free(buf); break <"flushline">; } switch ((*val)(alg,buf,len)) { default: panic("bad status"); break; case SCAN_DONE: free(buf); break <"lines">; case SCAN_SKIP: wantvals = 0; break; case SCAN_CONT: break; } free(buf); break; } continue <"lines">; } while (0); do c = getc(f); while ((c != '\n') && (c != EOF)); } } typedef enum { MOD_K_COPY = 1, MOD_K_DROP, MOD_K_CALL } MOD_K_OP; static void mod_hkdb( const char *dbf, MOD_K_OP (*key)( const char *, void (*)(HKALG *, const void *, int) ), void (*val)( HKALG *, const void *, int, void (*)(HKALG *, const void *, int) ), void (*final)( void (*)(const char *, HKALG *, const void *, int) ) ) { int dbfd; FILE *i; FILE *o; char *obuf; int olen; void *b64; int c; char *algname; HKALG *alg; char *buf; int len; char *hbuf; int needhbprint; FILE *g; MOD_K_OP kop; NESTED void printh(void) { if (needhbprint) { fprintf(o,"K%s\n",hbuf); needhbprint = 0; } } NESTED void save(HKALG *alg, const void *data, int len) { printh(); fprintf(o,"V%s ",alg->name); b64_write_blob(o,data,len); fprintf(o,"\n"); } NESTED void saven(const char *hn, HKALG *alg, const void *data, int len) { fprintf(o,"K%s\nV%s ",hn,alg->name); b64_write_blob(o,data,len); fprintf(o,"\n"); } dbfd = open(dbf,O_RDWR|O_EXLOCK|O_CREAT,0666); if (dbfd < 0) { fprintf(errf,"%s: %s: %s\n",__progname,dbf,strerror(errno)); fflush(errf); return; } i = fdopen(dbfd,"r+"); o = fopen_alloc(&obuf,&olen); hbuf = 0; needhbprint = 0; kop = MOD_K_DROP; while <"lines"> (1) { do <"echoline"> { do <"skipline"> { c = getc(i); switch (c) { default: break <"skipline">; case EOF: break <"lines">; case '#': case ';': putc('#',o); break <"echoline">; case 'K': free(hbuf); g = fopen_alloc(&hbuf,0); while (1) { c = getc(i); if (c == EOF) { fclose(g); break <"lines">; } if (c == '\n') break; putc(c,g); } putc('\0',g); fclose(g); needhbprint = 1; kop = (*key)(hbuf,&save); switch (kop) { default: panic("impossible mod_hkdb key op %d\n",(int)kop); break; case MOD_K_COPY: case MOD_K_DROP: case MOD_K_CALL: break; } break; case 'V': switch (kop) { default: panic("impossible mod_hkdb key op %d\n",(int)kop); break; case MOD_K_COPY: printh(); putc('V',o); break <"echoline">; break; case MOD_K_DROP: break <"skipline">; break; case MOD_K_CALL: break; } g = fopen_alloc(&algname,0); while <"algname"> (1) { c = getc(i); switch (c) { case EOF: fclose(g); free(algname); break <"lines">; case ' ': break <"algname">; case '\n': fclose(g); free(algname); continue <"lines">; break; } putc(c,g); } putc('\0',g); fclose(g); alg = (*at_hk.find_c)(algname); if (alg == 0) { fprintf(o,"V%s ",algname); free(algname); break <"echoline">; } free(algname); b64 = b64r_init(&b64_stdio_get,i); g = fopen_alloc(&buf,&len); while <"b64"> (1) { c = b64r_get(b64); switch (c) { case B64R_EOF: break <"b64">; case B64R_ERR: b64r_done(b64); fclose(g); free(buf); break <"skipline">; } putc(c,g); } fclose(g); if (! (*alg->checkpub)(buf,len)) { free(buf); break <"skipline">; } c = getc(i); if (c != '\n') break <"skipline">; (*val)(alg,buf,len,&save); free(buf); break; } continue <"lines">; } while (0); do c = getc(i); while ((c != '\n') && (c != EOF)); continue <"lines">; } while (0); while (1) { c = getc(i); if (c == EOF) break; putc(c,o); if (c == '\n') break; } } if (final) (*final)(&saven); fclose(o); free(hbuf); if (lseek(dbfd,0,SEEK_SET) < 0) { fprintf(errf,"%s: error rewinding %s: %s\n", __progname,dbf,strerror(errno)); fclose(i); } else { len = write(dbfd,obuf,olen); if (len < 0) { fprintf(errf,"%s: error rewriting %s [%s], it may be corrupt\n", __progname,strerror(errno),dbf); fclose(i); } else if (len != olen) { fprintf(errf,"%s: error rewriting %s [wanted %d, wrote %d], it may be corrupt\n", __progname,dbf,olen,len); fclose(i); } else if (ftruncate(dbfd,olen) < 0) { fprintf(errf,"%s: error truncating %s [%s], it may be corrupt\n", __progname,dbf,strerror(errno)); fclose(i); } else if (fclose(i) != 0) { fprintf(errf,"%s: error closing %s, it may be corrupt\n", __progname,dbf); } } } static void append_hkdb( const char *dbf, void (*fn)(void (*)(const char *, HKALG *, const void *, int))) { int dbfd; FILE *i; FILE *o; char *obuf; int olen; int len; NESTED void save(const char *key, HKALG *alg, const void *data, int len) { fprintf(o,"K%s\nV%s ",key,alg->name); b64_write_blob(o,data,len); fprintf(o,"\n"); } dbfd = open(dbf,O_RDWR|O_EXLOCK|O_CREAT|O_APPEND,0666); if (dbfd < 0) { fprintf(errf,"%s: %s: %s\n",__progname,dbf,strerror(errno)); fflush(errf); return; } o = fopen_alloc(&obuf,&olen); (*fn)(&save); fclose(o); if (olen > 0) { len = write(dbfd,obuf,olen); if (len < 0) { fprintf(errf,"%s: error appending to %s [%s], it may be corrupt\n", __progname,dbf,strerror(errno)); fclose(i); } else if (len != olen) { fprintf(errf,"%s: error appending to %s [wanted %d, wrote %d], it may be corrupt\n", __progname,dbf,olen,len); fclose(i); } else if (close(dbfd) != 0) { fprintf(errf,"%s: error closing %s [%s], it may be corrupt\n", __progname,dbf,strerror(errno)); } } } void check_host_key(HKALG *hk, const void *pkdata, int pklen) { void *h; unsigned char fp[16]; FILE *f; int found; int typefound; int match; const char *lookupkey; char *keyfree; const char *dbf; KEYLIST *keys; BLOBLIST *blobs; KEYLIST *kl; BLOBLIST *bl; NESTED MOD_K_OP mod_key_copy(const char *k, void (*save)(HKALG *, const void *, int)) { if (strcmp(k,lookupkey)) return(MOD_K_COPY); (*save)(hk,pkdata,pklen); return(MOD_K_COPY); } NESTED MOD_K_OP mod_key_drop(const char *k, void (*save)(HKALG *, const void *, int)) { if (strcmp(k,lookupkey)) return(MOD_K_COPY); (*save)(hk,pkdata,pklen); return(MOD_K_DROP); } NESTED MOD_K_OP mod_key_call(const char *k, void (*save)(HKALG *, const void *, int)) { if (strcmp(k,lookupkey)) return(MOD_K_COPY); (*save)(hk,pkdata,pklen); return(MOD_K_CALL); } NESTED void mod_replace(HKALG *alg, const void *data, int len, void (*save)(HKALG *, const void *, int)) { if (alg != hk) (*save)(alg,data,len); } NESTED void mod_new(void (*save)(const char *, HKALG *, const void *, int)) { (*save)(lookupkey,hk,pkdata,pklen); } NESTED void showkey(void) { fprintf(errf,"Host key: %s %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",hk->name,fp[0],fp[1],fp[2],fp[3],fp[4],fp[5],fp[6],fp[7],fp[8],fp[9],fp[10],fp[11],fp[12],fp[13],fp[14],fp[15]); if (keys) { fprintf(errf,"Other hosts for which this key is known:"); for (kl=keys;kl;kl=kl->link) fprintf(errf," %s",kl->key); fprintf(errf,"\n"); } } if (config_isset("hkdb-key")) { lookupkey = config_str("hkdb-key"); keyfree = 0; } else { const PEERID *id; id = client_peer_id(); f = fopen_alloc(&keyfree,0); print_lower(f,id->name); fprintf(f,"/%d",id->port); putc('\0',f); fclose(f); lookupkey = keyfree; } h = md5_init(); md5_process_bytes(h,pkdata,pklen); md5_result(h,&fp[0]); dbf = hostkeydb(); f = fopen(dbf,"r"); found = 0; typefound = 0; match = 0; keys = 0; blobs = 0; if (f == 0) { fprintf(errf,"%s: can't open host key database %s: %s\n",__progname,dbf,strerror(errno)); } else { char *curkey; int thiskey; NESTED SCAN_OP key(const char *key) { free(curkey); curkey = strdup(key); thiskey = !strcmp(key,lookupkey); if (thiskey) found = 1; return(SCAN_READ); } NESTED SCAN_OP val(HKALG *alg, const void *data, int len) { if ((alg == hk) && thiskey) typefound = 1; if ((alg == hk) && (len == pklen) && !bcmp(data,pkdata,len)) { if (thiskey) { match = 1; return(SCAN_DONE); } else { kl = malloc(sizeof(KEYLIST)); kl->key = strdup(curkey); kl->link = keys; keys = kl; } } else { if (thiskey) { bl = malloc(sizeof(BLOBLIST)); bl->alg = alg; bl->data = malloc(len); bl->len = len; bcopy(data,bl->data,len); bl->link = blobs; blobs = bl; } } return(SCAN_CONT); } curkey = 0; scan_hkdb(f,&key,&val); free(curkey); fclose(f); } /* found tf match 0 0 0 (0) host not present at all 0 0 1 (1) impossible 0 1 0 (2) impossible 0 1 1 (3) impossible 1 0 0 (4) host present, all keys other types 1 0 1 (5) impossible 1 1 0 (6) host present, key of this type but not this key 1 1 1 (7) host present with this key */ do <"ret"> { if (match) { /* 1, 3, 5, 7 */ if (!found || !typefound) panic("inconsistent state"); /* 1, 3, 5 - impossible */ break <"ret">; /* 7 - host found, with this key */ } if (typefound) { /* 2, 6 */ if (! found) abort(); /* 2 - impossible */ /* 6 - host present, key of this type but not this key */ switch <"action"> ((HKACT)config_key("bad-hk-action")) { case HKACT_PROMPT: fprintf(errf, "* * * * * * * * * * * * *\n" "* *\n" "* HOST KEY MISMATCH!! *\n" "* *\n" "* * * * * * * * * * * * *\n" ); if (config_bool("no-interaction")) exit(0); showkey(); fflush(errf); while (1) { switch (prompted_choice("Continue connecting? (yes/no/replace/replall/add/show/all/?) ", 8,"?","yes","no","replace","replall","add","show","all")) { case 0: fprintf(errf,"yes - continue connecting anyway\n"); fprintf(errf,"no - don't continue\n"); fprintf(errf,"replace - continue, replacing all existing keys for this host and type\n"); fprintf(errf,"replall - continue and replace all existing keys for this host of any type\n"); fprintf(errf,"add - continue and add this key for this host\n"); fprintf(errf,"show - reprint key fingerprint and reprompt\n"); fprintf(errf,"all - print all other keys for this host and reprompt\n"); fprintf(errf,"Note that, quite aside from the security dangers inherent in ignoring the\n"); fprintf(errf,"safeguards provided by host keys, `replace', `replall', and `add' are risky\n"); fprintf(errf,"because they can corrupt the known-hosts file if two concurrent runs\n"); fprintf(errf,"collide over access to the file; use them carefully if at all.\n"); break; case 1: case <"action"> HKACT_YES: break <"ret">; break; case 2: case <"action"> HKACT_NO: exit(0); break; case 3: case <"action"> HKACT_REPLACE: mod_hkdb(dbf,&mod_key_call,&mod_replace,0); break <"ret">; break; case 4: case <"action"> HKACT_REPLALL: mod_hkdb(dbf,&mod_key_drop,0,0); break <"ret">; break; case 5: case <"action"> HKACT_ADD: mod_hkdb(dbf,&mod_key_copy,0,0); break <"ret">; break; case 6: showkey(); break; case 7: if (blobs) { unsigned char bfp[16]; fprintf(errf,"Other host keys known for this host:\n"); for (bl=blobs;bl;bl=bl->link) { h = md5_init(); md5_process_bytes(h,bl->data,bl->len); md5_result(h,&bfp[0]); fprintf(errf,"%s, fingerprint %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n",bl->alg->name,bfp[0],bfp[1],bfp[2],bfp[3],bfp[4],bfp[5],bfp[6],bfp[7],bfp[8],bfp[9],bfp[10],bfp[11],bfp[12],bfp[13],bfp[14],bfp[15]); } } else { fprintf(errf,"No other host keys known for this host\n"); } break; default: panic("impossible choice"); break; } } break; default: panic("impossible action"); break; } } if (config_key("no-hk-action") == HKACT_PROMPT) { fprintf(errf,"No %s key known for %s\n",hk->name,lookupkey); showkey(); } while (1) { switch <"action"> ((HKACT)config_key("no-hk-action")) { case HKACT_PROMPT: if (config_bool("no-interaction")) exit(0); while (1) { fflush(errf); switch (prompted_choice("Continue connecting? (yes/no/save/?) ",4,"?","yes","no","save")) { case 0: fprintf(errf,"yes - continue connecting this once\n"); fprintf(errf,"no - don't continue\n"); fprintf(errf,"save - continue, and save this key\n"); break; case 1: case <"action"> HKACT_YES: break <"ret">; case 2: case <"action"> HKACT_NO: exit(0); break; case 3: case <"action"> HKACT_SAVE: if (found) { mod_hkdb(dbf,&mod_key_copy,0,0); } else { append_hkdb(dbf,&mod_new); } break <"ret">; default: panic("impossible choice"); break; } } break; default: panic("impossible action"); break; } } } while (0); while (keys) { kl = keys; keys = kl->link; free(kl->key); free(kl); } while (blobs) { bl = blobs; blobs = bl->link; free(bl->data); free(bl); } } static void kh_op_import(char *host, int port) { int done; char *lookupkey; void *pkdata; int pklen; HKALG *hk; char *comment; NESTED MOD_K_OP key(const char *k, void (*save)(HKALG *, const void *, int)) { if (strcmp(k,lookupkey)) return(MOD_K_COPY); (*save)(hk,pkdata,pklen); done = 1; return(MOD_K_COPY); } NESTED void new(void (*save)(const char *, HKALG *, const void *, int)) { if (! done) (*save)(lookupkey,hk,pkdata,pklen); } if (! load_key_pair_stdio(stdin,"standard input",0,0,&pkdata,&pklen,&hk,0,0,&comment,0,errf)) exit(1); free(comment); asprintf(&lookupkey,"%s/%d",host,(port<0)?22:port); done = 0; mod_hkdb(hostkeydb(),&key,0,&new); } static void kh_op_export(char *host, int port) { const char *dbf; FILE *f; int hostlen; char *lookupkey; NESTED SCAN_OP key(const char *key) { if (port < 0) { const char *slash; slash = rindex(key,'/'); if ((slash-key != hostlen) || bcmp(key,host,hostlen)) return(SCAN_SKIP); } else { if (strcmp(key,lookupkey)) return(SCAN_SKIP); } return(SCAN_READ); } NESTED SCAN_OP val(HKALG *alg, const void *data, int len) { printf("%s ",alg->name); b64_write_blob(stdout,data,len); printf("\n"); return(SCAN_CONT); } hostlen = strlen(host); if (port >= 0) asprintf(&lookupkey,"%s/%d",host,port); else lookupkey = 0; dbf = hostkeydb(); f = fopen(dbf,"r"); if (f == 0) { fprintf(errf,"%s: %s: %s\n",__progname,dbf,strerror(errno)); fflush(errf); exit(1); } scan_hkdb(f,&key,&val); fclose(f); free(lookupkey); } static void kh_op_clear(char *host, int port) { char *lookupkey; NESTED MOD_K_OP key(const char *k, void (*save)(HKALG *, const void *, int) __attribute__((__unused__))) { return(strcmp(k,lookupkey)?MOD_K_COPY:MOD_K_DROP); } if (port < 0) { fprintf(errf,"%s: port number required for -kh clear\n",__progname); exit(1); } asprintf(&lookupkey,"%s/%d",host,port); mod_hkdb(hostkeydb(),&key,0,0); } static void kh_op_delete(char *host, int port) { int hostlen; char *lookupkey; void *pkdata; int pklen; HKALG *hk; char *comment; int found; NESTED MOD_K_OP key(const char *k, void (*save)(HKALG *, const void *, int) __attribute__((__unused__))) { if (port < 0) { const char *slash; slash = rindex(k,'/'); if ((slash-k != hostlen) || bcmp(k,host,hostlen)) return(MOD_K_COPY); } else { if (strcmp(k,lookupkey)) return(MOD_K_COPY); } return(MOD_K_CALL); } NESTED void val(HKALG *alg, const void *data, int len, void (*save)(HKALG *, const void *, int)) { if ((alg == hk) && (len == pklen) && !bcmp(data,pkdata,len)) { found = 1; } else { (*save)(alg,data,len); } } if (! load_key_pair_stdio(stdin,"standard input",0,0,&pkdata,&pklen,&hk,0,0,&comment,0,errf)) exit(1); free(comment); hostlen = strlen(host); if (port >= 0) asprintf(&lookupkey,"%s/%d",host,port); else lookupkey = 0; found = 0; mod_hkdb(hostkeydb(),&key,&val,0); if (! found) { fprintf(stderr,"%s: key not found\n",__progname); exit(1); } } static void kh_op_ports(char *host, int port) { const char *dbf; FILE *f; int hostlen; NESTED SCAN_OP key(const char *key) { const char *slash; slash = rindex(key,'/'); if ((slash-key == hostlen) && !bcmp(key,host,hostlen)) { printf("%s\n",slash+1); } return(SCAN_SKIP); } if (port >= 0) { fprintf(errf,"%s: port number not allowed for -kh ports\n",__progname); exit(1); } hostlen = strlen(host); dbf = hostkeydb(); f = fopen(dbf,"r"); if (f == 0) { fprintf(errf,"%s: %s: %s\n",__progname,dbf,strerror(errno)); fflush(errf); exit(1); } scan_hkdb(f,&key,0); fclose(f); } static void kh_op_find(void) { void *pkdata; int pklen; HKALG *hk; char *comment; char *curkey; const char *dbf; FILE *f; NESTED SCAN_OP key(const char *k) { free(curkey); curkey = strdup(k); return(SCAN_READ); } NESTED SCAN_OP val(HKALG *alg, const void *data, int len) { if (curkey && (alg == hk) && (len == pklen) && !bcmp(data,pkdata,len)) { printf("%s\n",curkey); free(curkey); curkey = 0; } return(SCAN_CONT); } if (! load_key_pair_stdio(stdin,"standard input",0,0,&pkdata,&pklen,&hk,0,0,&comment,0,errf)) exit(1); free(comment); curkey = 0; dbf = hostkeydb(); f = fopen(dbf,"r"); if (f == 0) { fprintf(errf,"%s: %s: %s\n",__progname,dbf,strerror(errno)); fflush(errf); exit(1); } scan_hkdb(f,&key,&val); fclose(f); free(curkey); } static void kh_op_hosts(void) { char *curhost; int chl; const char *dbf; FILE *f; NESTED SCAN_OP key(const char *k) { const char *slash; slash = rindex(k,'/'); if ((slash-k == chl) && !bcmp(k,curhost,chl)) return(SCAN_SKIP); free(curhost); chl = slash - k; curhost = malloc(chl+1); bcopy(k,curhost,chl); curhost[chl] = '\0'; printf("%s\n",curhost); return(SCAN_SKIP); } curhost = 0; chl = -1; dbf = hostkeydb(); f = fopen(dbf,"r"); if (f == 0) { fprintf(errf,"%s: %s: %s\n",__progname,dbf,strerror(errno)); fflush(errf); exit(1); } scan_hkdb(f,&key,0); fclose(f); free(curhost); } void known_hosts_op(void) { KH_OP op; char *host; int port; NESTED void crack_host(const char *h) { int hl; const char *slash; slash = rindex(h,'/'); if (slash) { if (!strcmp(slash,"/-")) { port = -1; } else { port = atoi(slash+1); } hl = slash - h; } else { hl = strlen(h); port = -1; } host = malloc(hl+1); bcopy(h,host,hl); host[hl] = '\0'; } op = (KH_OP) config_key("kh-op"); switch (op) { default: panic("impossible kh-op"); break; case KH_OP_IMPORT: case KH_OP_EXPORT: case KH_OP_CLEAR: case KH_OP_DELETE: case KH_OP_PORTS: crack_host(config_str("kh-host")); break; case KH_OP_FIND: case KH_OP_HOSTS: break; } switch (op) { default: panic("impossible kh-op"); break; case KH_OP_IMPORT: kh_op_import(host,port); break; case KH_OP_EXPORT: kh_op_export(host,port); break; case KH_OP_CLEAR: kh_op_clear(host,port); break; case KH_OP_DELETE: kh_op_delete(host,port); break; case KH_OP_PORTS: kh_op_ports(host,port); break; case KH_OP_FIND: kh_op_find(); break; case KH_OP_HOSTS: kh_op_hosts(); break; } }