X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=pageant.c;h=e7430e7865faac93a173f15ede19442d76ea54cf;hb=b03020cab9297c53d1a65a497910ea7a988b94e7;hp=47e09dbec8f731e45358ab6372bd892b90f2e5fa;hpb=5ba2d611f9346701b7b5b983b605cb7641fb19d7;p=PuTTY.git diff --git a/pageant.c b/pageant.c index 47e09dbe..e7430e78 100644 --- a/pageant.c +++ b/pageant.c @@ -27,6 +27,8 @@ int random_byte(void) return 0; /* unreachable, but placate optimiser */ } +static int pageant_local = FALSE; + /* * rsakeys stores SSH-1 RSA keys. ssh2keys stores all SSH-2 keys. */ @@ -257,20 +259,48 @@ void *pageant_make_keylist2(int *length) return ret; } -void *pageant_handle_msg(const void *msg, int msglen, int *outlen) +static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...) +#ifdef __GNUC__ +__attribute__ ((format (printf, 3, 4))) +#endif + ; + +static void plog(void *logctx, pageant_logfn_t logfn, const char *fmt, ...) +{ + /* + * This is the wrapper that takes a variadic argument list and + * turns it into the va_list that the log function really expects. + * It's safe to call this with logfn==NULL, because we + * double-check that below; but if you're going to do lots of work + * before getting here (such as looping, or hashing things) then + * you should probably check logfn manually before doing that. + */ + if (logfn) { + va_list ap; + va_start(ap, fmt); + logfn(logctx, fmt, ap); + va_end(ap); + } +} + +void *pageant_handle_msg(const void *msg, int msglen, int *outlen, + void *logctx, pageant_logfn_t logfn) { const unsigned char *p = msg; const unsigned char *msgend; unsigned char *ret = snewn(AGENT_MAX_MSGLEN, unsigned char); int type; + const char *fail_reason; msgend = p + msglen; /* * Get the message type. */ - if (msgend < p+1) + if (msgend < p+1) { + fail_reason = "message contained no type code"; goto failure; + } type = *p++; switch (type) { @@ -282,14 +312,28 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) int len; void *keylist; + plog(logctx, logfn, "request: SSH1_AGENTC_REQUEST_RSA_IDENTITIES"); + ret[4] = SSH1_AGENT_RSA_IDENTITIES_ANSWER; keylist = pageant_make_keylist1(&len); if (len + 5 > AGENT_MAX_MSGLEN) { sfree(keylist); + fail_reason = "output would exceed max msglen"; goto failure; } PUT_32BIT(ret, len + 1); memcpy(ret + 5, keylist, len); + + plog(logctx, logfn, "reply: SSH1_AGENT_RSA_IDENTITIES_ANSWER"); + if (logfn) { /* skip this loop if not logging */ + int i; + struct RSAKey *rkey; + for (i = 0; NULL != (rkey = pageant_nth_ssh1_key(i)); i++) { + char fingerprint[128]; + rsa_fingerprint(fingerprint, sizeof(fingerprint), rkey); + plog(logctx, logfn, "returned key: %s", fingerprint); + } + } sfree(keylist); } break; @@ -301,14 +345,31 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) int len; void *keylist; + plog(logctx, logfn, "request: SSH2_AGENTC_REQUEST_IDENTITIES"); + ret[4] = SSH2_AGENT_IDENTITIES_ANSWER; keylist = pageant_make_keylist2(&len); if (len + 5 > AGENT_MAX_MSGLEN) { sfree(keylist); + fail_reason = "output would exceed max msglen"; goto failure; } PUT_32BIT(ret, len + 1); memcpy(ret + 5, keylist, len); + + plog(logctx, logfn, "reply: SSH2_AGENT_IDENTITIES_ANSWER"); + if (logfn) { /* skip this loop if not logging */ + int i; + struct ssh2_userkey *skey; + for (i = 0; NULL != (skey = pageant_nth_ssh2_key(i)); i++) { + char *fingerprint = ssh2_fingerprint(skey->alg, + skey->data); + plog(logctx, logfn, "returned key: %s %s", + fingerprint, skey->comment); + sfree(fingerprint); + } + } + sfree(keylist); } break; @@ -325,14 +386,19 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) struct MD5Context md5c; int i, len; + plog(logctx, logfn, "request: SSH1_AGENTC_RSA_CHALLENGE"); + p += 4; i = ssh1_read_bignum(p, msgend - p, &reqkey.exponent); - if (i < 0) + if (i < 0) { + fail_reason = "request truncated before key exponent"; goto failure; + } p += i; i = ssh1_read_bignum(p, msgend - p, &reqkey.modulus); if (i < 0) { freebn(reqkey.exponent); + fail_reason = "request truncated before key modulus"; goto failure; } p += i; @@ -340,6 +406,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) if (i < 0) { freebn(reqkey.exponent); freebn(reqkey.modulus); + freebn(challenge); + fail_reason = "request truncated before challenge"; goto failure; } p += i; @@ -347,16 +415,36 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) freebn(reqkey.exponent); freebn(reqkey.modulus); freebn(challenge); + fail_reason = "request truncated before session id"; goto failure; } memcpy(response_source + 32, p, 16); p += 16; - if (msgend < p+4 || - GET_32BIT(p) != 1 || - (key = find234(rsakeys, &reqkey, NULL)) == NULL) { + if (msgend < p+4) { + freebn(reqkey.exponent); + freebn(reqkey.modulus); + freebn(challenge); + fail_reason = "request truncated before response type"; + goto failure; + } + if (GET_32BIT(p) != 1) { + freebn(reqkey.exponent); + freebn(reqkey.modulus); + freebn(challenge); + fail_reason = "response type other than 1 not supported"; + goto failure; + } + if (logfn) { + char fingerprint[128]; + reqkey.comment = NULL; + rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey); + plog(logctx, logfn, "requested key: %s", fingerprint); + } + if ((key = find234(rsakeys, &reqkey, NULL)) == NULL) { freebn(reqkey.exponent); freebn(reqkey.modulus); freebn(challenge); + fail_reason = "key not found"; goto failure; } response = rsadecrypt(challenge, key); @@ -380,6 +468,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) PUT_32BIT(ret, len - 4); ret[4] = SSH1_AGENT_RSA_RESPONSE; memcpy(ret + 5, response_md5, 16); + + plog(logctx, logfn, "reply: SSH1_AGENT_RSA_RESPONSE"); } break; case SSH2_AGENTC_SIGN_REQUEST: @@ -395,24 +485,41 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) unsigned char *signature; int datalen, siglen, len; - if (msgend < p+4) + plog(logctx, logfn, "request: SSH2_AGENTC_SIGN_REQUEST"); + + if (msgend < p+4) { + fail_reason = "request truncated before public key"; goto failure; + } b.len = toint(GET_32BIT(p)); - if (b.len < 0 || b.len > msgend - (p+4)) + if (b.len < 0 || b.len > msgend - (p+4)) { + fail_reason = "request truncated before public key"; goto failure; + } p += 4; b.blob = p; p += b.len; - if (msgend < p+4) + if (msgend < p+4) { + fail_reason = "request truncated before string to sign"; goto failure; + } datalen = toint(GET_32BIT(p)); p += 4; - if (datalen < 0 || datalen > msgend - p) + if (datalen < 0 || datalen > msgend - p) { + fail_reason = "request truncated before string to sign"; goto failure; + } data = p; + if (logfn) { + char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len); + plog(logctx, logfn, "requested key: %s", fingerprint); + sfree(fingerprint); + } key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm); - if (!key) + if (!key) { + fail_reason = "key not found"; goto failure; + } signature = key->alg->sign(key->data, (const char *)data, datalen, &siglen); len = 5 + 4 + siglen; @@ -421,6 +528,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) PUT_32BIT(ret + 5, siglen); memcpy(ret + 5 + 4, signature, siglen); sfree(signature); + + plog(logctx, logfn, "reply: SSH2_AGENT_SIGN_RESPONSE"); } break; case SSH1_AGENTC_ADD_RSA_IDENTITY: @@ -433,6 +542,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) char *comment; int n, commentlen; + plog(logctx, logfn, "request: SSH1_AGENTC_ADD_RSA_IDENTITY"); + key = snew(struct RSAKey); memset(key, 0, sizeof(struct RSAKey)); @@ -440,6 +551,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) if (n < 0) { freersakey(key); sfree(key); + fail_reason = "request truncated before public key"; goto failure; } p += n; @@ -448,30 +560,38 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) if (n < 0) { freersakey(key); sfree(key); + fail_reason = "request truncated before private key"; goto failure; } p += n; + /* SSH-1 names p and q the other way round, i.e. we have + * the inverse of p mod q and not of q mod p. We swap the + * names, because our internal RSA wants iqmp. */ + n = ssh1_read_bignum(p, msgend - p, &key->iqmp); /* p^-1 mod q */ if (n < 0) { freersakey(key); sfree(key); + fail_reason = "request truncated before iqmp"; goto failure; } p += n; - n = ssh1_read_bignum(p, msgend - p, &key->p); /* p */ + n = ssh1_read_bignum(p, msgend - p, &key->q); /* p */ if (n < 0) { freersakey(key); sfree(key); + fail_reason = "request truncated before p"; goto failure; } p += n; - n = ssh1_read_bignum(p, msgend - p, &key->q); /* q */ + n = ssh1_read_bignum(p, msgend - p, &key->p); /* q */ if (n < 0) { freersakey(key); sfree(key); + fail_reason = "request truncated before q"; goto failure; } p += n; @@ -479,6 +599,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) if (msgend < p+4) { freersakey(key); sfree(key); + fail_reason = "request truncated before key comment"; goto failure; } commentlen = toint(GET_32BIT(p)); @@ -486,6 +607,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) if (commentlen < 0 || commentlen > msgend - p) { freersakey(key); sfree(key); + fail_reason = "request truncated before key comment"; goto failure; } @@ -495,14 +617,25 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) comment[commentlen] = '\0'; key->comment = comment; } - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_FAILURE; + + if (logfn) { + char fingerprint[128]; + rsa_fingerprint(fingerprint, sizeof(fingerprint), key); + plog(logctx, logfn, "submitted key: %s", fingerprint); + } + if (add234(rsakeys, key) == key) { keylist_update(); + PUT_32BIT(ret, 1); ret[4] = SSH_AGENT_SUCCESS; + + plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } else { freersakey(key); sfree(key); + + fail_reason = "key already present"; + goto failure; } } break; @@ -518,37 +651,34 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) int alglen, commlen; int bloblen; + plog(logctx, logfn, "request: SSH2_AGENTC_ADD_IDENTITY"); - if (msgend < p+4) + if (msgend < p+4) { + fail_reason = "request truncated before key algorithm"; goto failure; + } alglen = toint(GET_32BIT(p)); p += 4; - if (alglen < 0 || alglen > msgend - p) + if (alglen < 0 || alglen > msgend - p) { + fail_reason = "request truncated before key algorithm"; goto failure; + } alg = (const char *)p; p += alglen; key = snew(struct ssh2_userkey); - /* Add further algorithm names here. */ - if (alglen == 7 && !memcmp(alg, "ssh-rsa", 7)) - key->alg = &ssh_rsa; - else if (alglen == 7 && !memcmp(alg, "ssh-dss", 7)) - key->alg = &ssh_dss; - else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp256", 19)) - key->alg = &ssh_ecdsa_nistp256; - else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp384", 19)) - key->alg = &ssh_ecdsa_nistp384; - else if (alglen == 19 && memcmp(alg, "ecdsa-sha2-nistp521", 19)) - key->alg = &ssh_ecdsa_nistp521; - else { + key->alg = find_pubkey_alg_len(alglen, alg); + if (!key->alg) { sfree(key); + fail_reason = "algorithm unknown"; goto failure; } bloblen = msgend - p; - key->data = key->alg->openssh_createkey(&p, &bloblen); + key->data = key->alg->openssh_createkey(key->alg, &p, &bloblen); if (!key->data) { sfree(key); + fail_reason = "key setup failed"; goto failure; } @@ -561,6 +691,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) if (msgend < p+4) { key->alg->freekey(key->data); sfree(key); + fail_reason = "request truncated before key comment"; goto failure; } commlen = toint(GET_32BIT(p)); @@ -569,6 +700,7 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) if (commlen < 0 || commlen > msgend - p) { key->alg->freekey(key->data); sfree(key); + fail_reason = "request truncated before key comment"; goto failure; } comment = snewn(commlen + 1, char); @@ -578,15 +710,26 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) } key->comment = comment; - PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_FAILURE; + if (logfn) { + char *fingerprint = ssh2_fingerprint(key->alg, key->data); + plog(logctx, logfn, "submitted key: %s %s", + fingerprint, key->comment); + sfree(fingerprint); + } + if (add234(ssh2keys, key) == key) { keylist_update(); + PUT_32BIT(ret, 1); ret[4] = SSH_AGENT_SUCCESS; + + plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } else { key->alg->freekey(key->data); sfree(key->comment); sfree(key); + + fail_reason = "key already present"; + goto failure; } } break; @@ -600,22 +743,39 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) struct RSAKey reqkey, *key; int n; + plog(logctx, logfn, "request: SSH1_AGENTC_REMOVE_RSA_IDENTITY"); + n = makekey(p, msgend - p, &reqkey, NULL, 0); - if (n < 0) + if (n < 0) { + fail_reason = "request truncated before public key"; goto failure; + } + + if (logfn) { + char fingerprint[128]; + reqkey.comment = NULL; + rsa_fingerprint(fingerprint, sizeof(fingerprint), &reqkey); + plog(logctx, logfn, "unwanted key: %s", fingerprint); + } key = find234(rsakeys, &reqkey, NULL); freebn(reqkey.exponent); freebn(reqkey.modulus); PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_FAILURE; if (key) { + plog(logctx, logfn, "found with comment: %s", key->comment); + del234(rsakeys, key); keylist_update(); freersakey(key); sfree(key); ret[4] = SSH_AGENT_SUCCESS; - } + + plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); + } else { + fail_reason = "key not found"; + goto failure; + } } break; case SSH2_AGENTC_REMOVE_IDENTITY: @@ -628,29 +788,44 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) struct ssh2_userkey *key; struct blob b; - if (msgend < p+4) + plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_IDENTITY"); + + if (msgend < p+4) { + fail_reason = "request truncated before public key"; goto failure; + } b.len = toint(GET_32BIT(p)); p += 4; - if (b.len < 0 || b.len > msgend - p) + if (b.len < 0 || b.len > msgend - p) { + fail_reason = "request truncated before public key"; goto failure; + } b.blob = p; p += b.len; + if (logfn) { + char *fingerprint = ssh2_fingerprint_blob(b.blob, b.len); + plog(logctx, logfn, "unwanted key: %s", fingerprint); + sfree(fingerprint); + } + key = find234(ssh2keys, &b, cmpkeys_ssh2_asymm); - if (!key) + if (!key) { + fail_reason = "key not found"; goto failure; + } + plog(logctx, logfn, "found with comment: %s", key->comment); + + del234(ssh2keys, key); + keylist_update(); + key->alg->freekey(key->data); + sfree(key); PUT_32BIT(ret, 1); - ret[4] = SSH_AGENT_FAILURE; - if (key) { - del234(ssh2keys, key); - keylist_update(); - key->alg->freekey(key->data); - sfree(key); - ret[4] = SSH_AGENT_SUCCESS; - } + ret[4] = SSH_AGENT_SUCCESS; + + plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } break; case SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES: @@ -660,6 +835,9 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) { struct RSAKey *rkey; + plog(logctx, logfn, "request:" + " SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES"); + while ((rkey = index234(rsakeys, 0)) != NULL) { del234(rsakeys, rkey); freersakey(rkey); @@ -669,6 +847,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) PUT_32BIT(ret, 1); ret[4] = SSH_AGENT_SUCCESS; + + plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } break; case SSH2_AGENTC_REMOVE_ALL_IDENTITIES: @@ -678,6 +858,8 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) { struct ssh2_userkey *skey; + plog(logctx, logfn, "request: SSH2_AGENTC_REMOVE_ALL_IDENTITIES"); + while ((skey = index234(ssh2keys, 0)) != NULL) { del234(ssh2keys, skey); skey->alg->freekey(skey->data); @@ -687,15 +869,22 @@ void *pageant_handle_msg(const void *msg, int msglen, int *outlen) PUT_32BIT(ret, 1); ret[4] = SSH_AGENT_SUCCESS; + + plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS"); } break; default: + plog(logctx, logfn, "request: unknown message type %d", type); + + fail_reason = "unrecognised message"; + /* fall through */ failure: /* * Unrecognised message. Return SSH_AGENT_FAILURE. */ PUT_32BIT(ret, 1); ret[4] = SSH_AGENT_FAILURE; + plog(logctx, logfn, "reply: SSH_AGENT_FAILURE (%s)", fail_reason); break; } @@ -714,6 +903,7 @@ void *pageant_failure_msg(int *outlen) void pageant_init(void) { + pageant_local = TRUE; rsakeys = newtree234(cmpkeys_rsa); ssh2keys = newtree234(cmpkeys_ssh2); } @@ -740,12 +930,12 @@ int pageant_count_ssh2_keys(void) int pageant_add_ssh1_key(struct RSAKey *rkey) { - return add234(rsakeys, rkey) != rkey; + return add234(rsakeys, rkey) == rkey; } int pageant_add_ssh2_key(struct ssh2_userkey *skey) { - return add234(ssh2keys, skey) != skey; + return add234(ssh2keys, skey) == skey; } int pageant_delete_ssh1_key(struct RSAKey *rkey) @@ -765,3 +955,877 @@ int pageant_delete_ssh2_key(struct ssh2_userkey *skey) assert(deleted == skey); return TRUE; } + +/* ---------------------------------------------------------------------- + * The agent plug. + */ + +/* + * Coroutine macros similar to, but simplified from, those in ssh.c. + */ +#define crBegin(v) { int *crLine = &v; switch(v) { case 0:; +#define crFinish(z) } *crLine = 0; return (z); } +#define crGetChar(c) do \ + { \ + while (len == 0) { \ + *crLine =__LINE__; return 1; case __LINE__:; \ + } \ + len--; \ + (c) = (unsigned char)*data++; \ + } while (0) + +struct pageant_conn_state { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + Socket connsock; + void *logctx; + pageant_logfn_t logfn; + unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN]; + unsigned len, got; + int real_packet; + int crLine; /* for coroutine in pageant_conn_receive */ +}; + +static int pageant_conn_closing(Plug plug, const char *error_msg, + int error_code, int calling_back) +{ + struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; + if (error_msg) + plog(pc->logctx, pc->logfn, "%p: error: %s", pc, error_msg); + else + plog(pc->logctx, pc->logfn, "%p: connection closed", pc); + sk_close(pc->connsock); + sfree(pc); + return 1; +} + +static void pageant_conn_sent(Plug plug, int bufsize) +{ + /* struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; */ + + /* + * We do nothing here, because we expect that there won't be a + * need to throttle and unthrottle the connection to an agent - + * clients will typically not send many requests, and will wait + * until they receive each reply before sending a new request. + */ +} + +static void pageant_conn_log(void *logctx, const char *fmt, va_list ap) +{ + /* Wrapper on pc->logfn that prefixes the connection identifier */ + struct pageant_conn_state *pc = (struct pageant_conn_state *)logctx; + char *formatted = dupvprintf(fmt, ap); + plog(pc->logctx, pc->logfn, "%p: %s", pc, formatted); + sfree(formatted); +} + +static int pageant_conn_receive(Plug plug, int urgent, char *data, int len) +{ + struct pageant_conn_state *pc = (struct pageant_conn_state *)plug; + char c; + + crBegin(pc->crLine); + + while (len > 0) { + pc->got = 0; + while (pc->got < 4) { + crGetChar(c); + pc->lenbuf[pc->got++] = c; + } + + pc->len = GET_32BIT(pc->lenbuf); + pc->got = 0; + pc->real_packet = (pc->len < AGENT_MAX_MSGLEN-4); + + while (pc->got < pc->len) { + crGetChar(c); + if (pc->real_packet) + pc->pktbuf[pc->got] = c; + pc->got++; + } + + { + void *reply; + int replylen; + + if (pc->real_packet) { + reply = pageant_handle_msg(pc->pktbuf, pc->len, &replylen, pc, + pc->logfn?pageant_conn_log:NULL); + } else { + plog(pc->logctx, pc->logfn, "%p: overlong message (%u)", + pc, pc->len); + plog(pc->logctx, pc->logfn, "%p: reply: SSH_AGENT_FAILURE " + "(message too long)", pc); + reply = pageant_failure_msg(&replylen); + } + sk_write(pc->connsock, reply, replylen); + smemclr(reply, replylen); + } + } + + crFinish(1); +} + +struct pageant_listen_state { + const struct plug_function_table *fn; + /* the above variable absolutely *must* be the first in this structure */ + + Socket listensock; + void *logctx; + pageant_logfn_t logfn; +}; + +static int pageant_listen_closing(Plug plug, const char *error_msg, + int error_code, int calling_back) +{ + struct pageant_listen_state *pl = (struct pageant_listen_state *)plug; + if (error_msg) + plog(pl->logctx, pl->logfn, "listening socket: error: %s", error_msg); + sk_close(pl->listensock); + pl->listensock = NULL; + return 1; +} + +static int pageant_listen_accepting(Plug plug, + accept_fn_t constructor, accept_ctx_t ctx) +{ + static const struct plug_function_table connection_fn_table = { + NULL, /* no log function, because that's for outgoing connections */ + pageant_conn_closing, + pageant_conn_receive, + pageant_conn_sent, + NULL /* no accepting function, because we've already done it */ + }; + struct pageant_listen_state *pl = (struct pageant_listen_state *)plug; + struct pageant_conn_state *pc; + const char *err; + char *peerinfo; + + pc = snew(struct pageant_conn_state); + pc->fn = &connection_fn_table; + pc->logfn = pl->logfn; + pc->logctx = pl->logctx; + pc->crLine = 0; + + pc->connsock = constructor(ctx, (Plug) pc); + if ((err = sk_socket_error(pc->connsock)) != NULL) { + sk_close(pc->connsock); + sfree(pc); + return TRUE; + } + + sk_set_frozen(pc->connsock, 0); + + peerinfo = sk_peer_info(pc->connsock); + if (peerinfo) { + plog(pl->logctx, pl->logfn, "%p: new connection from %s", + pc, peerinfo); + } else { + plog(pl->logctx, pl->logfn, "%p: new connection", pc); + } + + return 0; +} + +struct pageant_listen_state *pageant_listener_new(void) +{ + static const struct plug_function_table listener_fn_table = { + NULL, /* no log function, because that's for outgoing connections */ + pageant_listen_closing, + NULL, /* no receive function on a listening socket */ + NULL, /* no sent function on a listening socket */ + pageant_listen_accepting + }; + + struct pageant_listen_state *pl = snew(struct pageant_listen_state); + pl->fn = &listener_fn_table; + pl->logctx = NULL; + pl->logfn = NULL; + pl->listensock = NULL; + return pl; +} + +void pageant_listener_got_socket(struct pageant_listen_state *pl, Socket sock) +{ + pl->listensock = sock; +} + +void pageant_listener_set_logfn(struct pageant_listen_state *pl, + void *logctx, pageant_logfn_t logfn) +{ + pl->logctx = logctx; + pl->logfn = logfn; +} + +void pageant_listener_free(struct pageant_listen_state *pl) +{ + if (pl->listensock) + sk_close(pl->listensock); + sfree(pl); +} + +/* ---------------------------------------------------------------------- + * Code to perform agent operations either as a client, or within the + * same process as the running agent. + */ + +static tree234 *passphrases = NULL; + +/* + * After processing a list of filenames, we want to forget the + * passphrases. + */ +void pageant_forget_passphrases(void) +{ + if (!passphrases) /* in case we never set it up at all */ + return; + + while (count234(passphrases) > 0) { + char *pp = index234(passphrases, 0); + smemclr(pp, strlen(pp)); + delpos234(passphrases, 0); + free(pp); + } +} + +void *pageant_get_keylist1(int *length) +{ + void *ret; + + if (!pageant_local) { + unsigned char request[5], *response; + void *vresponse; + int resplen; + + request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES; + PUT_32BIT(request, 1); + + agent_query_synchronous(request, 5, &vresponse, &resplen); + response = vresponse; + if (resplen < 5 || response[4] != SSH1_AGENT_RSA_IDENTITIES_ANSWER) { + sfree(response); + return NULL; + } + + ret = snewn(resplen-5, unsigned char); + memcpy(ret, response+5, resplen-5); + sfree(response); + + if (length) + *length = resplen-5; + } else { + ret = pageant_make_keylist1(length); + } + return ret; +} + +void *pageant_get_keylist2(int *length) +{ + void *ret; + + if (!pageant_local) { + unsigned char request[5], *response; + void *vresponse; + int resplen; + + request[4] = SSH2_AGENTC_REQUEST_IDENTITIES; + PUT_32BIT(request, 1); + + agent_query_synchronous(request, 5, &vresponse, &resplen); + response = vresponse; + if (resplen < 5 || response[4] != SSH2_AGENT_IDENTITIES_ANSWER) { + sfree(response); + return NULL; + } + + ret = snewn(resplen-5, unsigned char); + memcpy(ret, response+5, resplen-5); + sfree(response); + + if (length) + *length = resplen-5; + } else { + ret = pageant_make_keylist2(length); + } + return ret; +} + +int pageant_add_keyfile(Filename *filename, const char *passphrase, + char **retstr) +{ + struct RSAKey *rkey = NULL; + struct ssh2_userkey *skey = NULL; + int needs_pass; + int ret; + int attempts; + char *comment; + const char *this_passphrase; + const char *error = NULL; + int type; + + if (!passphrases) { + passphrases = newtree234(NULL); + } + + *retstr = NULL; + + type = key_type(filename); + if (type != SSH_KEYTYPE_SSH1 && type != SSH_KEYTYPE_SSH2) { + *retstr = dupprintf("Couldn't load this key (%s)", + key_type_to_str(type)); + return PAGEANT_ACTION_FAILURE; + } + + /* + * See if the key is already loaded (in the primary Pageant, + * which may or may not be us). + */ + { + void *blob; + unsigned char *keylist, *p; + int i, nkeys, bloblen, keylistlen; + + if (type == SSH_KEYTYPE_SSH1) { + if (!rsakey_pubblob(filename, &blob, &bloblen, NULL, &error)) { + *retstr = dupprintf("Couldn't load private key (%s)", error); + return PAGEANT_ACTION_FAILURE; + } + keylist = pageant_get_keylist1(&keylistlen); + } else { + unsigned char *blob2; + blob = ssh2_userkey_loadpub(filename, NULL, &bloblen, + NULL, &error); + if (!blob) { + *retstr = dupprintf("Couldn't load private key (%s)", error); + return PAGEANT_ACTION_FAILURE; + } + /* For our purposes we want the blob prefixed with its length */ + blob2 = snewn(bloblen+4, unsigned char); + PUT_32BIT(blob2, bloblen); + memcpy(blob2 + 4, blob, bloblen); + sfree(blob); + blob = blob2; + + keylist = pageant_get_keylist2(&keylistlen); + } + if (keylist) { + if (keylistlen < 4) { + *retstr = dupstr("Received broken key list from agent"); + return PAGEANT_ACTION_FAILURE; + } + nkeys = toint(GET_32BIT(keylist)); + if (nkeys < 0) { + *retstr = dupstr("Received broken key list from agent"); + return PAGEANT_ACTION_FAILURE; + } + p = keylist + 4; + keylistlen -= 4; + + for (i = 0; i < nkeys; i++) { + if (!memcmp(blob, p, bloblen)) { + /* Key is already present; we can now leave. */ + sfree(keylist); + sfree(blob); + return PAGEANT_ACTION_OK; + } + /* Now skip over public blob */ + if (type == SSH_KEYTYPE_SSH1) { + int n = rsa_public_blob_len(p, keylistlen); + if (n < 0) { + *retstr = dupstr("Received broken key list from agent"); + sfree(keylist); + sfree(blob); + return PAGEANT_ACTION_FAILURE; + } + p += n; + keylistlen -= n; + } else { + int n; + if (keylistlen < 4) { + *retstr = dupstr("Received broken key list from agent"); + sfree(keylist); + sfree(blob); + return PAGEANT_ACTION_FAILURE; + } + n = GET_32BIT(p); + p += 4; + keylistlen -= 4; + + if (n < 0 || n > keylistlen) { + *retstr = dupstr("Received broken key list from agent"); + sfree(keylist); + sfree(blob); + return PAGEANT_ACTION_FAILURE; + } + p += n; + keylistlen -= n; + } + /* Now skip over comment field */ + { + int n; + if (keylistlen < 4) { + *retstr = dupstr("Received broken key list from agent"); + sfree(keylist); + sfree(blob); + return PAGEANT_ACTION_FAILURE; + } + n = GET_32BIT(p); + p += 4; + keylistlen -= 4; + + if (n < 0 || n > keylistlen) { + *retstr = dupstr("Received broken key list from agent"); + sfree(keylist); + sfree(blob); + return PAGEANT_ACTION_FAILURE; + } + p += n; + keylistlen -= n; + } + } + + sfree(keylist); + } + + sfree(blob); + } + + error = NULL; + if (type == SSH_KEYTYPE_SSH1) + needs_pass = rsakey_encrypted(filename, &comment); + else + needs_pass = ssh2_userkey_encrypted(filename, &comment); + attempts = 0; + if (type == SSH_KEYTYPE_SSH1) + rkey = snew(struct RSAKey); + + /* + * Loop round repeatedly trying to load the key, until we either + * succeed, fail for some serious reason, or run out of + * passphrases to try. + */ + while (1) { + if (needs_pass) { + + /* + * If we've been given a passphrase on input, try using + * it. Otherwise, try one from our tree234 of previously + * useful passphrases. + */ + if (passphrase) { + this_passphrase = (attempts == 0 ? passphrase : NULL); + } else { + this_passphrase = (const char *)index234(passphrases, attempts); + } + + if (!this_passphrase) { + /* + * Run out of passphrases to try. + */ + *retstr = comment; + sfree(rkey); + return PAGEANT_ACTION_NEED_PP; + } + } else + this_passphrase = ""; + + if (type == SSH_KEYTYPE_SSH1) + ret = loadrsakey(filename, rkey, this_passphrase, &error); + else { + skey = ssh2_load_userkey(filename, this_passphrase, &error); + if (skey == SSH2_WRONG_PASSPHRASE) + ret = -1; + else if (!skey) + ret = 0; + else + ret = 1; + } + + if (ret == 0) { + /* + * Failed to load the key file, for some reason other than + * a bad passphrase. + */ + *retstr = dupstr(error); + sfree(rkey); + return PAGEANT_ACTION_FAILURE; + } else if (ret == 1) { + /* + * Successfully loaded the key file. + */ + break; + } else { + /* + * Passphrase wasn't right; go round again. + */ + attempts++; + } + } + + /* + * If we get here, we've succesfully loaded the key into + * rkey/skey, but not yet added it to the agent. + */ + + /* + * If the key was successfully decrypted, save the passphrase for + * use with other keys we try to load. + */ + { + char *pp_copy = dupstr(this_passphrase); + if (addpos234(passphrases, pp_copy, 0) != pp_copy) { + /* No need; it was already there. */ + smemclr(pp_copy, strlen(pp_copy)); + sfree(pp_copy); + } + } + + if (comment) + sfree(comment); + + if (type == SSH_KEYTYPE_SSH1) { + if (!pageant_local) { + unsigned char *request, *response; + void *vresponse; + int reqlen, clen, resplen; + + clen = strlen(rkey->comment); + + reqlen = 4 + 1 + /* length, message type */ + 4 + /* bit count */ + ssh1_bignum_length(rkey->modulus) + + ssh1_bignum_length(rkey->exponent) + + ssh1_bignum_length(rkey->private_exponent) + + ssh1_bignum_length(rkey->iqmp) + + ssh1_bignum_length(rkey->p) + + ssh1_bignum_length(rkey->q) + 4 + clen /* comment */ + ; + + request = snewn(reqlen, unsigned char); + + request[4] = SSH1_AGENTC_ADD_RSA_IDENTITY; + reqlen = 5; + PUT_32BIT(request + reqlen, bignum_bitcount(rkey->modulus)); + reqlen += 4; + reqlen += ssh1_write_bignum(request + reqlen, rkey->modulus); + reqlen += ssh1_write_bignum(request + reqlen, rkey->exponent); + reqlen += + ssh1_write_bignum(request + reqlen, + rkey->private_exponent); + reqlen += ssh1_write_bignum(request + reqlen, rkey->iqmp); + reqlen += ssh1_write_bignum(request + reqlen, rkey->p); + reqlen += ssh1_write_bignum(request + reqlen, rkey->q); + PUT_32BIT(request + reqlen, clen); + memcpy(request + reqlen + 4, rkey->comment, clen); + reqlen += 4 + clen; + PUT_32BIT(request, reqlen - 4); + + agent_query_synchronous(request, reqlen, &vresponse, &resplen); + response = vresponse; + if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) { + *retstr = dupstr("The already running Pageant " + "refused to add the key."); + freersakey(rkey); + sfree(rkey); + sfree(request); + sfree(response); + return PAGEANT_ACTION_FAILURE; + } + freersakey(rkey); + sfree(rkey); + sfree(request); + sfree(response); + } else { + if (!pageant_add_ssh1_key(rkey)) { + freersakey(rkey); + sfree(rkey); /* already present, don't waste RAM */ + } + } + } else { + if (!pageant_local) { + unsigned char *request, *response; + void *vresponse; + int reqlen, alglen, clen, keybloblen, resplen; + alglen = strlen(skey->alg->name); + clen = strlen(skey->comment); + + keybloblen = skey->alg->openssh_fmtkey(skey->data, NULL, 0); + + reqlen = 4 + 1 + /* length, message type */ + 4 + alglen + /* algorithm name */ + keybloblen + /* key data */ + 4 + clen /* comment */ + ; + + request = snewn(reqlen, unsigned char); + + request[4] = SSH2_AGENTC_ADD_IDENTITY; + reqlen = 5; + PUT_32BIT(request + reqlen, alglen); + reqlen += 4; + memcpy(request + reqlen, skey->alg->name, alglen); + reqlen += alglen; + reqlen += skey->alg->openssh_fmtkey(skey->data, + request + reqlen, + keybloblen); + PUT_32BIT(request + reqlen, clen); + memcpy(request + reqlen + 4, skey->comment, clen); + reqlen += clen + 4; + PUT_32BIT(request, reqlen - 4); + + agent_query_synchronous(request, reqlen, &vresponse, &resplen); + response = vresponse; + if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) { + *retstr = dupstr("The already running Pageant " + "refused to add the key."); + sfree(request); + sfree(response); + return PAGEANT_ACTION_FAILURE; + } + + sfree(request); + sfree(response); + } else { + if (!pageant_add_ssh2_key(skey)) { + skey->alg->freekey(skey->data); + sfree(skey); /* already present, don't waste RAM */ + } + } + } + return PAGEANT_ACTION_OK; +} + +int pageant_enum_keys(pageant_key_enum_fn_t callback, void *callback_ctx, + char **retstr) +{ + unsigned char *keylist, *p; + int i, nkeys, keylistlen; + char *comment; + struct pageant_pubkey cbkey; + + keylist = pageant_get_keylist1(&keylistlen); + if (keylistlen < 4) { + *retstr = dupstr("Received broken SSH-1 key list from agent"); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + nkeys = toint(GET_32BIT(keylist)); + if (nkeys < 0) { + *retstr = dupstr("Received broken SSH-1 key list from agent"); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + p = keylist + 4; + keylistlen -= 4; + + for (i = 0; i < nkeys; i++) { + struct RSAKey rkey; + char fingerprint[128]; + int n; + + /* public blob and fingerprint */ + memset(&rkey, 0, sizeof(rkey)); + n = makekey(p, keylistlen, &rkey, NULL, 0); + if (n < 0 || n > keylistlen) { + freersakey(&rkey); + *retstr = dupstr("Received broken SSH-1 key list from agent"); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + p += n, keylistlen -= n; + rsa_fingerprint(fingerprint, sizeof(fingerprint), &rkey); + + /* comment */ + if (keylistlen < 4) { + *retstr = dupstr("Received broken SSH-1 key list from agent"); + freersakey(&rkey); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + n = toint(GET_32BIT(p)); + p += 4, keylistlen -= 4; + if (n < 0 || keylistlen < n) { + *retstr = dupstr("Received broken SSH-1 key list from agent"); + freersakey(&rkey); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + comment = dupprintf("%.*s", (int)n, (const char *)p); + p += n, keylistlen -= n; + + cbkey.blob = rsa_public_blob(&rkey, &cbkey.bloblen); + cbkey.comment = comment; + cbkey.ssh_version = 1; + callback(callback_ctx, fingerprint, comment, &cbkey); + sfree(cbkey.blob); + freersakey(&rkey); + sfree(comment); + } + + sfree(keylist); + + if (keylistlen != 0) { + *retstr = dupstr("Received broken SSH-1 key list from agent"); + return PAGEANT_ACTION_FAILURE; + } + + keylist = pageant_get_keylist2(&keylistlen); + if (keylistlen < 4) { + *retstr = dupstr("Received broken SSH-2 key list from agent"); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + nkeys = toint(GET_32BIT(keylist)); + if (nkeys < 0) { + *retstr = dupstr("Received broken SSH-2 key list from agent"); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + p = keylist + 4; + keylistlen -= 4; + + for (i = 0; i < nkeys; i++) { + char *fingerprint; + int n; + + /* public blob */ + if (keylistlen < 4) { + *retstr = dupstr("Received broken SSH-2 key list from agent"); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + n = toint(GET_32BIT(p)); + p += 4, keylistlen -= 4; + if (n < 0 || keylistlen < n) { + *retstr = dupstr("Received broken SSH-2 key list from agent"); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + fingerprint = ssh2_fingerprint_blob(p, n); + cbkey.blob = p; + cbkey.bloblen = n; + p += n, keylistlen -= n; + + /* comment */ + if (keylistlen < 4) { + *retstr = dupstr("Received broken SSH-2 key list from agent"); + sfree(fingerprint); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + n = toint(GET_32BIT(p)); + p += 4, keylistlen -= 4; + if (n < 0 || keylistlen < n) { + *retstr = dupstr("Received broken SSH-2 key list from agent"); + sfree(fingerprint); + sfree(keylist); + return PAGEANT_ACTION_FAILURE; + } + comment = dupprintf("%.*s", (int)n, (const char *)p); + p += n, keylistlen -= n; + + cbkey.ssh_version = 2; + cbkey.comment = comment; + callback(callback_ctx, fingerprint, comment, &cbkey); + sfree(fingerprint); + sfree(comment); + } + + sfree(keylist); + + if (keylistlen != 0) { + *retstr = dupstr("Received broken SSH-1 key list from agent"); + return PAGEANT_ACTION_FAILURE; + } + + return PAGEANT_ACTION_OK; +} + +int pageant_delete_key(struct pageant_pubkey *key, char **retstr) +{ + unsigned char *request, *response; + int reqlen, resplen, ret; + void *vresponse; + + if (key->ssh_version == 1) { + reqlen = 5 + key->bloblen; + request = snewn(reqlen, unsigned char); + PUT_32BIT(request, reqlen - 4); + request[4] = SSH1_AGENTC_REMOVE_RSA_IDENTITY; + memcpy(request + 5, key->blob, key->bloblen); + } else { + reqlen = 9 + key->bloblen; + request = snewn(reqlen, unsigned char); + PUT_32BIT(request, reqlen - 4); + request[4] = SSH2_AGENTC_REMOVE_IDENTITY; + PUT_32BIT(request + 5, key->bloblen); + memcpy(request + 9, key->blob, key->bloblen); + } + + agent_query_synchronous(request, reqlen, &vresponse, &resplen); + response = vresponse; + if (resplen < 5 || response[4] != SSH_AGENT_SUCCESS) { + *retstr = dupstr("Agent failed to delete key"); + ret = PAGEANT_ACTION_FAILURE; + } else { + *retstr = NULL; + ret = PAGEANT_ACTION_OK; + } + sfree(request); + sfree(response); + return ret; +} + +int pageant_delete_all_keys(char **retstr) +{ + unsigned char request[5], *response; + int reqlen, resplen, success; + void *vresponse; + + PUT_32BIT(request, 1); + request[4] = SSH2_AGENTC_REMOVE_ALL_IDENTITIES; + reqlen = 5; + agent_query_synchronous(request, reqlen, &vresponse, &resplen); + response = vresponse; + success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS); + sfree(response); + if (!success) { + *retstr = dupstr("Agent failed to delete SSH-2 keys"); + return PAGEANT_ACTION_FAILURE; + } + + PUT_32BIT(request, 1); + request[4] = SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES; + reqlen = 5; + agent_query_synchronous(request, reqlen, &vresponse, &resplen); + response = vresponse; + success = (resplen >= 4 && response[4] == SSH_AGENT_SUCCESS); + sfree(response); + if (!success) { + *retstr = dupstr("Agent failed to delete SSH-1 keys"); + return PAGEANT_ACTION_FAILURE; + } + + *retstr = NULL; + return PAGEANT_ACTION_OK; +} + +struct pageant_pubkey *pageant_pubkey_copy(struct pageant_pubkey *key) +{ + struct pageant_pubkey *ret = snew(struct pageant_pubkey); + ret->blob = snewn(key->bloblen, unsigned char); + memcpy(ret->blob, key->blob, key->bloblen); + ret->bloblen = key->bloblen; + ret->comment = key->comment ? dupstr(key->comment) : NULL; + ret->ssh_version = key->ssh_version; + return ret; +} + +void pageant_pubkey_free(struct pageant_pubkey *key) +{ + sfree(key->comment); + sfree(key->blob); + sfree(key); +}