mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 01:48:00 +00:00
Pageant core: initial deferred decryption facility.
This adds an extension request to the agent protocol (named in our private namespace, naturally) which allows you to upload a key file in the form of a string containing an entire .ppk file. If the key is encrypted, then Pageant stores it in such a way that it will show up in the key list, and on the first attempt to sign something with it, prompt for a passphrase (if it can), decrypt the key, and then answer the request. There are a lot of rough edges still to deal with, but this is good enough to have successfully answered one request, so it's a start.
This commit is contained in:
parent
4d05eb424d
commit
d8337e2070
326
pageant.c
326
pageant.c
@ -29,6 +29,10 @@ void random_read(void *buf, size_t size)
|
|||||||
|
|
||||||
static bool pageant_local = false;
|
static bool pageant_local = false;
|
||||||
|
|
||||||
|
struct PageantClientDialogId {
|
||||||
|
int dummy;
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct PageantKeySort PageantKeySort;
|
typedef struct PageantKeySort PageantKeySort;
|
||||||
typedef struct PageantKey PageantKey;
|
typedef struct PageantKey PageantKey;
|
||||||
typedef struct PageantAsyncOp PageantAsyncOp;
|
typedef struct PageantAsyncOp PageantAsyncOp;
|
||||||
@ -99,7 +103,10 @@ struct PageantKey {
|
|||||||
RSAKey *rkey; /* if ssh_version == 1 */
|
RSAKey *rkey; /* if ssh_version == 1 */
|
||||||
ssh2_userkey *skey; /* if ssh_version == 2 */
|
ssh2_userkey *skey; /* if ssh_version == 2 */
|
||||||
};
|
};
|
||||||
|
strbuf *encrypted_key_file;
|
||||||
|
bool decryption_prompt_active;
|
||||||
PageantKeyRequestNode blocked_requests;
|
PageantKeyRequestNode blocked_requests;
|
||||||
|
PageantClientDialogId dlgid;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct PageantSignOp PageantSignOp;
|
typedef struct PageantSignOp PageantSignOp;
|
||||||
@ -115,6 +122,7 @@ struct PageantSignOp {
|
|||||||
|
|
||||||
static void failure(PageantClient *pc, PageantClientRequestId *reqid,
|
static void failure(PageantClient *pc, PageantClientRequestId *reqid,
|
||||||
strbuf *sb, const char *fmt, ...);
|
strbuf *sb, const char *fmt, ...);
|
||||||
|
static void fail_requests_for_key(PageantKey *pk, const char *reason);
|
||||||
|
|
||||||
static void pk_free(PageantKey *pk)
|
static void pk_free(PageantKey *pk)
|
||||||
{
|
{
|
||||||
@ -129,19 +137,9 @@ static void pk_free(PageantKey *pk)
|
|||||||
ssh_key_free(pk->skey->key);
|
ssh_key_free(pk->skey->key);
|
||||||
sfree(pk->skey);
|
sfree(pk->skey);
|
||||||
}
|
}
|
||||||
while (pk->blocked_requests.next != &pk->blocked_requests) {
|
if (pk->encrypted_key_file) strbuf_free(pk->encrypted_key_file);
|
||||||
PageantSignOp *so = container_of(pk->blocked_requests.next,
|
fail_requests_for_key(pk, "key deleted from Pageant while signing "
|
||||||
PageantSignOp, pkr);
|
"request was pending");
|
||||||
so->pkr.next->prev = so->pkr.prev;
|
|
||||||
so->pkr.prev->next = so->pkr.next;
|
|
||||||
strbuf *sb = strbuf_new();
|
|
||||||
failure(so->pao.info->pc, so->pao.reqid, sb,
|
|
||||||
"key deleted from Pageant while signing request was pending");
|
|
||||||
pageant_client_got_response(so->pao.info->pc, so->pao.reqid,
|
|
||||||
ptrlen_from_strbuf(sb));
|
|
||||||
strbuf_free(sb);
|
|
||||||
pageant_async_op_unlink_and_free(&so->pao);
|
|
||||||
}
|
|
||||||
sfree(pk);
|
sfree(pk);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,27 +329,81 @@ static void signop_free(PageantAsyncOp *pao)
|
|||||||
sfree(so);
|
sfree(so);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool request_passphrase(PageantClient *pc, PageantKey *pk)
|
||||||
|
{
|
||||||
|
if (!pk->decryption_prompt_active) {
|
||||||
|
strbuf *sb = strbuf_new();
|
||||||
|
strbuf_catf(sb, "Enter passphrase to decrypt key '%s'", pk->comment);
|
||||||
|
bool created_dlg = pageant_client_ask_passphrase(
|
||||||
|
pc, &pk->dlgid, sb->s);
|
||||||
|
strbuf_free(sb);
|
||||||
|
|
||||||
|
if (!created_dlg)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
pk->decryption_prompt_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static void signop_coroutine(PageantAsyncOp *pao)
|
static void signop_coroutine(PageantAsyncOp *pao)
|
||||||
{
|
{
|
||||||
PageantSignOp *so = container_of(pao, PageantSignOp, pao);
|
PageantSignOp *so = container_of(pao, PageantSignOp, pao);
|
||||||
|
strbuf *response;
|
||||||
|
|
||||||
crBegin(so->crLine);
|
crBegin(so->crLine);
|
||||||
|
|
||||||
/*
|
if (!so->pk->skey) {
|
||||||
* If we want to request a user interaction, we should set it up;
|
assert(so->pk->encrypted_key_file);
|
||||||
* arrange that when it finishes, it re-queues
|
|
||||||
* pageant_async_op_callback; and then crReturnV so that we resume
|
if (!request_passphrase(so->pao.info->pc, so->pk)) {
|
||||||
* from after that.
|
response = strbuf_new();
|
||||||
*/
|
failure(so->pao.info->pc, so->pao.reqid, response,
|
||||||
if (0) crReturnV;
|
"on-demand decryption could not prompt for a "
|
||||||
|
"passphrase");
|
||||||
|
goto respond;
|
||||||
|
}
|
||||||
|
|
||||||
|
so->pkr.prev = so->pk->blocked_requests.prev;
|
||||||
|
so->pkr.next = &so->pk->blocked_requests;
|
||||||
|
so->pkr.prev->next = &so->pkr;
|
||||||
|
so->pkr.next->prev = &so->pkr;
|
||||||
|
|
||||||
|
crReturnV;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t supported_flags = ssh_key_alg(so->pk->skey->key)->supported_flags;
|
||||||
|
if (so->flags & ~supported_flags) {
|
||||||
|
/*
|
||||||
|
* We MUST reject any message containing flags we don't
|
||||||
|
* understand.
|
||||||
|
*/
|
||||||
|
response = strbuf_new();
|
||||||
|
failure(so->pao.info->pc, so->pao.reqid, response,
|
||||||
|
"unsupported flag bits 0x%08"PRIx32,
|
||||||
|
so->flags & ~supported_flags);
|
||||||
|
goto respond;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *invalid = ssh_key_invalid(so->pk->skey->key, so->flags);
|
||||||
|
if (invalid) {
|
||||||
|
response = strbuf_new();
|
||||||
|
failure(so->pao.info->pc, so->pao.reqid, response,
|
||||||
|
"key invalid: %s", invalid);
|
||||||
|
sfree(invalid);
|
||||||
|
goto respond;
|
||||||
|
}
|
||||||
|
|
||||||
strbuf *signature = strbuf_new();
|
strbuf *signature = strbuf_new();
|
||||||
ssh_key_sign(so->pk->skey->key, ptrlen_from_strbuf(so->data_to_sign),
|
ssh_key_sign(so->pk->skey->key, ptrlen_from_strbuf(so->data_to_sign),
|
||||||
so->flags, BinarySink_UPCAST(signature));
|
so->flags, BinarySink_UPCAST(signature));
|
||||||
|
|
||||||
strbuf *response = strbuf_new();
|
response = strbuf_new();
|
||||||
put_byte(response, SSH2_AGENT_SIGN_RESPONSE);
|
put_byte(response, SSH2_AGENT_SIGN_RESPONSE);
|
||||||
put_stringsb(response, signature);
|
put_stringsb(response, signature);
|
||||||
|
|
||||||
|
respond:
|
||||||
pageant_client_got_response(so->pao.info->pc, so->pao.reqid,
|
pageant_client_got_response(so->pao.info->pc, so->pao.reqid,
|
||||||
ptrlen_from_strbuf(response));
|
ptrlen_from_strbuf(response));
|
||||||
strbuf_free(response);
|
strbuf_free(response);
|
||||||
@ -365,6 +417,89 @@ static struct PageantAsyncOpVtable signop_vtable = {
|
|||||||
signop_free,
|
signop_free,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void fail_requests_for_key(PageantKey *pk, const char *reason)
|
||||||
|
{
|
||||||
|
while (pk->blocked_requests.next != &pk->blocked_requests) {
|
||||||
|
PageantSignOp *so = container_of(pk->blocked_requests.next,
|
||||||
|
PageantSignOp, pkr);
|
||||||
|
so->pkr.next->prev = so->pkr.prev;
|
||||||
|
so->pkr.prev->next = so->pkr.next;
|
||||||
|
strbuf *sb = strbuf_new();
|
||||||
|
failure(so->pao.info->pc, so->pao.reqid, sb, "%s", reason);
|
||||||
|
pageant_client_got_response(so->pao.info->pc, so->pao.reqid,
|
||||||
|
ptrlen_from_strbuf(sb));
|
||||||
|
strbuf_free(sb);
|
||||||
|
pageant_async_op_unlink_and_free(&so->pao);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unblock_requests_for_key(PageantKey *pk)
|
||||||
|
{
|
||||||
|
for (PageantKeyRequestNode *pkr = pk->blocked_requests.next;
|
||||||
|
pkr != &pk->blocked_requests; pkr = pkr->next) {
|
||||||
|
PageantSignOp *so = container_of(pk->blocked_requests.next,
|
||||||
|
PageantSignOp, pkr);
|
||||||
|
queue_toplevel_callback(pageant_async_op_callback, &so->pao);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pageant_passphrase_request_success(PageantClientDialogId *dlgid,
|
||||||
|
ptrlen passphrase)
|
||||||
|
{
|
||||||
|
PageantKey *pk = container_of(dlgid, PageantKey, dlgid);
|
||||||
|
|
||||||
|
if (!pk->skey) {
|
||||||
|
const char *error;
|
||||||
|
|
||||||
|
BinarySource src[1];
|
||||||
|
BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
|
||||||
|
pk->encrypted_key_file));
|
||||||
|
|
||||||
|
strbuf *ppsb = strbuf_new_nm();
|
||||||
|
put_datapl(ppsb, passphrase);
|
||||||
|
|
||||||
|
pk->skey = ppk_load_s(src, ppsb->s, &error);
|
||||||
|
|
||||||
|
strbuf_free(ppsb);
|
||||||
|
|
||||||
|
if (!pk->skey) {
|
||||||
|
fail_requests_for_key(pk, "unable to decrypt key");
|
||||||
|
return;
|
||||||
|
} else if (pk->skey == SSH2_WRONG_PASSPHRASE) {
|
||||||
|
/*
|
||||||
|
* Find a PageantClient to use for another attempt at
|
||||||
|
* request_passphrase.
|
||||||
|
*/
|
||||||
|
PageantKeyRequestNode *pkr = pk->blocked_requests.next;
|
||||||
|
if (pkr == &pk->blocked_requests) {
|
||||||
|
/*
|
||||||
|
* Special case: if all the requests have gone away at
|
||||||
|
* this point, we need not bother putting up a request
|
||||||
|
* at all any more.
|
||||||
|
*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PageantSignOp *so = container_of(pk->blocked_requests.next,
|
||||||
|
PageantSignOp, pkr);
|
||||||
|
|
||||||
|
if (!request_passphrase(so->pao.info->pc, pk)) {
|
||||||
|
fail_requests_for_key(pk, "unable to continue creating "
|
||||||
|
"passphrase prompts");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unblock_requests_for_key(pk);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pageant_passphrase_request_refused(PageantClientDialogId *dlgid)
|
||||||
|
{
|
||||||
|
PageantKey *pk = container_of(dlgid, PageantKey, dlgid);
|
||||||
|
unblock_requests_for_key(pk);
|
||||||
|
}
|
||||||
|
|
||||||
typedef struct PageantImmOp PageantImmOp;
|
typedef struct PageantImmOp PageantImmOp;
|
||||||
struct PageantImmOp {
|
struct PageantImmOp {
|
||||||
int crLine;
|
int crLine;
|
||||||
@ -399,6 +534,8 @@ static struct PageantAsyncOpVtable immop_vtable = {
|
|||||||
immop_free,
|
immop_free,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define PUTTYEXT(base) PTRLEN_LITERAL(base "@putty.projects.tartarus.org")
|
||||||
|
|
||||||
void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
|
void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
|
||||||
ptrlen msgpl)
|
ptrlen msgpl)
|
||||||
{
|
{
|
||||||
@ -545,7 +682,7 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
|
|||||||
{
|
{
|
||||||
PageantKey *pk;
|
PageantKey *pk;
|
||||||
ptrlen keyblob, sigdata;
|
ptrlen keyblob, sigdata;
|
||||||
uint32_t flags, supported_flags;
|
uint32_t flags;
|
||||||
|
|
||||||
pageant_client_log(pc, reqid, "request: SSH2_AGENTC_SIGN_REQUEST");
|
pageant_client_log(pc, reqid, "request: SSH2_AGENTC_SIGN_REQUEST");
|
||||||
|
|
||||||
@ -587,24 +724,6 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
|
|||||||
else
|
else
|
||||||
pageant_client_log(pc, reqid, "no signature flags");
|
pageant_client_log(pc, reqid, "no signature flags");
|
||||||
|
|
||||||
supported_flags = ssh_key_alg(pk->skey->key)->supported_flags;
|
|
||||||
if (flags & ~supported_flags) {
|
|
||||||
/*
|
|
||||||
* We MUST reject any message containing flags we
|
|
||||||
* don't understand.
|
|
||||||
*/
|
|
||||||
failure(pc, reqid, sb, "unsupported flag bits 0x%08"PRIx32,
|
|
||||||
flags & ~supported_flags);
|
|
||||||
goto responded;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *invalid = ssh_key_invalid(pk->skey->key, flags);
|
|
||||||
if (invalid) {
|
|
||||||
failure(pc, reqid, sb, "key invalid: %s", invalid);
|
|
||||||
sfree(invalid);
|
|
||||||
goto responded;
|
|
||||||
}
|
|
||||||
|
|
||||||
strbuf_free(sb); /* no immediate response */
|
strbuf_free(sb); /* no immediate response */
|
||||||
|
|
||||||
PageantSignOp *so = snew(PageantSignOp);
|
PageantSignOp *so = snew(PageantSignOp);
|
||||||
@ -818,7 +937,7 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
|
|||||||
}
|
}
|
||||||
|
|
||||||
pageant_client_log(pc, reqid,
|
pageant_client_log(pc, reqid,
|
||||||
"found with comment: %s", pk->skey->comment);
|
"found with comment: %s", pk->comment);
|
||||||
|
|
||||||
del234(keytree, pk);
|
del234(keytree, pk);
|
||||||
keylist_update();
|
keylist_update();
|
||||||
@ -860,6 +979,130 @@ void pageant_handle_msg(PageantClient *pc, PageantClientRequestId *reqid,
|
|||||||
pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS");
|
pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case SSH2_AGENTC_EXTENSION:
|
||||||
|
{
|
||||||
|
ptrlen exttype = get_string(msg);
|
||||||
|
if (ptrlen_eq_ptrlen(exttype, PUTTYEXT("add-ppk"))) {
|
||||||
|
ptrlen keyfile = get_string(msg);
|
||||||
|
|
||||||
|
if (get_err(msg)) {
|
||||||
|
failure(pc, reqid, sb, "unable to decode request");
|
||||||
|
goto responded;
|
||||||
|
}
|
||||||
|
|
||||||
|
BinarySource src[1];
|
||||||
|
const char *error;
|
||||||
|
|
||||||
|
strbuf *public_blob = strbuf_new();
|
||||||
|
char *comment;
|
||||||
|
|
||||||
|
BinarySource_BARE_INIT_PL(src, keyfile);
|
||||||
|
if (!ppk_loadpub_s(src, NULL,
|
||||||
|
BinarySink_UPCAST(public_blob),
|
||||||
|
&comment, &error)) {
|
||||||
|
failure(pc, reqid, sb,
|
||||||
|
"add-ppk: failed to extract public key blob: %s",
|
||||||
|
error);
|
||||||
|
goto add_ppk_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pc->suppress_logging) {
|
||||||
|
char *fingerprint = ssh2_fingerprint_blob(
|
||||||
|
ptrlen_from_strbuf(public_blob));
|
||||||
|
pageant_client_log(pc, reqid, "add-ppk: %s %s",
|
||||||
|
fingerprint, comment);
|
||||||
|
sfree(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
BinarySource_BARE_INIT_PL(src, keyfile);
|
||||||
|
bool encrypted = ppk_encrypted_s(src, NULL);
|
||||||
|
|
||||||
|
if (!encrypted) {
|
||||||
|
/* If the key isn't encrypted, then we should just
|
||||||
|
* load and add it in the obvious way. */
|
||||||
|
BinarySource_BARE_INIT_PL(src, keyfile);
|
||||||
|
ssh2_userkey *skey = ppk_load_s(src, NULL, &error);
|
||||||
|
if (!skey) {
|
||||||
|
failure(pc, reqid, sb,
|
||||||
|
"add-ppk: failed to load private key: %s",
|
||||||
|
error);
|
||||||
|
} else if (pageant_add_ssh2_key(skey)) {
|
||||||
|
keylist_update();
|
||||||
|
put_byte(sb, SSH_AGENT_SUCCESS);
|
||||||
|
|
||||||
|
pageant_client_log(pc, reqid,
|
||||||
|
"reply: SSH_AGENT_SUCCESS"
|
||||||
|
" (loaded unencrypted PPK)");
|
||||||
|
} else {
|
||||||
|
failure(pc, reqid, sb, "key already present");
|
||||||
|
if (skey->key)
|
||||||
|
ssh_key_free(skey->key);
|
||||||
|
if (skey->comment)
|
||||||
|
sfree(skey->comment);
|
||||||
|
sfree(skey);
|
||||||
|
}
|
||||||
|
goto add_ppk_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
PageantKeySort sort =
|
||||||
|
keysort(2, ptrlen_from_strbuf(public_blob));
|
||||||
|
|
||||||
|
PageantKey *pk = find234(keytree, &sort, NULL);
|
||||||
|
if (pk) {
|
||||||
|
/*
|
||||||
|
* This public key blob already exists in the
|
||||||
|
* keytree. Add the encrypted key file to the
|
||||||
|
* existing record, if it doesn't have one already.
|
||||||
|
*/
|
||||||
|
if (!pk->encrypted_key_file) {
|
||||||
|
pk->encrypted_key_file = strbuf_new_nm();
|
||||||
|
put_datapl(pk->encrypted_key_file, keyfile);
|
||||||
|
|
||||||
|
put_byte(sb, SSH_AGENT_SUCCESS);
|
||||||
|
pageant_client_log(pc, reqid,
|
||||||
|
"reply: SSH_AGENT_SUCCESS (added"
|
||||||
|
" encrypted PPK to existing key"
|
||||||
|
" record)");
|
||||||
|
} else {
|
||||||
|
failure(pc, reqid, sb, "key already present");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* We're adding a new key record containing only
|
||||||
|
* an encrypted key file.
|
||||||
|
*/
|
||||||
|
PageantKey *pk = snew(PageantKey);
|
||||||
|
memset(pk, 0, sizeof(PageantKey));
|
||||||
|
pk->blocked_requests.next = pk->blocked_requests.prev =
|
||||||
|
&pk->blocked_requests;
|
||||||
|
pk->sort.ssh_version = 2;
|
||||||
|
pk->public_blob = public_blob;
|
||||||
|
public_blob = NULL;
|
||||||
|
pk->sort.public_blob = ptrlen_from_strbuf(pk->public_blob);
|
||||||
|
pk->comment = dupstr(comment);
|
||||||
|
pk->encrypted_key_file = strbuf_new_nm();
|
||||||
|
put_datapl(pk->encrypted_key_file, keyfile);
|
||||||
|
|
||||||
|
PageantKey *added = add234(keytree, pk);
|
||||||
|
assert(added == pk); (void)added;
|
||||||
|
|
||||||
|
put_byte(sb, SSH_AGENT_SUCCESS);
|
||||||
|
pageant_client_log(pc, reqid, "reply: SSH_AGENT_SUCCESS"
|
||||||
|
" (made new encrypted-only key"
|
||||||
|
" record)");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_ppk_cleanup:
|
||||||
|
if (public_blob)
|
||||||
|
strbuf_free(public_blob);
|
||||||
|
sfree(comment);
|
||||||
|
goto responded;
|
||||||
|
} else {
|
||||||
|
failure(pc, reqid, sb, "key not found");
|
||||||
|
goto responded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
pageant_client_log(pc, reqid, "request: unknown message type %d",
|
pageant_client_log(pc, reqid, "request: unknown message type %d",
|
||||||
type);
|
type);
|
||||||
@ -1045,7 +1288,6 @@ static bool pageant_conn_ask_passphrase(
|
|||||||
return pageant_listener_client_ask_passphrase(pcs->plc, dlgid, msg);
|
return pageant_listener_client_ask_passphrase(pcs->plc, dlgid, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static const struct PageantClientVtable pageant_connection_clientvt = {
|
static const struct PageantClientVtable pageant_connection_clientvt = {
|
||||||
pageant_conn_log,
|
pageant_conn_log,
|
||||||
pageant_conn_got_response,
|
pageant_conn_got_response,
|
||||||
|
1
ssh.h
1
ssh.h
@ -1497,6 +1497,7 @@ enum {
|
|||||||
#define SSH2_AGENTC_ADD_IDENTITY 17
|
#define SSH2_AGENTC_ADD_IDENTITY 17
|
||||||
#define SSH2_AGENTC_REMOVE_IDENTITY 18
|
#define SSH2_AGENTC_REMOVE_IDENTITY 18
|
||||||
#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19
|
#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19
|
||||||
|
#define SSH2_AGENTC_EXTENSION 27
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Assorted other SSH-related enumerations.
|
* Assorted other SSH-related enumerations.
|
||||||
|
Loading…
Reference in New Issue
Block a user