#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 "b64.h" #include "msgs.h" #include "cmdline.h" #include "pollloop.h" #include "pkt-util.h" #include "alg-util.h" #include "stdio-util.h" #include "agent-server.h" /* * There is a potential problem here. If we expect one fd, say, then * we would normally size ctlbuf to hold a struct cmsghdr and one int. * But if we do that simplictically, we're vulnerable to someone * sending us more fds, in which case we get the fds in our open file * table but aren't told their fd numbers. While we do hear about * this (MSG_CTRUNC), we can't do much to deal with it. * * We could just enlarge ctlbuf and hope it doesn't get overrun. But * that's not only gross but fragile. So instead, we have two other * processes around: * * agent * forker * accepter * * There is a socketpair between the agent and the forker, which is * inherited by the accepter. When the accepter gets a message with * no MSG_CTRUNC (ie, a well-behaved client), it passes the fd up to * the main agent over the socketpair. If it gets a MSG_CTRUNC * (indicating it has, or might have, stray fds), it exits (thereby * closing any stray fds) and the forker forks another one. The * forker never itself does a recvmsg() and thus never gets stray fds, * and because the forker is forked early in startup by the agent, it * is small even if the running agent is big. And the real agent * knows that every message-with-fd it gets will have been sanitized. * * There is a private socketpair between the agent and the forker, and * another between the forker and accepter, in each case so that one * process can tell when another dies. The agent<->forker pipe is * also listened to by the accepter, because it's easier. The forker * and accepter block all signals they can. The agent<->acceptor pipe * ("data pipe" below) is a DGRAM socketpair, since it's clearer to * pass fds with types that have message boundaries; the others * ("death pipe"s below) are STREAM socketpairs. */ typedef struct client CLIENT; typedef struct key KEY; typedef struct fwdhop FWDHOP; /* State struct for agent process. */ typedef struct agentstate AGENTSTATE; struct agentstate { int accfd; /* agent<->acceptor data pipe */ int forkerdeath; /* agent<->(forker|accepter) death pipe */ CLIENT **clients; int nclients; KEY *keys; unsigned char cookie[16]; int waitpipe[2]; int waitid; pid_t cmdkid; char *socketpath; } ; /* State struct for forker and accepter processes. */ typedef struct forkaccstate FORKACCSTATE; struct forkaccstate { int acceptfd; /* socket bound to advertised path */ int fork_acc_death; /* forker<->accepter death pipe */ int agentdeath; /* agent<->(forker|accepter) death pipe */ int agentpipe; /* agent<->accepter data pipe */ } ; struct client { AGENTSTATE *s; int n; int fd; int fill; int len; unsigned char *buf; int alloc; unsigned int flags; #define CF_DEAD 0x00000001 #define CF_INIT 0x00000002 OQ oq; int id; FWDHOP *fwding; int nfwd; } ; struct key { KEY *link; AGENTSTATE *s; HKALG *alg; STR pub; STR priv; STR desc; unsigned int timeout; int time_fd; int time_id; unsigned int fwdlimit; unsigned int uselimit; #define LIMIT_INFINITY 0xffffffff } ; struct fwdhop { FWDHOP *link; STR host; STR ip; unsigned int port; } ; static AGENTSTATE *global_agentstate; 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_fwding(CLIENT *c) { FWDHOP *f; while (c->fwding) { f = c->fwding; c->fwding= f->link; free_str(f->host); free_str(f->ip); free(f); } c->nfwd = 0; } static void killclient(CLIENT *c) { 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); clear_fwding(c); } 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 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_STRING(s) T__STRING,scr_to_str_p(s) #define T__STRING 3 #define T_CALL(f) T__CALL,scr_to_callfn_p(f) #define T__CALL 4 #define T_BLOCK(p,n) T__BLOCK,scr_to_void_p(p),scr_to_int(n) #define T__BLOCK 5 { 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) abort(); put_uint32(buf,len-4); oq_queue_free(&c->oq,buf,len); return; break; case T__UINT32: fput_uint32(f,va_arg(ap,unsigned int)); break; case T__STRING: { STR *sp; sp = va_arg(ap,STR *); fput_string(f,sp->data,sp->len); } break; case T__CALL: { void (*fn)(FILE *); fn = va_arg(ap,__typeof__(fn)); (*fn)(f); } break; default: abort(); break; } } va_end(ap); } static KEY *build_key(STR pub, STR priv, STR desc) { KEY *k; HKALG *alg; int i; for (i=0;(alg=hkalg_enum(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->timeout = 0; k->time_fd = -1; k->fwdlimit = LIMIT_INFINITY; k->uselimit = LIMIT_INFINITY; 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->timeout = v.i; break; case SSH_AGENT_CONSTRAINT_USE_LIMIT: k->uselimit = v.i; break; case SSH_AGENT_CONSTRAINT_FORWARDING_STEPS: k->fwdlimit = 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 */ return(SSH_AGENT_ERROR_UNSUPPORTED_OP); } else { return(SSH_AGENT_ERROR_UNSUPPORTED_OP); } } } static void drop_key(KEY *k) { 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; } } abort(); } static void time_key_out(int id __attribute__((__unused__)), void *kv) { remove_key(kv); } static void save_key(AGENTSTATE *s, KEY *k) { k->s = s; k->link = s->keys; s->keys = k; if (k->timeout) { int s; struct itimerval itv; s = socket(AF_TIMER,SOCK_STREAM,0); if (s < 0) { fprintf(stderr,"%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->timeout; 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); } } static KEY *lookup_key(AGENTSTATE *s, const STR *pub) { KEY *k; for (k=s->keys;k;k=k->link) if (str_equalss(*pub,k->pub)) break; return(k); } static void process_client_packet(CLIENT *c) { __label__ failure; static void fail(const void *at __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(stderr,"%s: client protocol error: can't parse packet: ",__progname); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fprintf(stderr,"\n"); goto failure; } if (c->len < 4) abort(); if (c->len < 5) { fprintf(stderr,"%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. */ send_client_response(c,SSH_AGENT_VERSION_RESPONSE, T_UINT32(2), 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: if (k->uselimit < 1) { drop_key(k); break; } save_key(c->s,k); 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->nfwd) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_DENIED), T_END ); } else { while (c->s->keys) remove_key(c->s->keys); send_client_response(c,SSH_AGENT_SUCCESS,T_END); } break; case SSH_AGENT_LIST_KEYS: { static 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); } } 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); if (! k) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_FOUND), T_END ); } else if ((k->fwdlimit != LIMIT_INFINITY) && (c->nfwd > k->fwdlimit)) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_DENIED), T_END ); } else if (str_equalsC(opname,"sign")) { STR data; STR sig; HASHALG *ha; parse_data(restp,restl,&fail, PP_BACKOUT(opname.data), PP_BACKOUT(key.data), 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 if ((*k->alg->sign)( &sig, k->pub.data, k->pub.len, k->priv.data, k->priv.len, data.data)) { send_client_response(c,SSH_AGENT_OPERATION_COMPLETE, T_STRING(&sig), T_END ); free_str(sig); } else { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_FAILURE), T_END ); } if (k->uselimit != LIMIT_INFINITY) { if (k->uselimit < 2) { remove_key(k); } else { k->uselimit --; } } } free_str(data); } else if (str_equalsC(opname,"hash-and-sign")) { STR data; STR sig; HASHALG *ha; void *hav; char *hash; parse_data(restp,restl,&fail, PP_BACKOUT(opname.data), PP_BACKOUT(key.data), 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); if ((*k->alg->sign)( &sig, k->pub.data, k->pub.len, k->priv.data, k->priv.len, hash)) { send_client_response(c,SSH_AGENT_OPERATION_COMPLETE, T_STRING(&sig), T_END ); free_str(sig); } else { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_FAILURE), T_END ); } free(hash); if (k->uselimit != LIMIT_INFINITY) { if (k->uselimit < 2) { remove_key(k); } else { k->uselimit --; } } } 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->nfwd > 100) { fprintf(stderr,"%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->fwding; c->fwding = f; c->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); if (! k) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_FOUND), T_END ); } else if (! str_equalss(desc,k->desc)) { send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_KEY_NOT_FOUND), T_END ); } else { remove_key(k); send_client_response(c,SSH_AGENT_SUCCESS,T_END); } free_str(key); free_str(desc); } break; case SSH_AGENT_LOCK: case SSH_AGENT_UNLOCK: { STR pass; parse_data(c->buf+5,c->len-5,&fail, PP_STRING(&pass), PP_ENDSHERE ); 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 ); send_client_response(c,SSH_AGENT_FAILURE, T_UINT32(SSH_AGENT_ERROR_UNSUPPORTED_OP), T_END ); } default: break; } clear_fwding(c); } static int rtest_client(int id __attribute__((__unused__)), void *arg) { CLIENT *c; c = arg; return(!(c->flags & CF_DEAD)); } static int wtest_client(int id __attribute__((__unused__)), void *arg) { CLIENT *c; c = arg; return( !(c->flags & CF_DEAD) && 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) return; w = oq_writev(&c->oq,c->fd,&oq_canthappen); if (w < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: agent client write: %s\n",__progname,strerror(errno)); killclient(c); return; } if (w == 0) { fprintf(stderr,"%s: agent client zero write\n",__progname); killclient(c); return; } oq_dropdata(&c->oq,w); } static void r_client_norm(int id __attribute__((__unused__)), void *arg) { CLIENT *c; int n; int r; c = arg; if (c->flags & CF_DEAD) return; n = (c->fill < 4) ? 4 : c->len; n -= c->fill; r = read(c->fd,c->buf+c->fill,n); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: agent client read: %s\n",__progname,strerror(errno)); killclient(c); return; } if (r == 0) { if (c->fill != 0) fprintf(stderr,"%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 > 32768) { fprintf(stderr,"%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; process_client_packet(c); } } } static void r_client_init(int id, void *arg) { CLIENT *c; int n; int r; c = arg; if (c->flags & CF_DEAD) return; n = c->alloc - c->fill; r = read(c->fd,c->buf+c->fill,n); if (r < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: agent client read: %s\n",__progname,strerror(errno)); killclient(c); return; } if (r == 0) { if (c->fill != 0) fprintf(stderr,"%s: agent client mid-message EOF\n",__progname); killclient(c); return; } c->fill += r; if (r >= n) { void *t; int i; unsigned char h[20]; t = sha1_init(); for (i=0;i<16;i++) { sha1_process_bytes(t,&c->s->cookie[0],sizeof(c->s->cookie)); sha1_process_bytes(t,c->buf,16); } sha1_result(t,&h[0]); xor_block(&h[0],&h[10],&h[0],10); if (bcmp(&h[0],c->buf+16,10)) { killclient(c); return; } c->fill = 0; remove_poll_id(id); c->id = add_poll_fd(c->fd,&rtest_client,&wtest_client,&r_client_norm,&w_client,c); } } static void new_agent_fd(AGENTSTATE *s, int newfd) { CLIENT *c; int i; do <"foundslot"> { for (i=0;inclients;i++) { c = s->clients[i]; if (c->flags & CF_DEAD) break <"foundslot">; } c = malloc(sizeof(CLIENT)); s->clients = realloc(s->clients,sizeof(*s->clients)*++s->nclients); s->clients[i] = c; } while (0); c->s = s; c->n = i; c->fd = newfd; c->fill = 16; c->len = 0; c->alloc = 16 + 10; c->buf = malloc(c->alloc); c->flags = CF_INIT; oq_init(&c->oq); c->id = add_poll_fd(c->fd,&rtest_client,&wtest_client,&r_client_init,&w_client,c); c->fwding = 0; c->nfwd = 0; s->clients[i] = c; fcntl(c->fd,F_SETFL,fcntl(c->fd,F_GETFL,0)|O_NONBLOCK); random_data(c->buf,16); oq_queue_point(&c->oq,c->buf,16); } static void agent_accept(int id __attribute__((__unused__)), void *arg) { AGENTSTATE *s; int newfd; struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[sizeof(struct cmsghdr)+sizeof(int)]; char junk; s = arg; iov.iov_base = &junk; iov.iov_len = 1; 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 = sizeof(struct cmsghdr) + sizeof(int); mh.msg_flags = 0; recvmsg(s->accfd,&mh,0); if (mh.msg_flags & MSG_CTRUNC) { fprintf(stderr,"%s: truncated control data from accepter?""?\n",__progname); exit(1); } if (mh.msg_controllen != sizeof(struct cmsghdr)+sizeof(int)) { fprintf(stderr,"%s: wrong control length (%d) from accepter?""?\n",__progname,(int)mh.msg_controllen); exit(1); } bcopy(&ctlbuf[0],&cmh,sizeof(struct cmsghdr)); if (cmh.cmsg_len != sizeof(struct cmsghdr)+sizeof(int)) { fprintf(stderr,"%s: wrong cmsg length (%d) from accepter?""?\n",__progname,(int)cmh.cmsg_len); exit(1); } if (cmh.cmsg_level != SOL_SOCKET) { fprintf(stderr,"%s: wrong cmsg level (%d) from accepter?""?\n",__progname,(int)cmh.cmsg_level); exit(1); } if (cmh.cmsg_type != SCM_RIGHTS) { fprintf(stderr,"%s: wrong cmsg level (%d) from accepter?""?\n",__progname,(int)cmh.cmsg_type); exit(1); } bcopy(&ctlbuf[sizeof(struct cmsghdr)],&newfd,sizeof(int)); new_agent_fd(s,newfd); } static void accepter_accept(int id __attribute__((__unused__)), void *arg) { FORKACCSTATE *s; int newfd; struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[sizeof(struct cmsghdr)+sizeof(int)]; char junk; s = arg; iov.iov_base = &junk; iov.iov_len = 1; 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 = sizeof(struct cmsghdr) + sizeof(int); mh.msg_flags = 0; recvmsg(s->acceptfd,&mh,0); if (mh.msg_flags & MSG_CTRUNC) { fprintf(stderr,"%s: truncated control data\n",__progname); exit(1); } if (mh.msg_controllen != sizeof(struct cmsghdr)+sizeof(int)) { fprintf(stderr,"%s: wrong control length (%d) from accepter?""?\n",__progname,(int)mh.msg_controllen); exit(1); } bcopy(&ctlbuf[0],&cmh,sizeof(struct cmsghdr)); if (cmh.cmsg_len != sizeof(struct cmsghdr)+sizeof(int)) { fprintf(stderr,"%s: wrong cmsg length (%d) from accepter?""?\n",__progname,(int)cmh.cmsg_len); exit(1); } if (cmh.cmsg_level != SOL_SOCKET) { fprintf(stderr,"%s: wrong cmsg level (%d) from accepter?""?\n",__progname,(int)cmh.cmsg_level); exit(1); } if (cmh.cmsg_type != SCM_RIGHTS) { fprintf(stderr,"%s: wrong cmsg level (%d) from accepter?""?\n",__progname,(int)cmh.cmsg_type); exit(1); } bcopy(&ctlbuf[sizeof(struct cmsghdr)],&newfd,sizeof(int)); sendmsg(s->agentpipe,&mh,0); close(newfd); } static void forker_died(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { fprintf(stderr,"%s: forker died\n",__progname); exit(1); } static void agent_die(int id __attribute__((__unused__)), void *arg __attribute__((__unused__))) { exit(0); } static void new_accepter(int id, void *arg) { FORKACCSTATE *s; int deathpipe[2]; pid_t pid; s = arg; remove_poll_id(id); close(s->fork_acc_death); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&deathpipe[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } pid = fork(); if (pid < 0) { fprintf(stderr,"%s: forker fork: %s\n",__progname,strerror(errno)); exit(1); } if (pid == 0) { close(deathpipe[0]); /* Leave s->agentdeath in the poll; its setup is conveniently right */ add_poll_fd(s->acceptfd,&rwtest_always,&rwtest_never,&accepter_accept,0,s); add_poll_fd(deathpipe[1],&rwtest_always,&rwtest_never,&agent_die,0,0); } else { close(deathpipe[1]); add_poll_fd(deathpipe[0],&rwtest_always,&rwtest_never,&new_accepter,0,s); s->fork_acc_death = deathpipe[0]; } } 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) { unlink(s->socketpath); exit(0); } } remove_poll_id(s->waitid); close(s->waitpipe[0]); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&s->waitpipe[0]) < 0) { fprintf(stderr,"%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); } void agent_setup(void) { int fd; char *path; struct sockaddr_un *s_un; int slen; AGENTSTATE *s; pid_t pid; int forkerdeath[2]; int accpipe[2]; sigset_t mask; fd = socket(AF_LOCAL,SOCK_DGRAM,0); if (fd < 0) { fprintf(stderr,"%s: socket(AF_LOCAL,SOCK_DGRAM): %s\n",__progname,strerror(errno)); exit(1); } asprintf(&path,"/tmp/.ssh-agent-%d",(int)getpid()); if (unlink(path) < 0) { if (errno != ENOENT) { fprintf(stderr,"%s: can't unlink %s: %s\n",__progname,path,strerror(errno)); exit(1); } } 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) { fprintf(stderr,"%s: can't bind to %s: %s\n",__progname,path,strerror(errno)); exit(1); } free(s_un); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&forkerdeath[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } if (socketpair(AF_LOCAL,SOCK_DGRAM,0,&accpipe[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } pid = fork(); if (pid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (pid == 0) { FORKACCSTATE *s; int tmp[2]; sigfillset(&mask); /* sigprocmask(SIG_BLOCK,&mask,0); */ close(accpipe[0]); close(forkerdeath[0]); if (socketpair(AF_LOCAL,SOCK_STREAM,0,&tmp[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } close(tmp[1]); s = malloc(sizeof(FORKACCSTATE)); s->acceptfd = fd; s->fork_acc_death = tmp[0]; s->agentdeath = forkerdeath[1]; s->agentpipe = accpipe[1]; add_poll_fd(s->agentdeath,&rwtest_always,&rwtest_never,&agent_die,0,0); add_poll_fd(s->fork_acc_death,&rwtest_always,&rwtest_never,&new_accepter,0,s); return; } close(accpipe[1]); close(forkerdeath[1]); s = malloc(sizeof(AGENTSTATE)); s->accfd = accpipe[0]; s->forkerdeath = forkerdeath[0]; s->clients = 0; s->nclients = 0; s->keys = 0; random_data(&s->cookie[0],sizeof(s->cookie)); close(fd); pid = fork(); if (pid < 0) { fprintf(stderr,"%s: fork: %s\n",__progname,strerror(errno)); exit(1); } if (pid == 0) { char *txt; int txtlen; FILE *f; FILE *w; close(s->accfd); close(s->forkerdeath); f = fopen_alloc(&txt,&txtlen); w = b64w_wrap(f); putc(1,w); putc(sizeof(s->cookie),w); fwrite(&s->cookie[0],1,sizeof(s->cookie),w); fprintf(w,"%s",path); fclose(w); putc(0,f); fclose(f); setenv("SSH_AGENT",txt,1); if (! argsbegin) { const char *sh; sh = getenv("SHELL"); if (! sh) sh = "/bin/sh"; execl(sh,sh,(char *)0); fprintf(stderr,"%s: %s: %s\n",__progname,sh,strerror(errno)); } else { execv(argsbegin[0],argsbegin); fprintf(stderr,"%s: %s: %s\n",__progname,argsbegin[0],strerror(errno)); } exit(1); } s->socketpath = path; s->cmdkid = pid; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&s->waitpipe[0]) < 0) { fprintf(stderr,"%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); add_poll_fd(s->accfd,&rwtest_always,&rwtest_never,&agent_accept,0,s); add_poll_fd(s->forkerdeath,&rwtest_always,&rwtest_never,&forker_died,0,s); }