/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "oq.h" #include "pp.h" #include "rnd.h" #include "dll.h" #include "b64.h" #include "msgs.h" #include "errf.h" #include "util.h" #include "agent.h" #include "panic.h" #include "config.h" #include "nested.h" #include "verbose.h" #include "pollloop.h" #include "pkt-util.h" #include "alg-util.h" #include "stdio-util.h" #include "agent-util.h" #include "scm-rights.h" #include "agent-server.h" /* * There is a potential portability-problem-cum-security-risk here. * Traditionally, when sending file descriptors as SCM_RIGHTS control * data, all the file descriptors sent by the sender arrive in the * receiver's open file table, even if the control area provided by * the receiver is too small and some of the file descriptor numbers * thus don't make it. The receiver can detect this (MSG_CTRUNC) but * has no good way to recover. * * I consider this a bug. It makes it difficult to build fd-receiving * servers which can withstand malicious clients. (Not quite * impossible; by using an auxiliary process or two, it's doable, but * it's difficult and kludgy. It might be possible to avoid auxiliary * processes by remembering the state of the open file table in user * memory across the recvmsg, but that too is difficult and kludgy, * and untested to boot.) * * This code assumes that this bug has been fixed - that any file * descriptors not making it into the message for MSG_CTRUNC reasons * also don't arrive in the receiver's open file table. If this isn't * the case for you, you can either live with the risk or just fix it; * it's a relatively simple thing to fix, at least for BSD kernels. * The risk is relatively minor; it just allows an attacker to DoS the * agent by running it out of file descriptors - it doesn't permit any * illicit access. It also is a local-only exploit, not something a * remote attacker can do. */ typedef enum { LKUP_NOTFOUND = 1, LKUP_AMBIGUOUS, LKUP_FOUND } LKUPSTAT; typedef enum { POS_CONFIRM = 1, POS_PENDING, POS_INPROGRESS } PENDOPSTATE; typedef struct client CLIENT; typedef struct key KEY; typedef struct fwdhop FWDHOP; typedef struct agentstate AGENTSTATE; typedef struct listener LISTENER; typedef struct challtemp CHALLTEMP; typedef struct interstate INTERSTATE; typedef struct pendop PENDOP; typedef struct icmd ICMD; typedef struct icmdlist ICMDLIST; typedef struct ictx ICTX; typedef struct dropid DROPID; typedef struct pendtc PENDTC; typedef struct cresppriv CRESPPRIV; struct ictx { unsigned char *bp; int len; FILE *o; CLIENT *c; void (*throw)(void); } ; struct icmd { const char *name; void (*handler)(ICTX *); const char *help; int namelen; } ; struct icmdlist { ICMD *cmds; int ncmds; int maxlen; } ; #define ICMDLIST_INIT(cmdvec) { &cmdvec[0], sizeof(cmdvec)/sizeof(cmdvec[0]), -1 } struct interstate { AGENTSTATE *s; char *buf; int len; } ; struct challtemp { CHALLTEMP *flink; CHALLTEMP *blink; LISTENER *l; int fd; int id; unsigned char challenge[16]; unsigned char crespbuf[10]; int crespfill; int proto; } ; struct listener { int fd; int id; unsigned char cookie[16]; char *socketpath; void (*newfd)(int, void *, AGENT_PROTO); void *arg; CHALLTEMP *cts; unsigned int flags; #define LF_JUSTONCE 0x00000001 #define LF_SHUTDOWN 0x00000002 } ; struct pendtc { PENDOP *q; PENDOP **qt; } ; struct agentstate { CLIENT **clients; int nclients; void *listener; KEY *keys; int waitpipe[2]; int waitid; int oppipe; pid_t cmdkid; pid_t opkid; PENDTC conf; PENDTC pend; char *tagbusy; int ntag; PENDOP *inprogress; int pendid; int ipid; } ; struct dropid { AGENTSTATE *s; int tag; int fd; int id; } ; struct pendop { PENDOP *link; PENDOPSTATE state; KEY *k; char *data; CLIENT *c; FWDHOP *fwding; int tag; AGENTSTATE *s; } ; struct client { AGENTSTATE *s; AGENT_PROTO proto; int n; int fd; int fill; int len; unsigned char *buf; int alloc; unsigned int flags; #define CF_DEAD 0x00000001 #define CF_IDLE 0x00000002 OQ oq; int id; union { struct { FWDHOP *fwding; int nfwd; char *idtext; } norm; } ; } ; struct cresppriv { CLIENT *c; char *rbuf; int rlen; int rq; } ; struct key { KEY *link; AGENTSTATE *s; HKALG *alg; STR pub; STR priv; STR desc; unsigned int serial; struct timeval timeout_at; int time_fd; int time_id; AGENT_CONSTRAINTS constraints; } ; struct fwdhop { FWDHOP *link; STR host; STR ip; unsigned int port; } ; typedef enum { OPRV_FAIL = 1, OPRV_OK } OPRV; static AGENTSTATE *global_agentstate; static const char letters[52] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; static unsigned int keyserial = 1; #define ICMD_ARGS \ ICTX *cx __attribute__((__unused__)) static ICMDLIST icmds; /* forward */ static int mysocketpair(int d, int t, int p, int *v) { int rv; static int dnfd = -1; char tbuf[64]; if (dnfd < 0) dnfd = open("/dev/null",O_WRONLY,0); rv = socketpair(d,t,p,v); if (rv < 0) return(-1); write(dnfd,&tbuf[0],sprintf(&tbuf[0],"%d %d",v[0],v[1])); return(0); } #define socketpair mysocketpair static void clear_fwd_list(FWDHOP **list) { FWDHOP *f; while ((f = *list)) { *list = f->link; free_str(f->host); free_str(f->ip); free(f); } } static int w_client_resp(void *pv, const char *buf, int len) { CRESPPRIV *p; p = pv; p->rbuf = realloc(p->rbuf,p->rlen+len); bcopy(buf,p->rbuf+p->rlen,len); p->rlen += len; return(len); } static int c_client_resp(void *pv) { CRESPPRIV *p; char lbuf[4]; unsigned char flags; p = pv; put_uint32(&lbuf[0],p->rlen); oq_queue_copy(&p->c->oq,&lbuf[0],4); flags = 0; if (p->rq) flags |= AGENT_INTER_REPLY; oq_queue_copy(&p->c->oq,&flags,1); oq_queue_free(&p->c->oq,p->rbuf,p->rlen); if (VERB(IAGENT)) { verb(IAGENT,"iagent client %s fd %d (%d):\n",p->rq?"reply":"message",p->c->fd,p->rlen); verb_data_block(VERBOSE_IAGENT,p->rbuf,p->rlen); } free(p); return(0); } static FILE *fopen_client_response(CLIENT *c, int requested) { CRESPPRIV *p; p = malloc(sizeof(CRESPPRIV)); p->c = c; p->rbuf = 0; p->rlen = 0; p->rq = requested; return(funopen(p,0,&w_client_resp,0,&c_client_resp)); } static void notify_interactive(AGENTSTATE *, const char *, ...) __attribute__((__format__(__printf__,2,3))); static void notify_interactive(AGENTSTATE *s, const char *fmt, ...) { FILE *f; va_list ap; int cx; CLIENT *c; for (cx=s->nclients-1;cx>=0;cx--) { c = s->clients[cx]; if ( !(c->flags & (CF_DEAD|CF_IDLE)) && (c->proto == AGENT_PROTO_INTERACTIVE) ) { f = fopen_client_response(c,0); va_start(ap,fmt); vfprintf(f,fmt,ap); va_end(ap); fclose(f); } } } static int inter_w(void *isv, const char *data, int len) { INTERSTATE *is; if (len > 0) { is = isv; is->buf = realloc(is->buf,is->len+len); bcopy(data,is->buf+is->len,len); is->len += len; } return(len); } static int inter_c(void *isv) { INTERSTATE *is; int cx; CLIENT *c; FILE *f; is = isv; if (is->len > 0) { for (cx=is->s->nclients-1;cx>=0;cx--) { c = is->s->clients[cx]; if ( !(c->flags & (CF_DEAD|CF_IDLE)) && (c->proto == AGENT_PROTO_INTERACTIVE) ) { f = fopen_client_response(c,0); fwrite(is->buf,1,is->len,f); fclose(f); } } } free(is->buf); free(is); return(0); } static FILE *fopen_interactive(AGENTSTATE *s) { FILE *f; INTERSTATE *is; is = malloc(sizeof(INTERSTATE)); if (! is) return(0); f = funopen(is,0,&inter_w,0,&inter_c); if (! f) { free(is); return(0); } is->s = s; is->buf = 0; is->len = 0; return(f); } static void do_pends(AGENTSTATE *s, void (*fn)(PENDOP *)) { PENDOP *p; for (p=s->conf.q;p;p=p->link) (*fn)(p); for (p=s->pend.q;p;p=p->link) (*fn)(p); if (s->inprogress) (*fn)(s->inprogress); } static void idle_client(int id __attribute__((__unused__)), void *cv) { CLIENT *c; c = cv; c->flags |= CF_IDLE; if (c->fd >= 0) { close(c->fd); c->fd = -1; } if (c->id != PL_NOID) { remove_poll_id(c->id); c->id = PL_NOID; } } static void print_fd_fwd_list(FILE *f, CLIENT *c, FWDHOP *fwds) { FWDHOP *h; const char *pref; const char *suf; if (! c) { fprintf(f,"dead client"); return; } if (c->proto != AGENT_PROTO_NORMAL) panic("bad proto"); fprintf(f,"client %d, FD %d",c->n,c->fd); pref = " ("; suf = ""; for (h=fwds;h;h=h->link) { fprintf(f,"%s%.*s[%.*s/%u]",pref,h->host.len,h->host.data,h->ip.len,h->ip.data,h->port); pref = " -> "; suf = ")"; } fprintf(f,"%s",suf); if (c->norm.idtext) fprintf(f," [%s]",c->norm.idtext); } static void killclient(CLIENT *c) { FILE *f; NESTED void clrclient(PENDOP *p) { if (p->c == c) p->c = 0; } switch (c->proto) { default: panic("bad proto"); break; case AGENT_PROTO_NORMAL: f = fopen_interactive(c->s); print_fd_fwd_list(f,c,0); fprintf(f,": dead\n"); fclose(f); clear_fwd_list(&c->norm.fwding); break; case AGENT_PROTO_INTERACTIVE: break; } do_pends(c->s,&clrclient); c->flags |= CF_DEAD; close(c->fd); c->fd = -1; free(c->buf); c->buf = 0; oq_flush(&c->oq); remove_poll_id(c->id); c->id = PL_NOID; #ifdef AF_TIMER { struct itimerval itv; c->fd = socket(AF_TIMER,SOCK_STREAM,0); if (c->fd < 0) { fprintf(errf,"%s: can't create AF_TIMER socket: %s\n",__progname,strerror(errno)); exit(1); } itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; itv.it_value.tv_sec = 10; itv.it_value.tv_usec = 0; write(c->fd,&itv,sizeof(itv)); c->id = add_poll_fd(c->fd,&rwtest_always,&rwtest_never,&idle_client,0,c); } #else idle_client(0,c); #endif } static inline unsigned int scr_to_uint32(unsigned int v) { return(v); } static inline STR *scr_to_str_p(STR *v) { return(v); } static inline ROSTR *scr_to_rostr_p(ROSTR *v) { return(v); } static inline void *scr_to_void_p(void *v) { return(v); } static inline int scr_to_int(int v) { return(v); } static inline void (*scr_to_callfn_p(void (*v)(FILE *)))(FILE *) { return(v); } static void send_client_response(CLIENT *c, int pkttype, ...) #define T_END T__END #define T__END 1 #define T_UINT32(v) T__UINT32,scr_to_uint32(v) #define T__UINT32 2 #define T_STR(s) T__STR,scr_to_str_p(s) #define T__STR 3 #define T_ROSTR(s) T__ROSTR,scr_to_rostr_p(s) #define T__ROSTR 4 #define T_CALL(f) T__CALL,scr_to_callfn_p(f) #define T__CALL 5 #define T_BLOCK(p,n) T__BLOCK,scr_to_void_p(p),scr_to_int(n) #define T__BLOCK 6 { va_list ap; char *buf; int len; FILE *f; int t; f = fopen_alloc(&buf,&len); fwrite("\0\0\0\0",1,4,f); putc(pkttype,f); va_start(ap,pkttype); while (1) { t = va_arg(ap,int); switch (t) { case T__END: fclose(f); if (len < 5) panic("impossibly short"); put_uint32(buf,len-4); if (VERB(AGENT)) { verb(AGENT,"Agent response packet (%d):\n",len); verb_data_block(VERBOSE_AGENT,buf,len); } oq_queue_free(&c->oq,buf,len); return; break; case T__UINT32: fput_uint32(f,va_arg(ap,unsigned int)); break; case T__STR: { STR *sp; sp = va_arg(ap,STR *); fput_string(f,sp->data,sp->len); } break; case T__ROSTR: { ROSTR *sp; sp = va_arg(ap,ROSTR *); fput_string(f,sp->data,sp->len); } break; case T__CALL: { void (*fn)(FILE *); fn = va_arg(ap,__typeof__(fn)); (*fn)(f); } break; default: panic("bad key"); break; } } va_end(ap); } static KEY *build_key(STR pub, STR priv, STR desc) { KEY *k; HKALG *alg; int i; for (i=0;(alg=(*at_hk.list)(i));i++) { if ((*alg->checkpub)(pub.data,pub.len)) { k = malloc(sizeof(KEY)); k->alg = alg; k->pub = pub; k->priv = priv; k->desc = desc; k->serial = keyserial ++; k->time_fd = -1; reinit_constraints(&k->constraints); return(k); } } return(0); } static int add_constraints(KEY *k, const void *restp, int restl, void (*fail)(const void *, const char *, ...)) { const unsigned char *rp; const unsigned char *rend; int ct; union { unsigned int i; int b; STR s; } v; rp = restp; rend = rp + restl; while (1) { if (rp >= rend) return(SSH_AGENT_ERROR_NO_ERROR); ct = *rp++; if ((ct >= 50) && (ct <= 99)) { /* Constraints 50-99 have a uint32 argument */ if (rend-rp < 4) (*fail)(0,"no space for argument to constraint %d",ct); v.i = get_uint32(rp); rp += 4; switch (ct) { case SSH_AGENT_CONSTRAINT_TIMEOUT: k->constraints.time = v.i; break; case SSH_AGENT_CONSTRAINT_USE_LIMIT: k->constraints.use = v.i; break; case SSH_AGENT_CONSTRAINT_FORWARDING_STEPS: k->constraints.fwd = v.i; break; default: return(SSH_AGENT_ERROR_UNSUPPORTED_OP); break; } } else if ((ct >= 100) && (ct <= 149)) { /* Constraints 100-149 have a string argument */ return(SSH_AGENT_ERROR_UNSUPPORTED_OP); } else if ((ct >= 150) && (ct <= 199)) { /* Constraints 150-199 have a boolean argument */ if (rend-rp < 1) (*fail)(0,"no space for argument to constraint %d",ct); v.b = *rp ? 1 : 0; rp ++; switch (ct) { case SSH_AGENT_CONSTRAINT_NEED_USER_VERIFICATION: k->constraints.confirm = v.b ? 1 : 0; break; default: return(SSH_AGENT_ERROR_UNSUPPORTED_OP); break; } } else { return(SSH_AGENT_ERROR_UNSUPPORTED_OP); } } } static void drop_key(KEY *k) { NESTED void clrkey(PENDOP *p) { if (p->k == k) p->k = 0; } do_pends(k->s,&clrkey); free_str(k->pub); free_str(k->priv); free_str(k->desc); if (k->time_fd >= 0) { remove_poll_id(k->time_id); close(k->time_fd); } free(k); } static void remove_key(KEY *key) { KEY **kp; KEY *k; for (kp=&key->s->keys;(k=*kp);kp=&k->link) { if (k == key) { *kp = k->link; drop_key(k); return; } } panic("not found"); } static void time_key_out(int id __attribute__((__unused__)), void *kv) { KEY *k; FILE *f; k = kv; f = fopen_interactive(k->s); fprintf(f,"Deleting key %u (timeout expired)\n",k->serial); fclose(f); remove_key(k); } static void reset_timeout(KEY *k) { if (k->time_fd >= 0) { remove_poll_id(k->time_id); close(k->time_fd); k->time_fd = -1; } if (k->constraints.time) { gettimeofday(&k->timeout_at,0); k->timeout_at.tv_sec += k->constraints.time; #ifdef AF_TIMER { int s; struct itimerval itv; s = socket(AF_TIMER,SOCK_STREAM,0); if (s < 0) { fprintf(errf,"%s: can't create AF_TIMER socket: %s\n",__progname,strerror(errno)); exit(1); } itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; itv.it_value.tv_sec = k->constraints.time; itv.it_value.tv_usec = 0; write(s,&itv,sizeof(itv)); k->time_fd = s; k->time_id = add_poll_fd(s,&rwtest_always,&rwtest_never,&time_key_out,0,k); } #else if (! k) time_key_out(0,k); /* pacify -Wunused */ #endif } } static void save_key(AGENTSTATE *s, KEY *k) { k->s = s; k->link = s->keys; s->keys = k; reset_timeout(k); } static KEY *lookup_key(AGENTSTATE *s, const STR *pub, int fwdcount) { KEY *k; for (k=s->keys;k;k=k->link) { if ( (k->constraints.fwd != LIMIT_INFINITY) && (k->constraints.fwd < fwdcount) ) continue; if (str_equalss(*pub,k->pub)) break; } return(k); } static void print_key_to(FILE *f, KEY *k) { int w; fprintf(f,"key %u: %n%s ",k->serial,&w,k->alg->name); fwrite(k->desc.data,1,k->desc.len,f); fprintf(f,"\n"); if (k->constraints.time) { time_t tt; struct tm *tm; struct timeval now; unsigned int secs; gettimeofday(&now,0); secs = k->timeout_at.tv_sec - now.tv_sec; tt = k->timeout_at.tv_sec; tm = gmtime(&tt); fprintf(f," Timeout in "); #define FOO(s,c) \ if (secs >= s) \ { fprintf(f,"%d"#c,secs/s); \ secs %= s; \ } if (secs == 0) { fprintf(f,"0s"); } else { FOO(604800,w) FOO(86400,d) FOO(3600,h) FOO(60,m) FOO(1,s) } #undef FOO fprintf(f," (at %04d-%02d-%02d %02d:%02d:%02d.%06u UTC)\n", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, (unsigned int)k->timeout_at.tv_usec); } if (k->constraints.fwd != LIMIT_INFINITY) { fprintf(f,"%*sForwarding limit: %u\n",w,"",k->constraints.fwd); } if (k->constraints.use != LIMIT_INFINITY) { fprintf(f,"%*sRemaining use count: %u\n",w,"",k->constraints.use); } if (k->constraints.confirm) { fprintf(f,"%*sConfirm on use\n",w,""); } } static void print_fd_fwd(FILE *f, CLIENT *c) { print_fd_fwd_list(f,c,c->norm.fwding); } static void use_key(KEY *k, CLIENT *c) { FILE *f; if (c && (c->s != k->s)) panic("state wrong"); if (k->constraints.use != LIMIT_INFINITY) { if (k->constraints.use < 2) { f = fopen_interactive(k->s); if (c) print_fd_fwd(f,c); else fprintf(f,"(no client)"); fprintf(f,": deleting key %u (uselimit expired)\n",k->serial); fclose(f); remove_key(k); } else { k->constraints.use --; } } } static FWDHOP *copy_fwding(FWDHOP *list) { FWDHOP *rv; FWDHOP **p; FWDHOP *h; FWDHOP *new; p = &rv; for (h=list;h;h=h->link) { new = malloc(sizeof(FWDHOP)); new->host = str_copy(h->host); new->ip = str_copy(h->ip); new->port = h->port; *p = new; p = &new->link; } *p = 0; return(rv); } static void append_pend(PENDOP *p, PENDTC *tc) { p->link = 0; *tc->qt = p; tc->qt = &p->link; } static void prepend_pend(PENDOP *p, PENDTC *tc) { if (! (p->link = tc->q)) tc->qt = &p->link; tc->q = p; } static PENDOP *pend_op(KEY *k, const void *hash, CLIENT *c) { AGENTSTATE *s; PENDOP *new; int hl; int n; s = k->s; if (s != c->s) panic("mismatched state"); hl = (*k->alg->prehash)()->hashlen; new = malloc(sizeof(PENDOP)+hl); verb(AWORK,"pend_op k=%p(%u) c=%p data len %d -> %p\n", (void *)k,k->serial,(void *)c,hl,(void *)new); new->k = k; new->data = (void *)(new+1); bcopy(hash,new->data,hl); new->c = c; new->fwding = copy_fwding(c->norm.fwding); do <"haven"> { for (n=0;nntag;n++) if (! s->tagbusy[n]) break <"haven">; s->tagbusy = realloc(s->tagbusy,s->ntag=n+1); } while (0); s->tagbusy[n] = 1; new->tag = n; new->s = s; if (k->constraints.confirm) { new->state = POS_CONFIRM; append_pend(new,&s->conf); notify_interactive(s,"PK op on key %u requires confirmation (tag %d)\n",k->serial,new->tag); } else { new->state = POS_PENDING; append_pend(new,&s->pend); } return(new); } static void print_pendop(FILE *f, PENDOP *p) { fprintf(f,"%d: ",p->tag); print_fd_fwd_list(f,p->c,p->fwding); if (p->k) { fprintf(f,", key %u",p->k->serial); } else { fprintf(f,", now-dropped key"); } } static void process_client_packet(CLIENT *c) { __label__ failure; FILE *f; NESTED void fail(const void *at __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: client protocol error: can't parse packet: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\n"); goto failure; } if (VERB(AGENT)) { verb(AGENT,"Agent input packet (%d):\n",c->len); verb_data_block(VERBOSE_AGENT,&c->buf[0],c->len); } if (c->len < 4) panic("too small"); if (c->len < 5) { fprintf(errf,"%s: agent client packet too short (%d)\n",__progname,c->len); failure:; killclient(c); return; } switch (c->buf[4]) { case SSH_AGENT_REQUEST_VERSION: /* Ignore the rest of the packet. We arguably should syntax-check the string if it's present. */ if (config_bool("no-private")) { send_client_response(c,SSH_AGENT_VERSION_RESPONSE, T_UINT32(3), T_END ); } else { ROSTR x_name_1; ROSTR x_data_1; x_name_1 = cstr_to_rostr("client-id@rodents.montreal.qc.ca"); x_data_1 = cstr_to_rostr(""); send_client_response(c,SSH_AGENT_VERSION_RESPONSE, T_UINT32(3), T_ROSTR(&x_name_1), T_ROSTR(&x_data_1), T_END ); } break; case SSH_AGENT_ADD_KEY: { STR privblob; STR pubblob; STR desc; KEY *k; const void *restp; int restl; int err; parse_data(c->buf+5,c->len-5,&fail, PP_STRING(&privblob), PP_STRING(&pubblob), PP_STRING(&desc), PP_REST(&restp,&restl) ); k = build_key(pubblob,privblob,desc); if (k == 0) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_SUITABLE), T_END ); free_str(privblob); free_str(pubblob); free_str(desc); break; } err = add_constraints(k,restp,restl,&fail); switch (err) { case SSH_AGENT_ERROR_NO_ERROR: f = fopen_interactive(c->s); print_fd_fwd(f,c); fprintf(f,": added key:\n"); print_key_to(f,k); if (k->constraints.use < 1) { drop_key(k); fprintf(f,"(no uses, expiring immediately)\n"); } else { save_key(c->s,k); } fclose(f); send_client_response(c,SSH_AGENT_SUCCESS,T_END); break; case SSH_AGENT_ERROR_TIMEOUT: case SSH_AGENT_ERROR_KEY_NOT_FOUND: case SSH_AGENT_ERROR_DECRYPT_FAILED: case SSH_AGENT_ERROR_SIZE_ERROR: case SSH_AGENT_ERROR_KEY_NOT_SUITABLE: case SSH_AGENT_ERROR_DENIED: case SSH_AGENT_ERROR_FAILURE: case SSH_AGENT_ERROR_UNSUPPORTED_OP: drop_key(k); send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(err), T_END ); break; default: break; } } break; case SSH_AGENT_DELETE_ALL_KEYS: parse_data(c->buf+5,c->len-5,&fail,PP_ENDSHERE); if (c->norm.nfwd) { f = fopen_interactive(c->s); print_fd_fwd(f,c); fprintf(f,": tried to delete all keys\n"); fclose(f); send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_DENIED), T_END ); } else { f = fopen_interactive(c->s); print_fd_fwd(f,c); fprintf(f,": deleted all keys\n"); fclose(f); while (c->s->keys) remove_key(c->s->keys); send_client_response(c,SSH_AGENT_SUCCESS,T_END); } break; case SSH_AGENT_LIST_KEYS: { NESTED void list_keys(FILE *f) { int n; KEY *k; n = 0; for (k=c->s->keys;k;k=k->link) n ++; fput_uint32(f,n); for (k=c->s->keys;k;k=k->link) { fput_string(f,k->pub.data,k->pub.len); fput_string(f,k->desc.data,k->desc.len); } } f = fopen_interactive(c->s); print_fd_fwd(f,c); fprintf(f,": listed keys\n"); fclose(f); send_client_response(c,SSH_AGENT_KEY_LIST, T_CALL(list_keys), T_END ); } break; case SSH_AGENT_PRIVATE_KEY_OP: { STR opname; STR key; const void *restp; int restl; KEY *k; parse_data(c->buf+5,c->len-5,&fail, PP_STRING(&opname), PP_STRING(&key), PP_REST(&restp,&restl) ); k = lookup_key(c->s,&key,c->norm.nfwd); if (! k) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_FOUND), T_END ); } else if ( (k->constraints.fwd != LIMIT_INFINITY) && (c->norm.nfwd > k->constraints.fwd)) { panic("pk op shouldn't've found key"); } else if (str_equalsC(opname,"sign")) { STR data; HASHALG *ha; parse_data(restp,restl,&fail, PP_UNWIND(&pp_free_str,&opname), PP_UNWIND(&pp_free_str,&key), PP_STRING(&data), PP_ENDSHERE ); if (! k->alg->can_sig) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_SUITABLE), T_END ); } else { ha = (*k->alg->prehash)(); if (data.len != ha->hashlen) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_FAILURE), T_END ); } else { f = fopen_interactive(c->s); print_pendop(f,pend_op(k,data.data,c)); fprintf(f," (pending)\n"); fclose(f); verb(AWORK,"sign key %u, pending\n",k->serial); } } free_str(data); } else if (str_equalsC(opname,"hash-and-sign")) { STR data; HASHALG *ha; void *hav; char *hash; parse_data(restp,restl,&fail, PP_UNWIND(&pp_free_str,&opname), PP_UNWIND(&pp_free_str,&key), PP_STRING(&data), PP_ENDSHERE ); if (! k->alg->can_sig) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_SUITABLE), T_END ); } else { ha = (*k->alg->prehash)(); hash = malloc(ha->hashlen); hav = (*ha->init)(); (*ha->process)(hav,data.data,data.len); (*ha->done)(hav,hash); f = fopen_interactive(c->s); print_pendop(f,pend_op(k,hash,c)); fprintf(f," (pending)\n"); fclose(f); verb(AWORK,"hash-and-sign key %u, pending\n",k->serial); free(hash); } free_str(data); } else { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_UNSUPPORTED_OP), T_END ); } free_str(opname); free_str(key); } break; case SSH_AGENT_FORWARDING_NOTICE: { STR host; STR ip; unsigned int port; FWDHOP *f; if (c->norm.nfwd > 100) { fprintf(errf,"%s: dropping client: insane fowarding\n",__progname); killclient(c); } parse_data(c->buf+5,c->len-5,&fail, PP_STRING(&host), PP_STRING(&ip), PP_UINT32(&port), PP_ENDSHERE ); f = malloc(sizeof(FWDHOP)); f->host = host; f->ip = ip; f->port = port; f->link = c->norm.fwding; c->norm.fwding = f; c->norm.nfwd ++; } return; break; case SSH_AGENT_DELETE_KEY: { STR key; STR desc; KEY *k; parse_data(c->buf+5,c->len-5,&fail, PP_STRING(&key), PP_STRING(&desc), PP_ENDSHERE ); k = lookup_key(c->s,&key,c->norm.nfwd); if (! k) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_FOUND), T_END ); } else { f = fopen_interactive(c->s); print_fd_fwd(f,c); fprintf(f,": deleted key %u\n",k->serial); fclose(f); remove_key(k); send_client_response(c,SSH_AGENT_SUCCESS,T_END); } free_str(key); free_str(desc); } break; case SSH_AGENT_LOCK: { const char *op; STR pass; op = "lock"; if (0) { case SSH_AGENT_UNLOCK: op = "unlock"; } parse_data(c->buf+5,c->len-5,&fail, PP_STRING(&pass), PP_ENDSHERE ); f = fopen_interactive(c->s); print_fd_fwd(f,c); fprintf(f,": tried to %s\n",op); fclose(f); send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_UNSUPPORTED_OP), T_END ); free_str(pass); } break; case SSH_AGENT_PING: send_client_response(c,SSH_AGENT_ALIVE, T_BLOCK(c->buf+5,c->len-5), T_END ); break; case SSH_AGENT_RANDOM: { unsigned int len; parse_data(c->buf+5,c->len-5,&fail, PP_UINT32(&len), PP_ENDSHERE ); f = fopen_interactive(c->s); print_fd_fwd(f,c); fprintf(f,": tried to random (%u)\n",len); fclose(f); send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_UNSUPPORTED_OP), T_END ); } break; case SSH_AGENT_EXTENSION: { STR ext; const void *restp; int restl; parse_data(c->buf+5,c->len-5,&fail, PP_STRING(&ext), PP_REST(&restp,&restl) ); if (str_equalsC(ext,"client-id@rodents.montreal.qc.ca")) { STR op; parse_data(restp,restl,&fail, PP_UNWIND(&pp_free_str,&ext), PP_STRING(&op), PP_REST(&restp,&restl) ); if (str_equalsC(op,"set")) { STR val; parse_data(restp,restl,&fail, PP_UNWIND(&pp_free_str,&ext), PP_UNWIND(&pp_free_str,&op), PP_STRING(&val), PP_ENDSHERE ); free(c->norm.idtext); c->norm.idtext = malloc(val.len+1); bcopy(val.data,c->norm.idtext,val.len); c->norm.idtext[val.len] = '\0'; free_str(val); send_client_response(c,SSH_AGENT_SUCCESS,T_END); } else { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_UNSUPPORTED_OP), T_END ); } free_str(op); } else { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_UNSUPPORTED_OP), T_END ); } free_str(ext); } break; default: break; } clear_fwd_list(&c->norm.fwding); } static void unlink_pend(PENDOP *op, PENDTC *l) { PENDOP *p; PENDOP **pp; pp = &l->q; while ((p = *pp)) { if (p == op) { *pp = p->link; if (! l->q) { l->qt = &l->q; } else if (l->qt == &p->link) { l->qt = pp; } return; } pp = &p->link; } panic("PENDOP not on list"); } static void drop_tag(AGENTSTATE *s, int tag) { if ((tag < 0) || (tag >= s->ntag)) panic("dropping impossible tag"); if (! s->tagbusy[tag]) panic("dropping non-busy tag"); s->tagbusy[tag] = 0; } #ifdef AF_TIMER static void drop_id(int id __attribute__((__unused__)), void *dv) { DROPID *d; d = dv; close(d->fd); remove_poll_id(d->id); drop_tag(d->s,d->tag); free(d); } static void unuse_tag(AGENTSTATE *s, int tag) { struct itimerval itv; DROPID *did; int fd; fd = socket(AF_TIMER,SOCK_STREAM,0); if (fd < 0) { fprintf(errf,"%s: can't create AF_TIMER socket: %s\n",__progname,strerror(errno)); exit(1); } itv.it_interval.tv_sec = 0; itv.it_interval.tv_usec = 0; itv.it_value.tv_sec = 10; itv.it_value.tv_usec = 0; write(fd,&itv,sizeof(itv)); did = malloc(sizeof(DROPID)); did->s = s; did->tag = tag; did->fd = fd; did->id = add_poll_fd(fd,&rwtest_always,&rwtest_never,&drop_id,0,did); } #else static void unuse_tag(AGENTSTATE *s, int tag) { drop_tag(s,tag); } #endif static void free_pendop(PENDOP *p) { unuse_tag(p->s,p->tag); clear_fwd_list(&p->fwding); free(p); } static void adv(ICTX *cx, int n) { cx->bp += n; cx->len -= n; } static void skipspc(ICTX *cx) { while ((cx->len > 0) && isspace(*cx->bp)) adv(cx,1); } static void skipnonspc(ICTX *cx) { while ((cx->len > 0) && !isspace(*cx->bp)) adv(cx,1); } static KEY *getkeybyno(ICTX *cx, int ends) { unsigned int n; int nl; KEY *k; int o; NESTED char nget(void) { return((o>=cx->len)?'\0':cx->bp[o++]); } skipspc(cx); o = 0; nl = parse_number(&nget,&n); if (nl < 1) { fprintf(cx->o,"Missing/invalid key number\n"); (*cx->throw)(); panic("throw returned"); } adv(cx,nl); skipspc(cx); if (ends && (cx->len > 0)) { fprintf(cx->o,"Junk after key number\n"); (*cx->throw)(); panic("throw returned"); } for (k=cx->c->s->keys;k;k=k->link) { if (k->serial == n) return(k); } fprintf(cx->o,"Key %u not found\n",n); (*cx->throw)(); panic("throw returned"); } static CLIENT *getclientbyno(ICTX *cx, int ends) { unsigned int n; int nl; int o; NESTED char nget(void) { return((o>=cx->len)?'\0':cx->bp[o++]); } skipspc(cx); o = 0; nl = parse_number(&nget,&n); if (nl < 1) { fprintf(cx->o,"Missing/invalid client number\n"); (*cx->throw)(); panic("throw returned"); } adv(cx,nl); skipspc(cx); if (ends && (cx->len > 0)) { fprintf(cx->o,"Junk after client number\n"); (*cx->throw)(); panic("throw returned"); } if ( (n >= cx->c->s->nclients) || (cx->c->s->clients[n]->flags & (CF_DEAD|CF_IDLE))) { fprintf(cx->o,"Key %u not found\n",n); (*cx->throw)(); panic("throw returned"); } return(cx->c->s->clients[n]); } static PENDOP *getconfbyno(ICTX *cx, int ends) { unsigned int n; int nl; int o; PENDOP *p; NESTED char nget(void) { return((o>=cx->len)?'\0':cx->bp[o++]); } skipspc(cx); o = 0; nl = parse_number(&nget,&n); if (nl < 1) { fprintf(cx->o,"Missing/invalid confirmation number\n"); (*cx->throw)(); panic("throw returned"); } adv(cx,nl); skipspc(cx); if (ends && (cx->len > 0)) { fprintf(cx->o,"Junk after confirmation number\n"); (*cx->throw)(); panic("throw returned"); } for (p=cx->c->s->conf.q;p;p=p->link) if (p->tag == n) return(p); fprintf(cx->o,"Confirmation %u not found\n",n); (*cx->throw)(); panic("throw returned"); } static PENDOP *getpendbyno(ICTX *cx, int ends) { unsigned int n; int nl; int o; PENDOP *p; NESTED char nget(void) { return((o>=cx->len)?'\0':cx->bp[o++]); } skipspc(cx); o = 0; nl = parse_number(&nget,&n); if (nl < 1) { fprintf(cx->o,"Missing/invalid pending operation number\n"); (*cx->throw)(); panic("throw returned"); } adv(cx,nl); skipspc(cx); if (ends && (cx->len > 0)) { fprintf(cx->o,"Junk after pending operation number\n"); (*cx->throw)(); panic("throw returned"); } for (p=cx->c->s->conf.q;p;p=p->link) if (p->tag == n) return(p); for (p=cx->c->s->pend.q;p;p=p->link) if (p->tag == n) return(p); p = cx->c->s->inprogress; if (p && (p->tag == n)) return(p); fprintf(cx->o,"Pending operation %u not found\n",n); (*cx->throw)(); panic("throw returned"); } static void cmd_init(ICMDLIST *l) { if (l->maxlen < 0) { int i; ICMD *c; int len; for (i=l->ncmds-1;i>=0;i--) { c = &l->cmds[i]; len = strlen(c->name); if (len > l->maxlen) l->maxlen = len; c->namelen = len; } } } static LKUPSTAT cmd_lookup(const char *name, int len, ICMDLIST *l, ICMD **cmdp) { int found; int count; int j; ICMD *c; cmd_init(l); found = -1; count = 0; for (j=l->ncmds-1;j>=0;j--) { c = &l->cmds[j]; if (len > c->namelen) continue; if (bcmp(name,c->name,len)) continue; found = j; count ++; } if (count < 1) return(LKUP_NOTFOUND); if (count > 1) return(LKUP_AMBIGUOUS); *cmdp = &l->cmds[found]; return(LKUP_FOUND); } static void icmd_question(ICTX *cx, ICMDLIST *cl, const char *cmdpref) { const char *pref; int j; pref = ""; for (j=0;jncmds;j++) { fprintf(cx->o,"%s%s",pref,cl->cmds[j].name); pref = " "; } fprintf(cx->o,"\n(Use \"%shelp\" for more help.)\n",cmdpref); } static void icmd_help(ICTX *cx, ICMDLIST *cl) { int j; unsigned char *cmd; int cmdlen; ICMD *ic; int any; if (cx->len < 1) { for (j=0;jncmds;j++) { fprintf(cx->o,"%s",cl->cmds[j].help); } } else { cmd = cx->bp; skipnonspc(cx); cmdlen = cx->bp - cmd; any = 0; for (j=0;jncmds;j++) { ic = &cl->cmds[j]; if (cmdlen > ic->namelen) continue; if (! bcmp(cmd,ic->name,cmdlen)) { fprintf(cx->o,"%s",ic->help); any = 1; } } if (! any) fprintf(cx->o,"No match.\n"); } } static void cmd_bad(const char *how, ICTX *cx, const unsigned char *cmd, int cmdlen, const char *cmdpref) { fprintf(cx->o,"%s %scommand `%.*s%s'\nUse ? for help.\n", how,cmdpref,(cmdlen>64)?64:cmdlen,cmd,(cmdlen>64)?"...":""); } static void subcommand(ICTX *cx, ICMDLIST *cmds, const char *cmdpref) { unsigned char *cmd; int cmdlen; ICMD *ic; cmd = cx->bp; skipnonspc(cx); cmdlen = cx->bp - cmd; skipspc(cx); switch (cmd_lookup(cmd,cmdlen,cmds,&ic)) { default: panic("bad lookup status"); break; case LKUP_NOTFOUND: cmd_bad("Unrecognized",cx,cmd,cmdlen,cmdpref); break; case LKUP_AMBIGUOUS: cmd_bad("Ambiguous",cx,cmd,cmdlen,cmdpref); break; case LKUP_FOUND: (*ic->handler)(cx); break; } } static void cmd_question(ICMD_ARGS) { icmd_question(cx,&icmds,""); } static void cmd_help(ICMD_ARGS) { icmd_help(cx,&icmds); } static void cmd_keys(ICMD_ARGS) { KEY *k; if (cx->len < 1) { fprintf(cx->o,"Keys:"); if (cx->c->s->keys) { for (k=cx->c->s->keys;k;k=k->link) { fprintf(cx->o," %u",k->serial); } } else { fprintf(cx->o," none"); } fprintf(cx->o,".\n"); } else if (cx->bp[0] == '*') { if (! cx->c->s->keys) { fprintf(cx->o,"No keys.\n"); } else { for (k=cx->c->s->keys;k;k=k->link) { print_key_to(cx->o,k); } } } else { while (cx->len > 0) { k = getkeybyno(cx,0); print_key_to(cx->o,k); } } } static void cmd_clients(ICMD_ARGS) { int x; CLIENT *cl; int any; if (cx->len < 1) { fprintf(cx->o,"Clients:"); any = 0; for (x=cx->c->s->nclients-1;x>=0;x--) { cl = cx->c->s->clients[x]; if ( !(cl->flags & (CF_DEAD|CF_IDLE)) && (cl->proto == AGENT_PROTO_NORMAL) ) { fprintf(cx->o," %u",cl->n); any = 1; } } if (! any) fprintf(cx->o," none"); fprintf(cx->o,".\n"); } else if ((cx->len == 1) && (cx->bp[0] == '*')) { any = 0; for (x=cx->c->s->nclients-1;x>=0;x--) { cl = cx->c->s->clients[x]; if ( !(cl->flags & (CF_DEAD|CF_IDLE)) && (cl->proto == AGENT_PROTO_NORMAL) ) { print_fd_fwd(cx->o,cl); fprintf(cx->o,"\n"); any = 1; } } if (! any) fprintf(cx->o,"No clients.\n"); } else if (isdigit(cx->bp[0])) { while (cx->len > 0) { cl = getclientbyno(cx,0); print_fd_fwd(cx->o,cl); fprintf(cx->o,"\n"); } } else { regex_t r; int e; int el; char *et; int neg; if (cx->bp[0] == '!') { adv(cx,1); neg = 1; } else { neg = 0; } if ((cx->len > 1) && (cx->bp[0] == '"')) adv(cx,1); r.re_endp = (void *) (cx->bp+cx->len); e = regcomp(&r,cx->bp,REG_EXTENDED|REG_NOSUB|REG_PEND); if (e) { el = regerror(e,&r,0,0); et = malloc(el); regerror(e,&r,et,el); fprintf(cx->o,"%.*s: %s\n",cx->len,cx->bp,et); free(et); return; } any = 0; for (x=cx->c->s->nclients-1;x>=0;x--) { cl = cx->c->s->clients[x]; if ( !(cl->flags & (CF_DEAD|CF_IDLE)) && (cl->proto == AGENT_PROTO_NORMAL) && cl->norm.idtext ) { e = regexec(&r,cl->norm.idtext,0,0,0); switch (e) { case 0: case REG_NOMATCH: if (e == (neg ? REG_NOMATCH : 0)) { print_fd_fwd(cx->o,cl); fprintf(cx->o,"\n"); any = 1; } break; default: el = regerror(e,&r,0,0); et = malloc(el); regerror(e,&r,et,el); fprintf(cx->o,"regexec error [%s]: %s\n",cl->norm.idtext,et); free(et); break; } } } if (! any) fprintf(cx->o,"No %smatching clients.\n",neg?"(non-)":""); } } static void cmd_drop(ICMD_ARGS) { KEY *k; k = getkeybyno(cx,1); remove_key(k); fprintf(cx->o,"Dropped.\n"); } static void cmd_kill(ICMD_ARGS) { CLIENT *cl; cl = getclientbyno(cx,1); killclient(cl); fprintf(cx->o,"Killed.\n"); } static void cmd_fp(ICMD_ARGS) { void *hh; unsigned char hr[16]; KEY *k; k = getkeybyno(cx,1); hh = md5_init(); md5_process_bytes(hh,k->pub.data,k->pub.len); md5_result(hh,&hr[0]); fprintf(cx->o,"%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:" "%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", hr[0],hr[1],hr[2],hr[3],hr[4],hr[5],hr[6],hr[7], hr[8],hr[9],hr[10],hr[11],hr[12],hr[13],hr[14],hr[15]); } static void cmd_constraint(ICMD_ARGS) { AGENT_CONSTRAINTS ac; KEY *k; k = getkeybyno(cx,0); ac = k->constraints; if (! set_constraints(&ac,cx->bp,cx->len,cx->o)) { k->constraints = ac; reset_timeout(k); fprintf(cx->o,"Done.\n"); } } static void cmd_confirm(ICMD_ARGS) { PENDOP *p; if (cx->len < 1) { int n; n = 0; for (p=cx->c->s->conf.q;p;p=p->link) { fprintf(cx->o,"tag %d: ",p->tag); print_fd_fwd_list(cx->o,p->c,p->fwding); fprintf(cx->o,", key %u\n",p->k->serial); n ++; } if (! n) { fprintf(cx->o,"No key ops are pending confirmation.\n"); } } else { while (cx->len > 0) { p = getconfbyno(cx,0); fprintf(cx->o,"Operation tag %d confirmed.\n",p->tag); p->state = POS_PENDING; unlink_pend(p,&p->s->conf); append_pend(p,&p->s->pend); } } } static void cmd_reject(ICMD_ARGS) { PENDOP *p; while (cx->len > 0) { p = getconfbyno(cx,0); fprintf(cx->o,"Operation tag %d rejected.\n",p->tag); unlink_pend(p,&p->s->conf); send_client_response(p->c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_DENIED), T_END ); free_pendop(p); } } static ICMDLIST ic_pq; /* forward */ static void cmd_pq_question(ICMD_ARGS) { icmd_question(cx,&ic_pq,"pq "); } static void cmd_pq_help(ICMD_ARGS) { icmd_help(cx,&ic_pq); } static void cmd_pq_list(ICMD_ARGS) { AGENTSTATE *s; PENDOP *p; s = cx->c->s; if (s->inprogress) { print_pendop(cx->o,s->inprogress); fprintf(cx->o," (in progress)\n"); } for (p=s->pend.q;p;p=p->link) { print_pendop(cx->o,p); fprintf(cx->o,"\n"); } for (p=s->conf.q;p;p=p->link) { print_pendop(cx->o,p); fprintf(cx->o," (pending confirmation)\n"); } } static void cmd_pq_head(ICMD_ARGS) { PENDOP *p; while (cx->len > 0) { p = getpendbyno(cx,0); switch (p->state) { default: panic("impossible state"); break; case POS_CONFIRM: fprintf(cx->o,"%d: can't `head' to-be-confirmed operations\n",p->tag); break; case POS_INPROGRESS: fprintf(cx->o,"%d: can't `head' operations already in progress\n",p->tag); break; case POS_PENDING: unlink_pend(p,&p->s->pend); prepend_pend(p,&p->s->pend); fprintf(cx->o,"%d: moved to head of queue\n",p->tag); break; } } } static void cmd_pq_tail(ICMD_ARGS) { PENDOP *p; while (cx->len > 0) { p = getpendbyno(cx,0); switch (p->state) { default: panic("impossible state"); break; case POS_CONFIRM: fprintf(cx->o,"%d: can't `tail' to-be-confirmed operations\n",p->tag); break; case POS_INPROGRESS: fprintf(cx->o,"%d: can't `tail' operations already in progress\n",p->tag); break; case POS_PENDING: unlink_pend(p,&p->s->pend); append_pend(p,&p->s->pend); fprintf(cx->o,"%d: moved to tail of queue\n",p->tag); break; } } } static void cmd_pq_fail(ICMD_ARGS) { PENDOP *p; FILE *f; PENDTC *tc; while (cx->len > 0) { p = getpendbyno(cx,0); switch (p->state) { default: panic("impossible state"); break; case POS_CONFIRM: tc = &p->s->conf; if (0) { case POS_PENDING: tc= &p->s->pend; } fprintf(cx->o,"%d: failed\n",p->tag); f = fopen_interactive(p->s); fprintf(f,"Confirmation tag %u aborted (failed manually)\n",p->tag); fclose(f); if (p->c) send_client_response(p->c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_DENIED), T_END ); unlink_pend(p,tc); free_pendop(p); break; case POS_INPROGRESS: fprintf(cx->o,"%d: failed (but must wait for completion)\n",p->tag); if (p->c) send_client_response(p->c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_DENIED), T_END ); p->c = 0; break; } } } static ICMD ic_pq_v[] = { { "?", &cmd_pq_question, "pq ? Print brief help info\n" }, { "help", &cmd_pq_help, "pq help Print help info\n" "pq help cmd Print help on \"cmd\" (command or prefix thereof)\n" }, { "list", &cmd_pq_list, "pq list List pending operations\n" }, { "head", &cmd_pq_head, "pq head # [# ...]\n" " Move pending operations to the head of the list\n" }, { "tail", &cmd_pq_tail, "pq tail # [# ...]\n" " Move pending operations to the tail of the list\n" }, { "fail", &cmd_pq_fail, "pq fail # [# ...]\n" " Cause pending operations to fail immediately\n" } }; static ICMDLIST ic_pq = ICMDLIST_INIT(ic_pq_v); static void cmd_pq(ICMD_ARGS) { subcommand(cx,&ic_pq,"pq "); } static ICMD icmd_v[] = { { "?", &cmd_question, "? Print brief help info\n" }, { "help", &cmd_help, "help Print help info\n" "help cmd Print help on \"cmd\" (command or prefix thereof)\n" }, { "keys", &cmd_keys, "keys [# [# ...]]\n" "keys *\n" " List agent keys (* = all keys in full)\n" }, { "clients", &cmd_clients, "clients [# [# ...]]\n" "clients [!]\"str\n" "clients *\n" " List agent clients (* = all clients in full)\n" " If the arglist does not begin with * or a digit, it is\n" " taken as a regexp to find in client IDs; a leading \"\n" " is stripped, and will force this interpretation. A !\n" " (unquoted) negates the regular-expression test.\n" }, { "drop", &cmd_drop, "drop # Discard a key\n" }, { "kill", &cmd_kill, "kill # Close a client connection (ungracefully)\n" }, { "fp", &cmd_fp, "fp # Show a key's fingerprint\n" }, { "constraint", &cmd_constraint, "constraint # constraint,constraint,...\n" " Change a key's timeout, use limit, etc\n" }, { "confirm", &cmd_confirm, "confirm List key ops pending confirmation\n" "confirm # [# ...]\n" " Allow confirmation-pending operations\n" }, { "reject", &cmd_reject, "reject # [# ...]\n" " Refuse confirmation-pending operations\n" }, { "pq", &cmd_pq, "pq ... Manipulate the queue of pending operations (`pq ?' for more)\n" } }; static ICMDLIST icmds = ICMDLIST_INIT(icmd_v); static void process_client_line(CLIENT *c) { __label__ catch; ICTX cx; NESTED void throw(void) { goto catch; } if (c->len < 4) panic("too small"); cx.bp = c->buf + 4; cx.len = c->len - 4; cx.o = fopen_client_response(c,1); cx.c = c; cx.throw = &throw; verb(IAGENT,"process_client_line fd %d: %d %.*s\n",c->fd,cx.len,cx.len,cx.bp); skipspc(&cx); subcommand(&cx,&icmds,""); catch:; fclose(cx.o); } static int rtest_client(int id __attribute__((__unused__)), void *arg) { CLIENT *c; c = arg; return(!(c->flags & (CF_DEAD|CF_IDLE))); } static int wtest_client(int id __attribute__((__unused__)), void *arg) { CLIENT *c; c = arg; return( !(c->flags & (CF_DEAD|CF_IDLE)) && oq_nonempty(&((CLIENT *)arg)->oq) ); } static void w_client(int id __attribute__((__unused__)), void *arg) { CLIENT *c; int w; c = arg; if (c->flags & (CF_DEAD|CF_IDLE)) return; w = oq_writev(&c->oq,c->fd,&oq_canthappen); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(errf,"%s: agent client write: %s\n",__progname,strerror(errno)); killclient(c); return; } if (w == 0) { fprintf(errf,"%s: agent client zero write\n",__progname); killclient(c); return; } oq_dropdata(&c->oq,w); } static void r_client(int id __attribute__((__unused__)), void *arg) { CLIENT *c; int n; int r; c = arg; if (c->flags & (CF_DEAD|CF_IDLE)) return; n = (c->fill < 4) ? 4 : c->len; n -= c->fill; r = read(c->fd,c->buf+c->fill,n); if (r < 0) { int e; e = errno; switch (e) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(errf,"%s: agent client read: %s\n",__progname,strerror(e)); killclient(c); return; } if (r == 0) { if (c->fill != 0) fprintf(errf,"%s: agent client mid-message EOF\n",__progname); killclient(c); return; } c->fill += r; if (r >= n) { if (c->fill == 4) { unsigned int l; l = get_uint32(c->buf); if ((l < 1) || (l > 32768)) { fprintf(errf,"%s: unreasonable agent client packet size %u\n",__progname,l); killclient(c); return; } c->len = l + 4; if (c->len > c->alloc) c->buf = realloc(c->buf,(c->alloc=c->len)); } else { c->fill = 0; switch (c->proto) { default: panic("bad proto"); break; case AGENT_PROTO_NORMAL: process_client_packet(c); break; case AGENT_PROTO_INTERACTIVE: process_client_line(c); break; } } } } static void new_agent_fd(int fd, void *sv, AGENT_PROTO proto) { AGENTSTATE *s; CLIENT *c; int i; s = sv; do <"foundslot"> { for (i=0;inclients;i++) { c = s->clients[i]; if (c->flags & CF_IDLE) break <"foundslot">; } c = malloc(sizeof(CLIENT)); s->clients = realloc(s->clients,sizeof(*s->clients)*++s->nclients); s->clients[i] = c; c->s = s; c->n = i; } while (0); c->proto = proto; c->fd = fd; c->fill = 0; c->len = 0; c->alloc = 32; c->buf = malloc(c->alloc); c->flags = 0; oq_init(&c->oq); c->id = add_poll_fd(fd,&rtest_client,&wtest_client,&r_client,&w_client,c); switch (proto) { default: panic("bad proto"); break; case AGENT_PROTO_NORMAL: notify_interactive(s,"client %d, FD %d: new\n",c->n,c->fd); c->norm.fwding = 0; c->norm.nfwd = 0; c->norm.idtext = 0; break; case AGENT_PROTO_INTERACTIVE: verb(IAGENT,"New interactive agent, fd %d\n",c->fd); if (s->conf.q) { FILE *f; f = fopen_client_response(c,0); fprintf(f,"Note: confirmation request(s) pending\n"); fclose(f); } break; } } static void handle_sigchld(int sig __attribute__((__unused__))) { close(global_agentstate->waitpipe[1]); global_agentstate->waitpipe[1] = -1; } static void setup_sigchld(AGENTSTATE *s) { struct sigaction sa; sa.sa_handler = &handle_sigchld; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; global_agentstate = s; sigaction(SIGCHLD,&sa,0); } static void do_wait(int id __attribute__((__unused__)), void *sv) { AGENTSTATE *s; pid_t kid; sigset_t mask; sigset_t omask; sigemptyset(&mask); sigaddset(&mask,SIGCHLD); sigprocmask(SIG_BLOCK,&mask,&omask); s = sv; while (1) { kid = wait4(-1,0,WNOHANG,0); if (kid <= 0) break; if (kid == s->cmdkid) { agent_listener_abort(s->listener); exit(0); } } remove_poll_id(s->waitid); close(s->waitpipe[0]); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&s->waitpipe[0]) < 0) { fprintf(errf,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } s->waitid = add_poll_fd(s->waitpipe[0],&rwtest_always,&rwtest_never,&do_wait,0,s); sigprocmask(SIG_SETMASK,&omask,0); } static void ct_destroy(CHALLTEMP *ct) { DLL_UNLINK(ct,ct->l->cts); free(ct); } static int ct_resp_ok(CHALLTEMP *ct) { void *t; int i; unsigned char h[20]; t = sha1_init(); for (i=0;i<16;i++) { sha1_process_bytes(t,&ct->l->cookie[0],sizeof(ct->l->cookie)); sha1_process_bytes(t,&ct->challenge[0],16); } sha1_result(t,&h[0]); xor_block(&h[0],&h[10],&h[0],10); return(!bcmp(&h[0],&ct->crespbuf[0],10)); } static void shutdown_listener(LISTENER *l) { if (l->id != PL_NOID) { remove_poll_id(l->id); l->id = PL_NOID; } if (l->fd >= 0) { close(l->fd); l->fd = -1; } if (l->socketpath) { unlink(l->socketpath); free(l->socketpath); l->socketpath = 0; } } static void rd_ct(int id __attribute__((__unused__)), void *ctv) { CHALLTEMP *ct; int r; ct = ctv; r = read(ct->fd,&ct->crespbuf[ct->crespfill],10-ct->crespfill); if (r < 0) { switch (errno) { case EWOULDBLOCK: case EINTR: return; break; } fprintf(errf,"%s: agent-server rd_ct: read: %s\n",__progname,strerror(errno)); } if ((r <= 0) || (ct->l->flags & LF_SHUTDOWN)) { remove_poll_id(ct->id); close(ct->fd); ct_destroy(ct); return; } ct->crespfill += r; if (ct->crespfill < 10) return; remove_poll_id(ct->id); if (! ct_resp_ok(ct)) { close(ct->fd); ct_destroy(ct); return; } (*ct->l->newfd)(ct->fd,ct->l->arg,ct->proto); if (ct->l->flags & LF_JUSTONCE) { shutdown_listener(ct->l); ct->l->flags |= LF_SHUTDOWN; } ct_destroy(ct); } static void agent_accept(int id __attribute__((__unused__)), void *arg) { LISTENER *l; struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[CMSPACE(sizeof(int))]; unsigned char connmsg[2]; int connmsglen; CHALLTEMP *ct; int fd; l = arg; iov.iov_base = &connmsg[0]; iov.iov_len = sizeof(connmsg); mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = (void *)&ctlbuf[0]; mh.msg_controllen = CMSPACE(sizeof(int)); mh.msg_flags = 0; connmsglen = recvmsg(l->fd,&mh,0); if (connmsglen < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } fprintf(errf,"%s: agent server recvmsg: %s\n",__progname,strerror(errno)); return; } if (connmsglen < 2) { connmsg[0] = AGENT_PROTO_NORMAL; connmsglen = 2; } if (mh.msg_controllen < sizeof(struct cmsghdr)) { fprintf(errf,"%s: control length (%d) too small\n",__progname,(int)mh.msg_controllen); return; } bcopy(&ctlbuf[0],&cmh,sizeof(struct cmsghdr)); if ((cmh.cmsg_level != SOL_SOCKET) || (cmh.cmsg_type != SCM_RIGHTS)) { fprintf(errf,"%s: level/type wrong\n",__progname); return; } if (cmh.cmsg_len < CMLEN(sizeof(int))) { fprintf(errf,"%s: cmsg_len too short (%d)\n",__progname,(int)cmh.cmsg_len); return; } bcopy(&ctlbuf[CMSKIP(&cmh)],&fd,sizeof(int)); switch (connmsg[0]) { case AGENT_PROTO_NORMAL: case AGENT_PROTO_INTERACTIVE: break; default: fprintf(errf,"%s: bad agent protocol ID %d\n",__progname,connmsg[0]); close(fd); return; } fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); fcntl(fd,F_SETFD,1); ct = malloc(sizeof(CHALLTEMP)); DLL_LINK_HEAD(ct,l->cts); ct->l = l; ct->fd = fd; random_data(&ct->challenge[0],16); write(fd,&ct->challenge[0],16); ct->crespfill = 0; ct->id = add_poll_fd(fd,&rwtest_always,&rwtest_never,&rd_ct,0,ct); ct->proto = connmsg[0]; } void *agent_listener(const char *path, void (*newfd)(int, void *, AGENT_PROTO), void *arg, char **env, unsigned int flags) { int fd; struct sockaddr_un *s_un; int slen; LISTENER *l; char *txt; FILE *f; FILE *w; int e; int i; if (VERB(AGENTF)) verb(AGENTF,"agent_listener %s -> ",path); fd = socket(AF_LOCAL,SOCK_DGRAM,0); if (fd < 0) { e = errno; if (VERB(AGENTF)) verb(AGENTF,"socket error %d [%s]\n",e,strerror(e)); fprintf(errf,"%s: socket(AF_LOCAL,SOCK_DGRAM): %s\n",__progname,strerror(e)); exit(1); } fcntl(fd,F_SETFD,1); if (unlink(path) < 0) { if (errno != ENOENT) { if (flags & ALF_MUSTWORK) { e = errno; if (VERB(AGENTF)) verb(AGENTF,"unlink error %d [%s]\n",e,strerror(e)); fprintf(errf,"%s: can't unlink %s: %s\n",__progname,path,strerror(e)); exit(1); } close(fd); return(0); } } slen = sizeof(*s_un) - sizeof(s_un->sun_path) + strlen(path) + 1; s_un = malloc(slen); s_un->sun_len = slen; s_un->sun_family = AF_LOCAL; strcpy(&s_un->sun_path[0],path); if (bind(fd,(void *)s_un,slen) < 0) { e = errno; if (VERB(AGENTF)) verb(AGENTF,"bind error %d [%s]\n",e,strerror(e)); if (flags & ALF_MUSTWORK) { fprintf(errf,"%s: can't bind to %s: %s\n",__progname,path,strerror(e)); exit(1); } free(s_un); close(fd); return(0); } free(s_un); l = malloc(sizeof(LISTENER)); l->fd = fd; l->id = add_poll_fd(fd,&rwtest_always,&rwtest_never,&agent_accept,0,l); random_data(&l->cookie[0],sizeof(l->cookie)); l->socketpath = strdup(path); l->newfd = newfd; l->arg = arg; l->cts = 0; l->flags = (flags & ALF_JUSTONCE) ? LF_JUSTONCE : 0; f = fopen_alloc(&txt,0); w = b64w_wrap(f); putc(1,w); putc(sizeof(l->cookie),w); fwrite(&l->cookie[0],1,sizeof(l->cookie),w); fprintf(w,"%s",path); fclose(w); putc(0,f); fclose(f); *env = txt; if (VERB(AGENTF)) { f = verb_fopen(VERBOSE_AGENTF); fprintf(f,"%p [cookie",(void *)l); for (i=0;icookie);i++) fprintf(f," %02x",l->cookie[i]); fprintf(f,"]\n"); fclose(f); } return(l); } void agent_listener_abort(void *lv) { LISTENER *l; CHALLTEMP *ct; l = lv; if (VERB(AGENTF)) { FILE *f; int i; f = verb_fopen(VERBOSE_AGENTF); fprintf(f,"aborting listener %p (%s, cookie ",lv,l->socketpath); for (i=0;icookie);i++) fprintf(f," %02x",l->cookie[i]); fprintf(f,")\n"); fclose(f); } while ((ct = l->cts)) { remove_poll_id(ct->id); close(ct->fd); ct_destroy(ct); } shutdown_listener(l); free(l); } static void op_kid(int fd) { char hdr[sizeof(HKALG *)+(2*sizeof(((STR *)0)->len))]; int n; HKALG *alg; char *pubbuf; int puball; __typeof__(((STR *)0)->len) publen; char *privbuf; int privall; __typeof__(((STR *)0)->len) privlen; char *databuf; int dataall; int datalen; struct iovec iov[3]; struct msghdr mh; STR sig; char oprv; pubbuf = 0; puball = 0; privbuf = 0; privall = 0; databuf = 0; dataall = 0; while (1) { n = recv(fd,&hdr[0],sizeof(hdr),MSG_WAITALL); if (n < sizeof(hdr)) exit(0); bcopy(&hdr[0],&alg,sizeof(alg)); bcopy(&hdr[sizeof(HKALG *)],&publen,sizeof(publen)); bcopy(&hdr[sizeof(HKALG *)+sizeof(publen)],&privlen,sizeof(privlen)); datalen = (*alg->prehash)()->hashlen; if (publen > puball) { free(pubbuf); pubbuf = malloc(puball=publen); } if (privlen > privall) { free(privbuf); privbuf = malloc(privall=privlen); } if (datalen > dataall) { free(databuf); databuf = malloc(dataall=datalen); } iov[0].iov_base = pubbuf; iov[0].iov_len = publen; iov[1].iov_base = privbuf; iov[1].iov_len = privlen; iov[2].iov_base = databuf; iov[2].iov_len = datalen; mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov[0]; mh.msg_iovlen = 3; mh.msg_control = 0; mh.msg_controllen = 0; mh.msg_flags = 0; n = recvmsg(fd,&mh,MSG_WAITALL); if (n < publen+privlen+datalen) exit(0); if ((*alg->sign)( &sig, pubbuf, publen, privbuf, privlen, databuf )) { oprv = OPRV_OK; iov[0].iov_base = &oprv; iov[0].iov_len = 1; iov[1].iov_base = &sig.len; iov[1].iov_len = sizeof(sig.len); iov[2].iov_base = sig.data; iov[2].iov_len = sig.len; writev(fd,&iov[0],3); free_str(sig); bzero(pubbuf,publen); bzero(privbuf,privlen); bzero(databuf,datalen); } else { oprv = OPRV_FAIL; write(fd,&oprv,1); } } } static void ipdone(AGENTSTATE *s) { remove_poll_id(s->ipid); free_pendop(s->inprogress); s->inprogress = 0; s->ipid = PL_NOID; } static void pend_done(int id __attribute__((__unused__)), void *sv) { AGENTSTATE *s; char oprv; FILE *f; NESTED void getn(void *to, int n, const char *what) { int r; r = recv(s->oppipe,to,n,MSG_WAITALL); if (r == n) return; if (r < 0) { fprintf(errf,"%s: agent can't get %s from worker: %s\n",__progname,what,strerror(errno)); } else if (r == 0) { fprintf(errf,"%s: agent can't get %s from worker (EOF)\n",__progname,what); } else { fprintf(errf,"%s: agent can't get %s from worker (wanted %d, read %d)\n",__progname,what,n,r); } exit(1); } s = sv; getn(&oprv,1,"status"); switch (oprv) { case OPRV_FAIL: verb(AWORK,"pend_done %p failed\n",(void *)s->inprogress); f = fopen_interactive(s); print_fd_fwd_list(f,s->inprogress->c,s->inprogress->fwding); if (s->inprogress->k) { fprintf(f,": PK op key %u (failed)\n",s->inprogress->k->serial); } else { fprintf(f,": PK op key [now deleted] (failed)\n"); } fclose(f); if (s->inprogress->c) { send_client_response(s->inprogress->c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_FAILURE), T_END ); } ipdone(s); break; case OPRV_OK: { STR sig; f = fopen_interactive(s); print_fd_fwd_list(f,s->inprogress->c,s->inprogress->fwding); if (s->inprogress->k) { fprintf(f,": PK op key %u (done)\n",s->inprogress->k->serial); } else { fprintf(f,": PK op key [now deleted] (done)\n"); } fclose(f); getn(&sig.len,sizeof(sig.len),"sig length"); verb(AWORK,"pend_done %p ok len %d\n",(void *)s->inprogress,sig.len); sig.data = malloc(sig.len); getn(sig.data,sig.len,"sig data"); if (s->inprogress->c) { send_client_response(s->inprogress->c,SSH_AGENT_OPERATION_COMPLETE, T_STR(&sig), T_END ); } free_str(sig); ipdone(s); } break; default: fprintf(errf,"%s: impossible status %d from worker\n",__progname,oprv); exit(1); } } /* * Check to see if we can dispatch a pending op to the worker process. * If so, send it. Since the op process is a forked copy of * ourselves, and all algs are compile-time constants, we can pass the * alg by simply sending its pointer, and the receiver can trust it * because the channel to us cannot lead to an untrusted process. * * We don't need to send ha->hashlen because the other end can get it * the same way we do once it has ha. */ static int pendcheck(void *sv) { AGENTSTATE *s; HKALG *ha; PENDOP *p; PENDOP **pp; struct iovec iov[6]; FILE *f; int n; s = sv; pp = &s->conf.q; while ((p = *pp)) { if (p->k && p->c) { pp = &p->link; continue; } f = fopen_interactive(s); fprintf(f,"Confirmation tag %u aborted (%s%s%s vanished)\n",p->tag, p->c ? "key" : "", (p->c||p->k) ? "" : " and ", p->k ? "client" : "" ); fclose(f); if (p->c) send_client_response(p->c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_FOUND), T_END ); *pp = p->link; if (! s->conf.q) s->conf.qt = &s->conf.q; free_pendop(p); return(BLOCK_LOOP); } if (s->inprogress || !s->pend.q) return(BLOCK_NIL); p = s->pend.q; if (! (s->pend.q=p->link)) s->pend.qt = &s->pend.q; verb(AWORK,"pendcheck doing %p, k=%p c=%p\n",(void *)p,(void *)p->k,(void *)p->c); if (p->k && p->c) { f = fopen_interactive(s); print_fd_fwd_list(f,p->c,p->fwding); fprintf(f,": PK op key %u",p->k->serial); ha = p->k->alg; iov[0].iov_base = &ha; iov[0].iov_len = sizeof(HKALG *); iov[1].iov_base = &p->k->pub.len; iov[1].iov_len = sizeof(p->k->pub.len); iov[2].iov_base = &p->k->priv.len; iov[2].iov_len = sizeof(p->k->priv.len); iov[3].iov_base = p->k->pub.data; iov[3].iov_len = p->k->pub.len; iov[4].iov_base = p->k->priv.data; iov[4].iov_len = p->k->priv.len; iov[5].iov_base = p->data; iov[5].iov_len = (*ha->prehash)()->hashlen; verb(AWORK,"pendcheck alg %p, pub %d priv %d\n",(void *)ha,p->k->pub.len,p->k->priv.len); n = writev(s->oppipe,&iov[0],6); if (n < 0) { n = errno; verb(AWORK,"pendcheck writev error: %s\n",strerror(n)); fprintf(f,": failed (writev: %s)",strerror(n)); send_client_response(p->c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_FAILURE), T_END ); free_pendop(p); } else if (n != iov[0].iov_len+iov[1].iov_len+iov[2].iov_len+iov[3].iov_len+iov[4].iov_len+iov[5].iov_len) { int w; w = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len + iov[3].iov_len + iov[4].iov_len + iov[5].iov_len; verb(AWORK,"pendcheck wrote %d, wanted %d\n",n,w); fprintf(f,": failed (writev wanted %d, wrote %d)",w,n); send_client_response(p->c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_FAILURE), T_END ); free_pendop(p); } else { verb(AWORK,"pendcheck wrote %d\n",n); fprintf(f," (started)"); p->state = POS_INPROGRESS; s->inprogress = p; s->ipid = add_poll_fd(s->oppipe,&rwtest_always,&rwtest_never,&pend_done,0,s); use_key(p->k,p->c); } fprintf(f,"\n"); fclose(f); } else { f = fopen_interactive(s); fprintf(f,"Pending tag %u aborted (%s%s%s vanished)\n",p->tag, p->c ? "key" : "", (p->c||p->k) ? "" : " and ", p->k ? "client" : "" ); fclose(f); free_pendop(p); } return(BLOCK_LOOP); } static void pendtc_init(PENDTC *tc) { tc->q = 0; tc->qt = &tc->q; } void agent_setup(void) { char *dir; char *path; AGENTSTATE *s; pid_t pid; char *env; int opp[2]; s = malloc(sizeof(AGENTSTATE)); s->clients = 0; s->nclients = 0; s->keys = 0; dir = agent_dir(); if (! dir) exit(1); asprintf(&path,"%s/agent-%d",dir,(int)getpid()); free(dir); fflush(0); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&opp[0]) < 0) { fprintf(errf,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } pid = moussh_fork(); if (pid < 0) { fprintf(errf,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (pid == 0) { close(opp[0]); op_kid(opp[1]); exit(0); } s->opkid = pid; close(opp[1]); s->oppipe = opp[0]; s->listener = agent_listener(path,&new_agent_fd,s,&env,ALF_MUSTWORK); fflush(0); pid = moussh_fork(); if (pid < 0) { fprintf(errf,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (pid == 0) { setenv("SSH_AGENT",env,1); if (! argsbegin) { const char *sh; sh = getenv("SHELL"); if (! sh) sh = "/bin/sh"; execl(sh,sh,(char *)0); fprintf(errf,"%s: %s: %s\n",__progname,sh,strerror(errno)); } else { execv(argsbegin[0],argsbegin); fprintf(errf,"%s: %s: %s\n",__progname,argsbegin[0],strerror(errno)); } exit(1); } s->cmdkid = pid; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&s->waitpipe[0]) < 0) { fprintf(errf,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } setup_sigchld(s); s->waitid = add_poll_fd(s->waitpipe[0],&rwtest_always,&rwtest_never,&do_wait,0,s); pendtc_init(&s->conf); pendtc_init(&s->pend); s->tagbusy = 0; s->ntag = 0; s->inprogress = 0; s->pendid = add_block_fn(&pendcheck,s); s->ipid = PL_NOID; } char *agent_dir(void) { int id; int serial; const char *confdir; char pathbuf[64]; int pl; NESTED int try(const char *path, const char *(*check)(const struct stat *)) { struct stat stb; const char *msg; if (stat(path,&stb) < 0) { if (errno == ENOENT) { if (mkdir(path,0700) < 0) { if (errno == EEXIST) return(0); logmsg(LM_WARN,"mkdir %s: %s",path,strerror(errno)); return(0); } return(1); } logmsg(LM_WARN,"stat %s: %s",&pathbuf[0],strerror(errno)); return(0); } msg = (*check)(&stb); if (! msg) return(1); logmsg(LM_WARN,"%s: %s",path,msg); return(0); } NESTED const char *check_isdir(const struct stat *stb) { if ((stb->st_mode & S_IFMT) == S_IFDIR) return(0); return(strerror(ENOTDIR)); } NESTED const char *check_owneddir(const struct stat *stb) { if ((stb->st_mode & S_IFMT) != S_IFDIR) return(strerror(ENOTDIR)); if (stb->st_uid != id) return("Owner wrong"); return(0); } id = getuid(); if (config_isset("agent-dir")) { confdir = config_str("agent-dir"); if (try(confdir,&check_isdir)) return(strdup(confdir)); return(0); } pl = sprintf(&pathbuf[0],"/tmp/.ssh-agent-%d",id); serial = 0; while (1) { if (try(&pathbuf[0],&check_owneddir)) return(strdup(&pathbuf[0])); if (serial >= 52) return(0); sprintf(&pathbuf[pl],"-%c",letters[serial]); } }