return 0; /* unreachable, but placate optimiser */
}
+static int pageant_local = FALSE;
+
/*
* rsakeys stores SSH-1 RSA keys. ssh2keys stores all SSH-2 keys.
*/
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) {
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;
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;
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;
if (i < 0) {
freebn(reqkey.exponent);
freebn(reqkey.modulus);
+ freebn(challenge);
+ fail_reason = "request truncated before challenge";
goto failure;
}
p += i;
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);
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:
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;
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:
char *comment;
int n, commentlen;
+ plog(logctx, logfn, "request: SSH1_AGENTC_ADD_RSA_IDENTITY");
+
key = snew(struct RSAKey);
memset(key, 0, sizeof(struct RSAKey));
if (n < 0) {
freersakey(key);
sfree(key);
+ fail_reason = "request truncated before public key";
goto failure;
}
p += n;
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;
if (msgend < p+4) {
freersakey(key);
sfree(key);
+ fail_reason = "request truncated before key comment";
goto failure;
}
commentlen = toint(GET_32BIT(p));
if (commentlen < 0 || commentlen > msgend - p) {
freersakey(key);
sfree(key);
+ fail_reason = "request truncated before key comment";
goto failure;
}
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;
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;
}
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));
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);
}
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;
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:
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:
{
struct RSAKey *rkey;
+ plog(logctx, logfn, "request:"
+ " SSH1_AGENTC_REMOVE_ALL_RSA_IDENTITIES");
+
while ((rkey = index234(rsakeys, 0)) != NULL) {
del234(rsakeys, rkey);
freersakey(rkey);
PUT_32BIT(ret, 1);
ret[4] = SSH_AGENT_SUCCESS;
+
+ plog(logctx, logfn, "reply: SSH_AGENT_SUCCESS");
}
break;
case SSH2_AGENTC_REMOVE_ALL_IDENTITIES:
{
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);
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;
}
void pageant_init(void)
{
+ pageant_local = TRUE;
rsakeys = newtree234(cmpkeys_rsa);
ssh2keys = newtree234(cmpkeys_ssh2);
}
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)
Socket connsock;
void *logctx;
- void (*logfn)(void *logctx, const char *fmt, ...);
+ pageant_logfn_t logfn;
unsigned char lenbuf[4], pktbuf[AGENT_MAX_MSGLEN];
unsigned len, got;
int real_packet;
int error_code, int calling_back)
{
struct pageant_conn_state *pc = (struct pageant_conn_state *)plug;
- if (error_msg && pc->logfn)
- pc->logfn(pc->logctx, "Pageant connection socket: %s", error_msg);
+ 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_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;
int replylen;
if (pc->real_packet) {
- reply = pageant_handle_msg(pc->pktbuf, pc->len, &replylen);
+ 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);
Socket listensock;
void *logctx;
- void (*logfn)(void *logctx, const char *fmt, ...);
+ 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 && pl->logfn)
- pl->logfn(pl->logctx, "Pageant listening socket: %s", error_msg);
+ if (error_msg)
+ plog(pl->logctx, pl->logfn, "listening socket: error: %s", error_msg);
sk_close(pl->listensock);
pl->listensock = NULL;
return 1;
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;
sk_set_frozen(pc->connsock, 0);
- /* FIXME: can we get any useful peer id info? */
- if (pl->logfn)
- pl->logfn(pl->logctx, "Pageant socket connected");
+ 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 *logctx, void (*logfn)(void *logctx, const char *fmt, ...))
+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 */
struct pageant_listen_state *pl = snew(struct pageant_listen_state);
pl->fn = &listener_fn_table;
- pl->logctx = logctx;
- pl->logfn = logfn;
+ pl->logctx = NULL;
+ pl->logfn = NULL;
pl->listensock = NULL;
return pl;
}
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");
+ sfree(keylist);
+ sfree(blob);
+ return PAGEANT_ACTION_FAILURE;
+ }
+ nkeys = toint(GET_32BIT(keylist));
+ if (nkeys < 0) {
+ *retstr = dupstr("Received broken key list from agent");
+ sfree(keylist);
+ sfree(blob);
+ 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);
+}