/* This file is in the public domain. */ #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "pp.h" #include "b64.h" #include "msgs.h" #include "util.h" #include "errf.h" #include "panic.h" #include "agent.h" #include "config.h" #include "nested.h" #include "verbose.h" #include "keyfiles.h" #include "alg-util.h" #include "pkt-util.h" #include "stdio-util.h" #include "agent-util.h" #include "scm-rights.h" #include "priv-crypto.h" #include "agent-client.h" /* * An AGENT_KEY is our representation of a key in the agent. This is * used for listing and to detect when we're about to add a key that's * already present. * * An ACLIENT encapsulates state for an agent connection; it is the * structure behind the opaque pointer returned by * open_agent_connection(). */ typedef struct agent_key AGENT_KEY; typedef struct aclient ACLIENT; /* * Not much here: just the file descriptor and a flags word. * * Flag bits: * * ACF_DOES_ID * Indicates that the agent supports setting client ID * strings. If set, we set our command line as the ID * string, so the interactive agent can tell what command * a given client corresponds to. */ struct aclient { int fd; unsigned int flags; #define ACF_DOES_ID 0x00000001 } ; /* * Not much here either. There's a link field (since these are kept in * linked lists), an algorithm pointer, and a data blob * pointer/length. */ struct agent_key { AGENT_KEY *link; HKALG *alg; void *data; int len; } ; /* * conn is a persistent connection to the agent. Operations which need * an agent connection but don't want to manipulate the agent * connection in its own right (eg, a client trying to authenticate * with agent keys) call connect_to_agent() to provoke the creation of * a connection, which is stored in conn. * * tried is a boolean, indicating whether we've tried to connect. This * is used to prevent multiple futile attempts to connect: once one * attempt is made, other attempts are not tried - using the existing * connection if it worked, or assuming it won't work better this time * either if it failed. * * connfailure remembers the failure message from a failed attempt to * open the connection. This is used by connect_to_agent. * * constraints_add is the AGENT_CONSTRAINTS corresponding to the * -constraint command-line flag, as used by the -add operation. */ static ACLIENT *conn; static char *connfailure; static int tried = 0; AGENT_CONSTRAINTS constraints_add; /* * These are here to get proper argument-casting semantics. * * When calling agent_exchange, we'd like for the arguments to the * T_*() pseudo-functions to be handled as if they were real function * arguments. Since they are part of a varargs list, agent_exchange() * itself cannot provoke this. So the macros expand to arglists * including calls to these trivial functions, which act like real * functions because they *are* real functions - in particular, they * have the desired argument-passing semantics, and always provide the * correct type to agent_exchange(). They are marked inline to hint * to the compiler that they're worth inlining away. (A good compiler * would probably inline them anyway because they're so simple, but * the hints can't hurt.) */ static inline unsigned int scr_to_uint32(unsigned int v) { return(v); } static inline const void *scr_to_const_void_p(const void *v) { return(v); } static inline const char *scr_to_cstr(const char *v) { return(v); } static inline int scr_to_int(int v) { return(v); } static inline unsigned char scr_to_uchar(unsigned char v) { return(v); } /* * agent_exchange() carries out an exchange with the agent. * * fd is the file descriptor of the agent connection. * * rdp and rlp receive the response packet pointer and length. The * data will be mallocked, and the caller is rseponsible for freeing * it when it's no longer needed. * * pkttype is the packet type (the first byte of the request), such as * SSH_AGENT_REQUEST_VERSION or SSH_AGENT_DELETE_KEY. * * The rest of the arguments consist of calls to the T_*() * pseudo-functions, terminated with T_END. These work by inserting a * constant in the arglist, which serves to indicate what follows, * then the appropriate further arguments. Their semantics: * * T_END * (Note: takes no () after it.) This indicates the end * of the arglist. It must occur, or the arglist parsing * code will walk off the end of the arglist. (This * probably will provoke a panic.) * * T_UNIT32(val) * Represents an unsigned 32-bit integer. The argument is * the value. * * T_CSTRING(str) * Represents a string, where the string is passed as a C * const char *, with the length being its strlen(). The * reason we don't expand this to * T__STRING,(str),(int)strlen(str) * is that we want to evaluate str only once. So we do * the equivalent inside agent_exchange(). A nil pointer * argument is semantically equivalent to "". * * T_STRING(ptr,len) * Represents a string, where the string is passed as a * const void * pointer and an int length. If the length * is zero, the pointer may be nil. * * T_IF(cond) * Conditionally includes part of the packet. Does not * generate anything itself; instead, everything up to the * next T_IF (or up to the T_END, if that occurs first) is * included only if cond is true. Note that this is not a * full-fledged conditional facility; T_IFs cannot be * nested, so there is no endif-analog. (If you want to * return to unconditional operation, use T_IF(1).) * * T_BYTE(val) * Represents a one-byte value. The argument is the * value. * * For example, to delete a key, the operation is documented as * byte SSH_AGENT_DELETE_KEY * string public key and/or certificates for it * string description of the key * which maps into something like * agent_exchange(fd,&reply_data,&reply_len,SSH_AGENT_DELETE_KEY, * T_STRING(pubkey_data,pubkey_len), * T_CSTRING(comment_str), * T_END ); * assuming the public key data blob is on hand as a * pointer-and-length but the description string is a C string. * * agent_exchange returns nothing. Failures produce complaints (to * errf) and exits. The agent is always assumed to reply with exactly * one packet. */ static void agent_exchange(int fd, void **rdp, int *rlp, 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_CSTRING(s) T__CSTRING,scr_to_cstr(s) #define T__CSTRING 3 #define T_STRING(d,l) T__STRING,scr_to_const_void_p(d),scr_to_int(l) #define T__STRING 4 #define T_IF(cond) T__IF,scr_to_int(cond) #define T__IF 5 #define T_BYTE(v) T__BYTE,scr_to_uchar(v) #define T__BYTE 6 { va_list ap; char *obuf; int olen; FILE *f; int t; unsigned char ilenbuf[4]; char *ibuf; unsigned int ilen; int curcond; f = fopen_alloc(&obuf,&olen); /* These four bytes are replaced by the actual length, once we have it. */ fwrite("\0\0\0\0",1,4,f); putc(pkttype,f); curcond = 1; va_start(ap,pkttype); while <"args"> (1) { t = va_arg(ap,int); switch (t) { case T__END: break <"args">; case T__UINT32: { unsigned int v; v = va_arg(ap,unsigned int); if (curcond) fput_uint32(f,v); } break; case T__CSTRING: { const char *s; s = va_arg(ap,const char *); if (curcond) fput_string(f,s,s?strlen(s):0); } break; case T__STRING: { const void *data; int len; data = va_arg(ap,const void *); len = va_arg(ap,int); if (curcond) fput_string(f,data,len); } break; case T__IF: curcond = va_arg(ap,int); break; case T__BYTE: { unsigned char v; /* gcc objects here, saying we should pass the widened type, ie, int instead of unsigned char. Unfortunately, as wrong as it is from an abstractly-type-correct point of view, it is right from a strict C point of view. Blame C for making it impossible to stay type-correct. :-( */ v = va_arg(ap,/*unsigned char*/int); if (curcond) putc(v,f); } break; default: panic("bad key"); break; } } va_end(ap); fclose(f); if (olen < 4/*length*/+1/*pkttype*/) panic("too short"); /* Fill in the length, now that we have it. */ put_uint32(obuf,olen-4); if (VERB(AGENT)) { verb(AGENT,"Agent request packet (%d):\n",olen-4); verb_data_block(VERBOSE_AGENT,obuf+4,olen-4); } write(fd,obuf,olen); t = recv(fd,&ilenbuf[0],4,MSG_WAITALL); if (t < 0) { fprintf(errf,"%s: agent connection recv: %s\n",__progname,strerror(errno)); exit(1); } if (t < 4) { fprintf(errf,"%s: short agent connection read: %d < 4\n",__progname,t); exit(1); } ilen = get_uint32(&ilenbuf[0]); ibuf = malloc(ilen); if (! ibuf) { fprintf(errf,"%s: agent error: reply malloc(%u) failed\n",__progname,ilen); exit(1); } t = recv(fd,ibuf,ilen,MSG_WAITALL); if (t < 0) { fprintf(errf,"%s: agent connection recv: %s\n",__progname,strerror(errno)); exit(1); } if (t < ilen) { fprintf(errf,"%s: short agent connection read: %d < %u\n",__progname,t,ilen); exit(1); } if (VERB(AGENT)) { verb(AGENT,"Agent response packet (%d):\n",ilen); verb_data_block(VERBOSE_AGENT,ibuf,ilen); } *rdp = ibuf; *rlp = ilen; } /* * Open a connection to the agent. See agent-client.h for the * interface contract. * * This function handles extracting the contact info from the * environment, sending over the socketpair that further communication * takes place over, and answering the challenge based on the contact * info cookie. For AGENT_PROTO_NORMAL connections, it then requests * the agent version number, verifies it, and checks the extension * list for extensions we know about - and if it supports our * ID-string extension, it then sends over cmdline_txt (truncated to * 60 chars if it's too long). In particular, this means that * cmdline_txt must be set up before this is called. * * While the local protocol for contacting the agent is private to * moussh, the agent contacted might not be moussh, especially if it's * forwarded from elsewhere. This is why we unconditionally do * moussh-specific things like the crypto challenge/response but then * conditionalize setting the ID text string. * * The return value is an ACLIENT pointer on success (converted to a * void *; outside this file, these handles are opaque) and a nil * pointer on failure. On failure, errp is stored through with an * error message, which the caller is responsible for freeing as * necessary; on success, errp may or may not be stored through, but * if it is, the value stored is meaningless and the caller has no * responsibility for it. * * proto describes what kind of agent connection is desired; it is one * of the AGENT_PROTO_* values from agent.h. */ void *open_agent_connection(char **errp, AGENT_PROTO proto) { __label__ failret; char *txt; char *rmsg; FILE *f; void *blob; int blen; const unsigned char *cookie; int clen; unsigned char challenge[16]; int plen; struct sockaddr_un *s_un; int slen; int s; int pipe[2]; unsigned char connmsg[257]; int n; void *h; unsigned char hash[20]; void *reply; int rlen; const void *represt; int represtl; unsigned char rcode; unsigned int v; struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[CMSPACE(sizeof(int))]; ACLIENT *rv; unsigned int flags; NESTED void dofail(void) { if (s >= 0) close(s); if (pipe[0] >= 0) close(pipe[0]); if (pipe[1] >= 0) close(pipe[1]); free(blob); free(s_un); free(reply); } NESTEDFWD void fail(const char *, ...) __attribute__((__format__(__printf__,1,2),__noreturn__)); NESTEDDEF void fail(const char *fmt, ...) { va_list ap; dofail(); va_start(ap,fmt); vasprintf(&rmsg,fmt,ap); va_end(ap); goto failret; } NESTEDFWD void ppfail(const void *, const char *, ...) __attribute__((__format__(__printf__,2,3),__noreturn__)); NESTEDDEF void ppfail(const void *at __attribute__((__unused__)), const char *fmt, ...) { va_list ap; dofail(); va_start(ap,fmt); vasprintf(&rmsg,fmt,ap); va_end(ap); goto failret; } if (0) { failret:; *errp = rmsg; return(0); } s = -1; pipe[0] = -1; pipe[1] = -1; blob = 0; s_un = 0; reply = 0; txt = getenv("SSH_AGENT"); if (txt == 0) fail("no SSH_AGENT in the environment"); f = fopen_rstr(txt,-1); if (! b64_read_blob(f,&blob,&blen)) { fclose(f); fail("can't parse $SSH_AGENT"); } fclose(f); if (blen < 1) fail("$SSH_AGENT too short (blen %d)",blen); if (((unsigned char *)blob)[0] != 1) fail("$SSH_AGENT version wrong"); if (blen < 2) fail("$SSH_AGENT too short (blen %d)",blen); clen = ((unsigned char *)blob)[1]; cookie = ((unsigned char *)blob) + 2; plen = blen - (1+1+clen); if (plen < 1) fail("$SSH_AGENT too short (plen %d)",plen); slen = sizeof(*s_un) - sizeof(s_un->sun_path) + plen + 1; s_un = malloc(slen); s_un->sun_family = AF_LOCAL; s_un->sun_len = slen; bcopy(((unsigned char *)blob)+1+1+clen,&s_un->sun_path[0],plen); s_un->sun_path[plen] = '\0'; s = socket(AF_LOCAL,SOCK_DGRAM,0); if (s < 0) { fprintf(errf,"%s: socket (AF_LOCAL SOCK_DGRAM): %s\n",__progname,strerror(errno)); exit(1); } if (socketpair(AF_LOCAL,SOCK_STREAM,0,&pipe[0]) < 0) { fprintf(errf,"%s: socketpair (AF_LOCAL SOCK_STREAM): %s\n",__progname,strerror(errno)); exit(1); } connmsg[0] = proto; connmsg[1] = 0; iov.iov_base = &connmsg[0]; iov.iov_len = 2; switch (proto) { default: panic("bad proto %d to open_agent_connection",(int)proto); break; case AGENT_PROTO_NORMAL: case AGENT_PROTO_INTERACTIVE: break; } cmh.cmsg_len = CMLEN(sizeof(int)); cmh.cmsg_level = SOL_SOCKET; cmh.cmsg_type = SCM_RIGHTS; bcopy(&cmh,&ctlbuf[0],sizeof(struct cmsghdr)); bcopy(&pipe[1],&ctlbuf[CMSKIP(&cmh)],sizeof(int)); mh.msg_name = (void *) s_un; mh.msg_namelen = slen; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = (void *) &ctlbuf[0]; mh.msg_controllen = CMLEN(sizeof(int)); mh.msg_flags = 0; if (sendmsg(s,&mh,0) < 0) fail("sendmsg: %s",strerror(errno)); free(s_un); s_un = 0; close(pipe[1]); close(s); s = pipe[0]; pipe[0] = -1; pipe[1] = -1; n = recv(s,&challenge[0],16,MSG_WAITALL); if (n < 0) fail("challenge recv: %s",strerror(errno)); if (n < 16) fail("challenge too short (%d < 16)",n); h = sha1_init(); for (n=0;n<16;n++) { sha1_process_bytes(h,cookie,clen); sha1_process_bytes(h,&challenge[0],16); } sha1_result(h,&hash[0]); xor_block(&hash[0],&hash[10],&hash[0],10); write(s,&hash[0],10); flags = 0; switch (proto) { case AGENT_PROTO_NORMAL: agent_exchange(s,&reply,&rlen,SSH_AGENT_REQUEST_VERSION,T_END); parse_data(reply,rlen,&ppfail, PP_BYTE(&rcode), PP_UINT32(&v), PP_REST(&represt,&represtl) ); if (rcode != SSH_AGENT_VERSION_RESPONSE) fail("version response message type wrong (%d != %d)",((unsigned char *)reply)[0],SSH_AGENT_VERSION_RESPONSE); switch (v) { case 2: case 3: break; default: fail("version number wrong (%u)",v); break; } while (represtl > 0) { STR xname; STR xdata; parse_data(represt,represtl,&ppfail, PP_STRING(&xname), PP_STRING(&xdata), PP_REST(&represt,&represtl) ); if (str_equalsC(xname,"client-id@rodents.montreal.qc.ca")) flags |= ACF_DOES_ID; free_str(xname); free_str(xdata); } free(reply); if (flags & ACF_DOES_ID) { char *tmp; if (strlen(cmdline_txt) > 64) { asprintf(&tmp,"%.60s...",cmdline_txt); } else { tmp = strdup(cmdline_txt); } agent_exchange(s,&reply,&rlen,SSH_AGENT_EXTENSION, T_CSTRING("client-id@rodents.montreal.qc.ca"), T_CSTRING("set"), T_CSTRING(tmp), T_END ); free(reply); free(tmp); } break; case AGENT_PROTO_INTERACTIVE: break; } free(blob); rv = malloc(sizeof(ACLIENT)); rv->fd = s; rv->flags = flags; return(rv); } /* * connect_to_agent() is called to set up the private agent connection, * as discussed above on the "conn" variable. * * If mustwork is true, any failure causes a gripe-and-exit. The * return value is a "succeeded" boolean; if mustwork is true, the * return value will always be true and need not be checked. * * This is the reason connfailure exists: if this is called once with * mustwork false, it can leave tried set true but conn nil. In this * case, a later call with mustwork true must complain and exit, so it * needs the error message from the first, failed, attempt. */ static int connect_to_agent(int mustwork) { char *msg; if (! tried) { tried = 1; conn = open_agent_connection(&msg,AGENT_PROTO_NORMAL); if (! conn) connfailure = msg; } if (mustwork && !conn) { fprintf(errf,"%s: can't connect to agent: %s\n",__progname,connfailure); exit(1); } return(conn!=0); } /* * success_failure_response encapsulates common code for most agent * operations, which return either SSH_AGENT_SUCCESS with no * additional information or SSH_AGENT_FAILURE with a reason code and * nothing further. reply and rlen are pointer-and-length for the * reply; tag is a short description of the kind of exchange, for use * in messages (usually the SSH_AGENT_* message name without the * prefix, as in "ADD_KEY"). * * Response value is nonzero if everything's cool or 0 if there was * some error (a message will have been printed to errf on error). */ static int success_failure_response(void *reply, int rlen, const char *tag) { if (rlen < 1) { fprintf(errf,"%s: %s response too short (len %d)\n",__progname,tag,rlen); return(0); } switch (((unsigned char *)reply)[0]) { case SSH_AGENT_SUCCESS: if (rlen != 1) { fprintf(errf,"%s: %s SUCCESS response length wrong (len %d)\n",__progname,tag,rlen); return(0); } break; case SSH_AGENT_FAILURE: if (rlen != 5) { fprintf(errf,"%s: %s FAILURE response length wrong (len %d)\n",__progname,tag,rlen); return(0); } else { unsigned int v; v = get_uint32(((unsigned char *)reply)+1); fprintf(errf,"%s: %s failed: ",__progname,tag); switch (v) { case SSH_AGENT_ERROR_TIMEOUT: fprintf(errf,"timeout"); break; case SSH_AGENT_ERROR_KEY_NOT_FOUND: fprintf(errf,"key not found"); break; case SSH_AGENT_ERROR_DECRYPT_FAILED: fprintf(errf,"decrypt failed"); break; case SSH_AGENT_ERROR_SIZE_ERROR: fprintf(errf,"size error"); break; case SSH_AGENT_ERROR_KEY_NOT_SUITABLE: fprintf(errf,"key not suitable"); break; case SSH_AGENT_ERROR_DENIED: fprintf(errf,"denied"); break; case SSH_AGENT_ERROR_FAILURE: fprintf(errf,"failure"); break; case SSH_AGENT_ERROR_UNSUPPORTED_OP: fprintf(errf,"unsupported op"); break; default: fprintf(errf,"unknown error %u",v); break; } fprintf(errf,"\n"); return(0); } break; default: fprintf(errf,"%s: %s provoked unknown response %d\n",__progname,tag,((unsigned char *)reply)[0]); return(0); break; } return(1); } /* * The implementation of -add. * * First, we list the keys the agent has, so we can warn if we're * adding a potentially-redundant copy. (It might not be actually * redundant if the constrants differ - for example, the agent may * have a copy of some key K marked with infinite use-count and a * forwarding-limit of 1, but it's still potentially useful to add K * with a use-count of 1 and an infinite forwarding-limit. This is * why the "key already present" test produces just a warning.) * * Then, we loop over the args, if there were any, or the algorithms' * default keys, otherwise. For each one, we try to load it, check if * the agent already has it, unwrap it (which may involve prompting * for a passphrase), and then do the ADD_KEY operation (including any * constraints in constraints_add). */ void agent_add(void) { int i; void *pub; int publen; HKALG *alg; void *priv; int privlen; char *comment; const char *file; const char *sshdir; int atnl; FILE *ef; char *t; AGENT_KEY *already; AGENT_KEY *ak; NESTED int w(void *cookie __attribute__((__unused__)), const char *buf, int len) { int i; for (i=0;ialg = alg; k->data = (void *)(k+1); bcopy(data,k->data,len); k->len = len; k->link = already; already = k; } atnl = 1; ef = funopen(0,0,&w,0,&c); sshdir = 0; already = 0; agent_list_keys(&havekey); for (i=0;;i++) { if (argsbegin) { if (i >= argscount) break; file = argsbegin[i]; } else { HKALG *a; a = (*at_hk.list)(i); if (! a) break; file = a->deffile; printf("Trying %s...\n",file); } if (index(file,'/')) { t = 0; } else { if (! sshdir) sshdir = get_sshdir("key-file directory"); asprintf(&t,"%s/%s",sshdir,file); file = t; } if (load_key_pair(file,file,&pub,&publen,&alg,&priv,&privlen,&comment,0,ef)) { void *privclear; int privclen; do <"freepp"> { for (ak=already;ak;ak=ak->link) { if ((ak->len == publen) && !bcmp(ak->data,pub,publen)) { printf("Note: the agent already has the key from %s.\n",file); break; } } if (priv_unwrap(priv,privlen,comment,&privclear,&privclen,config_str("passphrase"))) { void *reply; int rlen; connect_to_agent(1); if (! constraints_add.initted) reinit_constraints(&constraints_add); agent_exchange(conn->fd,&reply,&rlen,SSH_AGENT_ADD_KEY, T_STRING(privclear,privclen), T_STRING(pub,publen), T_CSTRING(comment), T_IF(constraints_add.time!=0), T_BYTE(SSH_AGENT_CONSTRAINT_TIMEOUT), T_UINT32(constraints_add.time), T_IF(constraints_add.use!=LIMIT_INFINITY), T_BYTE(SSH_AGENT_CONSTRAINT_USE_LIMIT), T_UINT32(constraints_add.use), T_IF(constraints_add.fwd!=LIMIT_INFINITY), T_BYTE(SSH_AGENT_CONSTRAINT_FORWARDING_STEPS), T_UINT32(constraints_add.fwd), T_IF(constraints_add.confirm), T_BYTE(SSH_AGENT_CONSTRAINT_NEED_USER_VERIFICATION), T_BYTE(1), T_END ); success_failure_response(reply,rlen,"ADD_KEY"); free(reply); bzero(privclear,privclen); free(privclear); } } while (0); free(pub); bzero(priv,privlen); free(priv); free(comment); } free(t); fflush(ef); } fclose(ef); } /* * The implementation of -list. * * We just list the agent keys, printing out whatever info we can for * each one. (For keys using unsupported algorithms, we just print * out the raw data blob, whether or not -long is in effect.) */ void agent_list(void) { int n; int longlisting; NESTED void printkey(HKALG *alg, void *pubdata, int publen, char *comment) { if (alg) { if (longlisting) { write_pub_to_fd("standard","output",1,alg,pubdata,publen,comment); } else { brief_pub_to_fd("standard","output",1,alg,comment); } } else { int i; printf("Unsupported: "); for (i=0;ifd,&reply,&rlen,SSH_AGENT_DELETE_KEY, T_STRING(pub,publen), T_CSTRING(comment), T_END ); success_failure_response(reply,rlen,"DELETE_KEY"); free(reply); free(pub); free(comment); } fclose(ef); free(t); } } /* * The implementation of -delete-all. * * This borders on trivial. */ void agent_delete_all(void) { void *reply; int rlen; if (argsbegin) { fprintf(errf,"%s: must not specify any arguments\n",__progname); exit(1); } connect_to_agent(1); agent_exchange(conn->fd,&reply,&rlen,SSH_AGENT_DELETE_ALL_KEYS,T_END); success_failure_response(reply,rlen,"DELETE_ALL_KEYS"); free(reply); } /* * Called (both from within this file and from elsewhere, eg, publickey * user authentication code) to list the keys the agent has. See * agent-client.h for the interface contract. */ void agent_list_keys(void (*fn)(HKALG *, void *, int, char *)) { int i; HKALG *alg; void *reply; int rlen; const void *rest; int restlen; unsigned int nkeys; NESTED void fail(const void *dp __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: LIST_KEYS response invalid: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\n"); exit(1); } if (! connect_to_agent(0)) return; agent_exchange(conn->fd,&reply,&rlen,SSH_AGENT_LIST_KEYS,T_END); if (rlen < 1) { fprintf(errf,"%s: LIST_KEYS response too short (len %d)\n",__progname,rlen); exit(1); } if (((unsigned char *)reply)[0] != SSH_AGENT_KEY_LIST) { fprintf(errf,"%s: LIST_KEYS response type wrong (%d)\n",__progname,((unsigned char *)reply)[0]); exit(1); } parse_data(reply,rlen,fail, PP_IGNORE(1), PP_UINT32(&nkeys), PP_REST(&rest,&restlen) ); for (;nkeys>0;nkeys--) { STR pkblob; STR desc; parse_data(rest,restlen,fail, PP_STRING(&pkblob), PP_STRING(&desc), PP_REST(&rest,&restlen) ); do <"handled"> { char *t; t = blk_to_cstr(desc.data,desc.len); for (i=0;(alg=(*at_hk.list)(i));i++) { if ((*alg->checkpub)(pkblob.data,pkblob.len)) { (*fn)(alg,pkblob.data,pkblob.len,t); free(t); break <"handled">; } } (*fn)(0,pkblob.data,pkblob.len,t); free(t); } while (0); free_str(pkblob); free_str(desc); } free(reply); } /* * agent_sign has the agent sign some data. See agent-client.h for the * interface contract. */ int agent_sign(const void *keyd, int keyl, const void *hashd, int hashl, void **sigdp, int *siglp) { void *reply; int rlen; STR sig; NESTED void fail(const void *dp __attribute__((__unused__)), const char *fmt, ...) { va_list ap; fprintf(errf,"%s: PRIVATE_KEY_OP response invalid: ",__progname); va_start(ap,fmt); vfprintf(errf,fmt,ap); va_end(ap); fprintf(errf,"\n"); exit(1); } agent_exchange(conn->fd,&reply,&rlen,SSH_AGENT_PRIVATE_KEY_OP, T_STRING("sign",-1), T_STRING(keyd,keyl), T_STRING(hashd,hashl), T_END ); if (rlen < 1) { fprintf(errf,"%s: PRIVATE_KEY_OP response too short (len %d)\n",__progname,rlen); exit(1); } switch (((unsigned char *)reply)[0]) { case SSH_AGENT_OPERATION_COMPLETE: parse_data(reply,rlen,fail, PP_UNWIND(&pp_free_ptr,reply), PP_IGNORE(1), PP_STRING(&sig), PP_ENDSHERE ); *sigdp = sig.data; *siglp = sig.len; bzero(reply,rlen); free(reply); return(1); break; case SSH_AGENT_FAILURE: bzero(reply,rlen); free(reply); return(0); break; } bzero(reply,rlen); free(reply); fail(0,"wrong message type %d",((unsigned char *)reply)[0]); return(0); } /* * agent_available() checks whether an agent appears to be available. * See agent-client.h for the interface contract. */ int agent_available(void) { char *txt; FILE *f; void *blob; int blen; int clen; int plen; char *pbuf; struct stat stb; txt = getenv("SSH_AGENT"); if (txt == 0) return(0); f = fopen_rstr(txt,-1); if (! b64_read_blob(f,&blob,&blen)) { fclose(f); return(0); } fclose(f); if ( (blen < 1) || (((unsigned char *)blob)[0] != 1) || (blen < 2) ) { free(blob); return(0); } clen = ((unsigned char *)blob)[1]; plen = blen - (1+1+clen); if (plen < 1) { free(blob); return(0); } pbuf = blk_to_cstr(((unsigned char *)blob)+1+1+clen,plen); free(blob); if (stat(pbuf,&stb) < 0) { free(pbuf); return(0); } free(pbuf); if ((stb.st_mode & S_IFMT) != S_IFSOCK) return(0); return(1); } /* * Return the file descriptor underlying an agent handle. See * agent-client.h for the interface contract. */ int agent_client_fd(void *acv) { return(((ACLIENT *)acv)->fd); } /* * Close an agent handle. See agent-client.h for the interface * contract. */ void agent_client_close(void *acv) { ACLIENT *ac; ac = acv; close(ac->fd); free(ac); }