/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "b64.h" #include "errf.h" #include "panic.h" #include "nested.h" #include "verbose.h" #include "stdio-util.h" #include "keyfiles.h" typedef enum { IPKIND_DEFAULT = 1, IPKIND_v4, IPKIND_v6, } IPKIND; typedef enum { IPACTION_ALLOW = 1, IPACTION_DENY, IPACTION_IGNORE, } IPACTION; typedef struct ak_ip AK_IP; typedef struct ak_ikey AK_IKEY; struct ak { int nkeys; int akeys; AK_IKEY **keys; int nips; int aips; AK_IP **ips; } ; struct ak_ikey { AK_KEY ekey; STR blob; char *comment; } ; struct ak_ip { IPKIND kind; IPACTION action; union { struct in_addr a4; struct in6_addr a6; } ; int width; } ; static int writeit(int fd, const char *data, int len, const char *what, const char *fn) { int n; int first; first = 1; while (len > 0) { n = write(fd,data,len); if (n < 0) { fprintf(errf,"%s: error writing %s %s: %s\n",__progname,what,fn,strerror(errno)); return(0); } if (n == 0) { fprintf(errf,"%s: error writing %s %s: zero-length write\n",__progname,what,fn); return(0); } if (first && (n < len)) { fprintf(errf,"%s: warning: short write writing %s %s (wanted %d, wrote %d) - retrying remainder\n",__progname,what,fn,len,n); first = 0; } data += n; len -= n; } return(1); } static char *read_pubkey_comment(FILE *f, const char *def) { char *cbuf; int l; int n; int c; NESTED void savec(char c) { if (l >= n) cbuf = realloc(cbuf,n=l+16); cbuf[l++] = c; } cbuf = 0; l = 0; n = 0; while (1) { c = getc(f); switch (c) { case '\n': ungetc(c,f); /* fall through */ case EOF: if (l == 0) return(def?strdup(def):0); savec('\0'); return(cbuf); break; case ' ': if (l == 0) break; default: savec(c); if (l >= 80) { savec('.'); savec('.'); savec('.'); savec('\0'); return(cbuf); } break; } } } int load_key_pair( const char *fn_pub, const char *fn_priv, void **pubdp, int *publp, HKALG **algp, void **privdp, int *privlp, char **commentp, char *defcomm, FILE *ef ) { char *pubfn; FILE *pubf; char *privfn; FILE *privf; int rv; if (pubdp) { if (! fn_pub) panic("no fn_pub"); asprintf(&pubfn,"%s.pub",fn_pub); } else { pubfn = 0; } if (privdp) { if (! fn_priv) panic("no fn_priv"); asprintf(&privfn,"%s.priv",fn_priv); } else { privfn = 0; } pubf = 0; privf = 0; rv = 0; do <"ret"> { if (pubdp) { pubf = fopen(pubfn,"r"); if (pubf == 0) { fprintf(ef,"can't open public key file %s: %s",pubfn,strerror(errno)); break <"ret">; } } if (privdp) { privf = fopen(privfn,"r"); if (privf == 0) { fprintf(ef,"can't open private key file %s: %s",privfn,strerror(errno)); fclose(pubf); break <"ret">; } } rv = load_key_pair_stdio( pubf, pubfn, privf, privfn, pubdp, publp, algp, privdp, privlp, commentp, defcomm, ef ); } while (0); free(pubfn); free(privfn); if (pubf) fclose(pubf); if (privf) fclose(privf); return(rv); } int load_key_pair_stdio( FILE *pubf, const char *pubfn, FILE *privf, const char *privfn, void **pubdp, int *publp, HKALG **algp, void **privdp, int *privlp, char **commentp, char *defcomm, FILE *ef ) { char kname[64+1]; HKALG *alg; void *pub; int publen; void *priv; int privlen; char *comment; int rv; if (pubdp && !pubf) panic("no pubf"); if (privdp && !privf) panic("no privf"); pub = 0; priv = 0; comment = 0; do <"ret"> { do <"fail"> { if (pubdp) { if (fscanf(pubf,"%64s ",&kname[0]) != 1) { fprintf(ef,"can't use public key from %s (missing type)",pubfn); break <"fail">; } alg = (*at_hk.find_c)(&kname[0]); if (alg == 0) { fprintf(ef,"can't use public key from %s (unknown algorithm name)",pubfn); break <"fail">; } *algp = alg; if (! b64_read_blob(pubf,&pub,&publen)) { fprintf(ef,"can't use public key from %s (can't read public-key blob)",pubfn); break <"fail">; } if (! (*alg->checkpub)(pub,publen)) { fprintf(ef,"can't use public key from %s (corrupted public-key blob)",pubfn); break <"fail">; } comment = read_pubkey_comment(pubf,0); } if (privdp) { if (! b64_read_blob(privf,&priv,&privlen)) { fprintf(ef,"can't use private key from %s (blob unreadable)",privfn); break <"fail">; } } if (pubdp) { *pubdp = pub; *publp = publen; *commentp = comment ? : defcomm; } if (privdp) { *privdp = priv; *privlp = privlen; } rv = 1; break <"ret">; } while (0); free(pub); free(priv); free(comment); rv = 0; break <"ret">; } while (0); return(rv); } int write_pub_to_fd(const char *what, const char *to, int fd, HKALG *alg, const void *pubdata, int publen, const char *comment) { FILE *f; char *blob; int blen; int rv; f = fopen_alloc(&blob,&blen); fprintf(f,"%s ",alg->name); b64_write_blob(f,pubdata,publen); if (comment) fprintf(f," %s",comment); fprintf(f,"\n"); fclose(f); rv = writeit(fd,blob,blen,what,to); free(blob); return(rv); } int brief_pub_to_fd(const char *what, const char *to, int fd, HKALG *alg, const char *comment) { FILE *f; char *blob; int blen; int rv; f = fopen_alloc(&blob,&blen); fprintf(f,"%s",alg->name); if (comment) fprintf(f," %s",comment); fprintf(f,"\n"); fclose(f); rv = writeit(fd,blob,blen,what,to); free(blob); return(rv); } int write_priv_to_fd(const char *what, const char *to, int fd, const void *privdata, int privlen) { FILE *f; char *blob; int blen; int rv; f = fopen_alloc(&blob,&blen); b64_write_blob(f,privdata,privlen); fprintf(f,"\n"); fclose(f); rv = writeit(fd,blob,blen,what,to); free(blob); return(rv); } void write_key(HKALG *alg, const char *pubfile, void *pubdata, int publen, const char *comment, const char *privfile, void *privdata, int privlen) { char *pubtmp; char *pubfn; char *privtmp; char *privfn; int fd; sigset_t mask; sigset_t omask; NESTED void dofrees(void) { free(pubfn); free(pubtmp); free(privfn); free(privtmp); } pubfn = 0; pubtmp = 0; privfn = 0; privtmp = 0; do <"errexit"> { if (pubfile) { asprintf(&pubfn,"%s.pub",pubfile); asprintf(&pubtmp,"%s.PUB",pubfile); fd = open(pubtmp,O_WRONLY|O_CREAT|O_EXCL,0644); if (fd < 0) { fprintf(errf,"%s: can't create %s: %s\n",__progname,pubtmp,strerror(errno)); break <"errexit">; } if (! write_pub_to_fd("public key temporary file",pubtmp,fd,alg,pubdata,publen,comment)) break <"errexit">; if (fsync(fd) < 0) { fprintf(errf,"%s: error flushing public key temporary file %s: %s\n",__progname,pubtmp,strerror(errno)); break <"errexit">; } if (close(fd) < 0) { fprintf(errf,"%s: error closing public key temporary file %s: %s\n",__progname,pubtmp,strerror(errno)); break <"errexit">; } } if (privfile) { asprintf(&privfn,"%s.priv",privfile); asprintf(&privtmp,"%s.PRIV",privfile); fd = open(privtmp,O_WRONLY|O_CREAT|O_EXCL,0600); if (fd < 0) { fprintf(errf,"%s: can't create %s: %s\n",__progname,privtmp,strerror(errno)); break <"errexit">; } if (! write_priv_to_fd("private key temporary file",privtmp,fd,privdata,privlen)) break <"errexit">; if (fsync(fd) < 0) { fprintf(errf,"%s: error flushing private key temporary file %s: %s\n",__progname,privtmp,strerror(errno)); break <"errexit">; } if (close(fd) < 0) { fprintf(errf,"%s: error closing private key temporary file %s: %s\n",__progname,privtmp,strerror(errno)); break <"errexit">; } } sigemptyset(&mask); sigaddset(&mask,SIGHUP); sigaddset(&mask,SIGINT); sigaddset(&mask,SIGQUIT); sigaddset(&mask,SIGTERM); sigaddset(&mask,SIGTSTP); sigaddset(&mask,SIGTTIN); sigaddset(&mask,SIGTTOU); sigprocmask(SIG_BLOCK,&mask,&omask); if (pubfile) { if (rename(pubtmp,pubfn) < 0) { fprintf(errf,"%s: can't rename %s -> %s: %s\n",__progname,pubtmp,pubfn,strerror(errno)); pubtmp = 0; break <"errexit">; } } if (privfile) { if (rename(privtmp,privfn) < 0) { fprintf(errf,"%s: can't rename %s -> %s: %s\n",__progname,privtmp,privfn,strerror(errno)); privtmp = 0; break <"errexit">; } } sigprocmask(SIG_SETMASK,&omask,0); dofrees(); return; } while (0); if (pubtmp) unlink(pubtmp); if (privtmp) unlink(privtmp); exit(1); } static void skip_to_nl(FILE *f) { int c; do c = getc(f); while ((c != '\n') && (c != EOF)); } static void ak_init(AK *ak) { ak->nkeys = 0; ak->akeys = 0; ak->keys = 0; ak->nips = 0; ak->aips = 0; ak->ips = 0; } static void ak_clear(AK *ak) { int i; for (i=ak->nkeys-1;i>=0;i--) { free_str(ak->keys[i]->blob); free(ak->keys[i]->comment); free(ak->keys[i]); } ak->nkeys = 0; for (i=ak->nips-1;i>=0;i--) { free(ak->ips[i]); } ak->nips = 0; } static void ak_done(AK *ak) { ak_clear(ak); free(ak->keys); free(ak->ips); } static void ak_key_line(AK *ak, const char *preread, FILE *f) { HKALG *alg; void *blob; int bloblen; char *comment; AK_IKEY *k; if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: input offset %ld\n",ftell(f)); do <"done"> { if (preread) { if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: pre-read algname %s\n",preread); alg = (*at_hk.find_c)(preread); } else { char algname[64+1]; if (fscanf(f,"%*[ \t]%64[^ \t\n]",&algname[0]) != 1) break <"done">; if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: read algname %s\n",&algname[0]); alg = (*at_hk.find_c)(&algname[0]); } if (alg == 0) { if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: alg not found\n"); break <"done">; } fscanf(f,"%*[ \t]"); if (! b64_read_blob(f,&blob,&bloblen)) { if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: base64 blob read failed\n"); break <"done">; } do <"done"> { if (! (*alg->checkpub)(blob,bloblen)) { if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: alg checkpub failed\n"); break <"done">; } comment = read_pubkey_comment(f,0); k = malloc(sizeof(AK_IKEY)); k->blob.data = blob; k->blob.len = bloblen; k->comment = comment; k->ekey.alg = alg; k->ekey.blob = str_to_rostr(k->blob); k->ekey.comment = comment; if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: saving key %s\n",comment); if (ak->nkeys >= ak->akeys) ak->keys = realloc(ak->keys,(ak->akeys=ak->nkeys+8)*sizeof(*ak->keys)); ak->keys[ak->nkeys++] = k; skip_to_nl(f); if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: (success) output offset %ld\n",ftell(f)); return; } while (0); free(blob); } while (0); skip_to_nl(f); if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_key_line: (failure) output offset %ld\n",ftell(f)); } static void ak_clientip_line(AK *ak, FILE *f) { char action[64+1]; char arg[64+1]; AK_IP ip; AK_IP *ipp; int maxwidth; if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_clientip_line: input offset %ld\n",ftell(f)); if (fscanf(f,"%*[ \t]%64[^ \t\n]%*[ \t]%64[^ \t\n]",&action[0],&arg[0]) != 2) return; skip_to_nl(f); if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_clientip_line: output offset %ld, action %s, arg %s\n",ftell(f),&action[0],&arg[0]); if (!strcmp(&action[0],"allow")) ip.action = IPACTION_ALLOW; else if (!strcmp(&action[0],"deny")) ip.action = IPACTION_DENY; else if (!strcmp(&action[0],"ignore")) ip.action = IPACTION_IGNORE; else return; if (!strcmp(&arg[0],"default")) { ip.kind = IPKIND_DEFAULT; } else { char *slash; slash = index(&arg[0],'/'); if (slash) { *slash++ = '\0'; ip.width = atoi(slash); } if (inet_pton(AF_INET,&arg[0],&ip.a4) == 1) { ip.kind = IPKIND_v4; maxwidth = 32; } else if (inet_pton(AF_INET6,&arg[0],&ip.a6) == 1) { ip.kind = IPKIND_v6; maxwidth = 128; } else { return; } if (slash) { if ((ip.width < 0) || (ip.width > maxwidth)) return; } else { ip.width = maxwidth; } } ipp = malloc(sizeof(AK_IP)); *ipp = ip; if (ak->nips >= ak->aips) ak->ips = realloc(ak->ips,(ak->aips=ak->nips+8)*sizeof(*ak->ips)); ak->ips[ak->nips++] = ipp; } int read_authkeys_file(const char *file, int (*callback)(AK *)) { FILE *f; char kname[64+1]; int rv; AK ak; int in_parens; NESTED int do_callback(void) { int v; if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: calling callback\n"); v = (*callback)(&ak); if (v < 0) { rv = v; return(1); } rv += v; return(0); } if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: file is %s\n",file); f = fopen(file,"r"); if (f == 0) { if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: can't open %s: %s\n",file,strerror(errno)); return(0); } rv = 0; in_parens = 0; ak_init(&ak); while (1) { if (fscanf(f,"%64s",&kname[0]) != 1) { fclose(f); ak_done(&ak); if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: keyword fscanf failed, returning %d\n",rv); return(rv); } if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: keyword is %s\n",&kname[0]); if ((kname[0] == '#') || (kname[0] == ';')) { if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: comment line\n"); skip_to_nl(f); continue; } if (!in_parens && !strcmp(&kname[0],"(")) { if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: starting parenthesized clause\n"); in_parens = 1; skip_to_nl(f); ak_clear(&ak); continue; } if (in_parens && !strcmp(&kname[0],")")) { if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: finishing parenthesized clause\n"); in_parens = 0; skip_to_nl(f); if (do_callback()) break; continue; } if (in_parens) { if (VERB(AUTHKEY)) verb(AUTHKEY,"read_authkeys_file: inside parenthesized clause\n"); if (! strcmp(&kname[0],"key")) { ak_key_line(&ak,0,f); } else if (! strcmp(&kname[0],"clientip")) { ak_clientip_line(&ak,f); } else { skip_to_nl(f); continue; } } else { ak_clear(&ak); ak_key_line(&ak,&kname[0],f); if (ak.nkeys && do_callback()) break; } } fclose(f); return(rv); } int ak_nkeys(AK *ak) { return(ak->nkeys); } const AK_KEY *ak_key(AK *ak, int x) { return(&ak->keys[x]->ekey); } static int ipv4_match(const struct in_addr *ia1, const struct in_addr *ia2, int width) { unsigned int mask; if (width == 0) mask = 0U; else mask = (~0U) << (32-mask); mask &= 0xffffffffU; return(((ntohl(ia1->s_addr)^ntohl(ia2->s_addr))&mask)==0); } static int ipv6_match(const struct in6_addr *ia1, const struct in6_addr *ia2, int width) { if ( (width >= 8) && bcmp(&ia1->s6_addr[0],&ia2->s6_addr[0],width>>3) ) return(0); if ( (width & 7) && ( (ia1->s6_addr[width>>3] ^ ia2->s6_addr[width>>3]) & 0xff & (0xff00>>(width&7)) ) ) return(0); return(1); } /* * Yes, the last arg to inet_ntop is the output buffer size. Why it's * socklen_t instead of size_t I have no idea. */ static const char *sockaddr_addr_str(const void *ipsa) { static char obuf[64]; switch (((const struct sockaddr *)ipsa)->sa_family) { case AF_INET: return(inet_ntop(AF_INET,&((const struct sockaddr_in *)ipsa)->sin_addr,&obuf[0],sizeof(obuf))); break; case AF_INET6: return(inet_ntop(AF_INET6,&((const struct sockaddr_in6 *)ipsa)->sin6_addr,&obuf[0],sizeof(obuf))); break; } panic("sockaddr_addr_str impossible family"); } static const char *ip_addr_str(int af, const void *ipa) { static char obuf[64]; return(inet_ntop(af,ipa,&obuf[0],sizeof(obuf))); } static const char *ipaction_str(IPACTION a) { switch (a) { case IPACTION_ALLOW: return("allow"); break; case IPACTION_DENY: return("deny"); break; case IPACTION_IGNORE: return("ignore"); break; } return("?""?""?"); } AK_IP_OK_RV ak_ip_ok(AK *ak, const struct sockaddr *ip, int iplen) { int i; int match; AK_IP *akip; if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_ip_ok: checking %s\n",sockaddr_addr_str(ip)); if (ak->nips == 0) { if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_ip_ok: no clientips\n"); return(AK_IP_ALLOW); } for (i=0;inips;i++) { akip = ak->ips[i]; switch (akip->kind) { case IPKIND_DEFAULT: if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_ip_ok: checking versus %s default\n",ipaction_str(akip->action)); match = 1; break; case IPKIND_v4: if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_ip_ok: checking versus %s %s/%d\n",ipaction_str(akip->action),ip_addr_str(AF_INET,&akip->a4),akip->width); match = (ip->sa_family == AF_INET) && (iplen == ip->sa_len) && ipv4_match(&((const struct sockaddr_in *)ip)->sin_addr,&akip->a4,akip->width); break; case IPKIND_v6: if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_ip_ok: checking versus %s %s/%d\n",ipaction_str(akip->action),ip_addr_str(AF_INET6,&akip->a6),akip->width); match = (ip->sa_family == AF_INET6) && (iplen == ip->sa_len) && ipv6_match(&((const struct sockaddr_in6 *)ip)->sin6_addr,&akip->a6,akip->width); break; default: panic("impossible AK IP kind %d",(int)akip->kind); break; } if (match) { if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_ip_ok: matched, returning\n"); switch (akip->action) { case IPACTION_ALLOW: return(AK_IP_ALLOW); break; case IPACTION_DENY: return(AK_IP_DENY); break; case IPACTION_IGNORE: return(AK_IP_SKIP); break; default: panic("impossible AK IP action %d",(int)akip->action); break; } } } if (VERB(AUTHKEY)) verb(AUTHKEY,"ak_ip_ok: no IP match, denying\n"); return(AK_IP_DENY); } AK_CMD_LIST *ak_cmd_list_get(AK *ak) { ak=ak; return(0); } int ak_cmd_list_ok(AK_CMD_LIST *akcl, const char *cmd, int cmdlen) { akcl=akcl; cmd=cmd; cmdlen=cmdlen; return(1); } void ak_cmd_list_done(AK_CMD_LIST *akcl) { akcl=akcl; }