mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
b4e1bca2c3
This is a sweeping change applied across the whole code base by a spot of Emacs Lisp. Now, everywhere I declare a vtable filled with function pointers (and the occasional const data member), all the members of the vtable structure are initialised by name using the '.fieldname = value' syntax introduced in C99. We were already using this syntax for a handful of things in the new key-generation progress report system, so it's not new to the code base as a whole. The advantage is that now, when a vtable only declares a subset of the available fields, I can initialise the rest to NULL or zero just by leaving them out. This is most dramatic in a couple of the outlying vtables in things like psocks (which has a ConnectionLayerVtable containing only one non-NULL method), but less dramatically, it means that the new 'flags' field in BackendVtable can be completely left out of every backend definition except for the SUPDUP one which defines it to a nonzero value. Similarly, the test_for_upstream method only used by SSH doesn't have to be mentioned in the rest of the backends; network Plugs for listening sockets don't have to explicitly null out 'receive' and 'sent', and vice versa for 'accepting', and so on. While I'm at it, I've normalised the declarations so they don't use the unnecessarily verbose 'struct' keyword. Also a handful of them weren't const; now they are.
372 lines
12 KiB
C
372 lines
12 KiB
C
/*
|
|
* Packet protocol layer for the server side of the SSH-2 userauth
|
|
* protocol (RFC 4252).
|
|
*/
|
|
|
|
#include <assert.h>
|
|
|
|
#include "putty.h"
|
|
#include "ssh.h"
|
|
#include "sshbpp.h"
|
|
#include "sshppl.h"
|
|
#include "sshcr.h"
|
|
#include "sshserver.h"
|
|
|
|
#ifndef NO_GSSAPI
|
|
#include "sshgssc.h"
|
|
#include "sshgss.h"
|
|
#endif
|
|
|
|
struct ssh2_userauth_server_state {
|
|
int crState;
|
|
|
|
PacketProtocolLayer *transport_layer, *successor_layer;
|
|
ptrlen session_id;
|
|
|
|
AuthPolicy *authpolicy;
|
|
const SshServerConfig *ssc;
|
|
|
|
ptrlen username, service, method;
|
|
unsigned methods, this_method;
|
|
bool partial_success;
|
|
|
|
AuthKbdInt *aki;
|
|
|
|
PacketProtocolLayer ppl;
|
|
};
|
|
|
|
static void ssh2_userauth_server_free(PacketProtocolLayer *);
|
|
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *);
|
|
|
|
static const PacketProtocolLayerVtable ssh2_userauth_server_vtable = {
|
|
.free = ssh2_userauth_server_free,
|
|
.process_queue = ssh2_userauth_server_process_queue,
|
|
.queued_data_size = ssh_ppl_default_queued_data_size,
|
|
.name = "ssh-userauth",
|
|
/* other methods are NULL */
|
|
};
|
|
|
|
static void free_auth_kbdint(AuthKbdInt *aki)
|
|
{
|
|
int i;
|
|
|
|
if (!aki)
|
|
return;
|
|
|
|
sfree(aki->title);
|
|
sfree(aki->instruction);
|
|
for (i = 0; i < aki->nprompts; i++)
|
|
sfree(aki->prompts[i].prompt);
|
|
sfree(aki->prompts);
|
|
sfree(aki);
|
|
}
|
|
|
|
PacketProtocolLayer *ssh2_userauth_server_new(
|
|
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy,
|
|
const SshServerConfig *ssc)
|
|
{
|
|
struct ssh2_userauth_server_state *s =
|
|
snew(struct ssh2_userauth_server_state);
|
|
memset(s, 0, sizeof(*s));
|
|
s->ppl.vt = &ssh2_userauth_server_vtable;
|
|
|
|
s->successor_layer = successor_layer;
|
|
s->authpolicy = authpolicy;
|
|
s->ssc = ssc;
|
|
|
|
return &s->ppl;
|
|
}
|
|
|
|
void ssh2_userauth_server_set_transport_layer(PacketProtocolLayer *userauth,
|
|
PacketProtocolLayer *transport)
|
|
{
|
|
struct ssh2_userauth_server_state *s =
|
|
container_of(userauth, struct ssh2_userauth_server_state, ppl);
|
|
s->transport_layer = transport;
|
|
}
|
|
|
|
static void ssh2_userauth_server_free(PacketProtocolLayer *ppl)
|
|
{
|
|
struct ssh2_userauth_server_state *s =
|
|
container_of(ppl, struct ssh2_userauth_server_state, ppl);
|
|
|
|
if (s->successor_layer)
|
|
ssh_ppl_free(s->successor_layer);
|
|
|
|
free_auth_kbdint(s->aki);
|
|
|
|
sfree(s);
|
|
}
|
|
|
|
static PktIn *ssh2_userauth_server_pop(struct ssh2_userauth_server_state *s)
|
|
{
|
|
return pq_pop(s->ppl.in_pq);
|
|
}
|
|
|
|
static void ssh2_userauth_server_add_session_id(
|
|
struct ssh2_userauth_server_state *s, strbuf *sigdata)
|
|
{
|
|
if (s->ppl.remote_bugs & BUG_SSH2_PK_SESSIONID) {
|
|
put_datapl(sigdata, s->session_id);
|
|
} else {
|
|
put_stringpl(sigdata, s->session_id);
|
|
}
|
|
}
|
|
|
|
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
|
|
{
|
|
struct ssh2_userauth_server_state *s =
|
|
container_of(ppl, struct ssh2_userauth_server_state, ppl);
|
|
PktIn *pktin;
|
|
PktOut *pktout;
|
|
|
|
crBegin(s->crState);
|
|
|
|
s->session_id = ssh2_transport_get_session_id(s->transport_layer);
|
|
|
|
if (s->ssc->banner.ptr) {
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_BANNER);
|
|
put_stringpl(pktout, s->ssc->banner);
|
|
put_stringz(pktout, ""); /* language tag */
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
}
|
|
|
|
while (1) {
|
|
crMaybeWaitUntilV((pktin = ssh2_userauth_server_pop(s)) != NULL);
|
|
if (pktin->type != SSH2_MSG_USERAUTH_REQUEST) {
|
|
ssh_proto_error(s->ppl.ssh, "Received unexpected packet when "
|
|
"expecting USERAUTH_REQUEST, type %d (%s)",
|
|
pktin->type,
|
|
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
|
|
s->ppl.bpp->pls->actx, pktin->type));
|
|
return;
|
|
}
|
|
|
|
s->username = get_string(pktin);
|
|
s->service = get_string(pktin);
|
|
s->method = get_string(pktin);
|
|
|
|
if (!ptrlen_eq_string(s->service, s->successor_layer->vt->name)) {
|
|
/*
|
|
* Unconditionally reject authentication for any service
|
|
* other than the one we're going to hand over to.
|
|
*/
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
|
|
put_stringz(pktout, "");
|
|
put_bool(pktout, false);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
continue;
|
|
}
|
|
|
|
s->methods = auth_methods(s->authpolicy);
|
|
s->partial_success = false;
|
|
|
|
if (ptrlen_eq_string(s->method, "none")) {
|
|
s->this_method = AUTHMETHOD_NONE;
|
|
if (!(s->methods & s->this_method))
|
|
goto failure;
|
|
|
|
if (!auth_none(s->authpolicy, s->username))
|
|
goto failure;
|
|
} else if (ptrlen_eq_string(s->method, "password")) {
|
|
bool changing;
|
|
ptrlen password, new_password, *new_password_ptr;
|
|
|
|
s->this_method = AUTHMETHOD_PASSWORD;
|
|
if (!(s->methods & s->this_method))
|
|
goto failure;
|
|
|
|
changing = get_bool(pktin);
|
|
password = get_string(pktin);
|
|
|
|
if (changing) {
|
|
new_password = get_string(pktin);
|
|
new_password_ptr = &new_password;
|
|
} else {
|
|
new_password_ptr = NULL;
|
|
}
|
|
|
|
int result = auth_password(s->authpolicy, s->username,
|
|
password, new_password_ptr);
|
|
if (result == 2) {
|
|
pktout = ssh_bpp_new_pktout(
|
|
s->ppl.bpp, SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ);
|
|
put_stringz(pktout, "Please change your password");
|
|
put_stringz(pktout, ""); /* language tag */
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */
|
|
} else if (result != 1) {
|
|
goto failure;
|
|
}
|
|
} else if (ptrlen_eq_string(s->method, "publickey")) {
|
|
bool has_signature, success;
|
|
ptrlen algorithm, blob, signature;
|
|
const ssh_keyalg *keyalg;
|
|
ssh_key *key;
|
|
strbuf *sigdata;
|
|
|
|
s->this_method = AUTHMETHOD_PUBLICKEY;
|
|
if (!(s->methods & s->this_method))
|
|
goto failure;
|
|
|
|
has_signature = get_bool(pktin);
|
|
algorithm = get_string(pktin);
|
|
blob = get_string(pktin);
|
|
|
|
if (!auth_publickey(s->authpolicy, s->username, blob))
|
|
goto failure;
|
|
|
|
keyalg = find_pubkey_alg_len(algorithm);
|
|
if (!keyalg)
|
|
goto failure;
|
|
key = ssh_key_new_pub(keyalg, blob);
|
|
if (!key)
|
|
goto failure;
|
|
|
|
if (!has_signature) {
|
|
ssh_key_free(key);
|
|
pktout = ssh_bpp_new_pktout(
|
|
s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK);
|
|
put_stringpl(pktout, algorithm);
|
|
put_stringpl(pktout, blob);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */
|
|
}
|
|
|
|
sigdata = strbuf_new();
|
|
ssh2_userauth_server_add_session_id(s, sigdata);
|
|
put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST);
|
|
put_stringpl(sigdata, s->username);
|
|
put_stringpl(sigdata, s->service);
|
|
put_stringpl(sigdata, s->method);
|
|
put_bool(sigdata, has_signature);
|
|
put_stringpl(sigdata, algorithm);
|
|
put_stringpl(sigdata, blob);
|
|
|
|
signature = get_string(pktin);
|
|
success = ssh_key_verify(key, signature,
|
|
ptrlen_from_strbuf(sigdata));
|
|
ssh_key_free(key);
|
|
strbuf_free(sigdata);
|
|
|
|
if (!success)
|
|
goto failure;
|
|
} else if (ptrlen_eq_string(s->method, "keyboard-interactive")) {
|
|
int i, ok;
|
|
unsigned n;
|
|
|
|
s->this_method = AUTHMETHOD_KBDINT;
|
|
if (!(s->methods & s->this_method))
|
|
goto failure;
|
|
|
|
do {
|
|
s->aki = auth_kbdint_prompts(s->authpolicy, s->username);
|
|
if (!s->aki)
|
|
goto failure;
|
|
|
|
pktout = ssh_bpp_new_pktout(
|
|
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_REQUEST);
|
|
put_stringz(pktout, s->aki->title);
|
|
put_stringz(pktout, s->aki->instruction);
|
|
put_stringz(pktout, ""); /* language tag */
|
|
put_uint32(pktout, s->aki->nprompts);
|
|
for (i = 0; i < s->aki->nprompts; i++) {
|
|
put_stringz(pktout, s->aki->prompts[i].prompt);
|
|
put_bool(pktout, s->aki->prompts[i].echo);
|
|
}
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
crMaybeWaitUntilV(
|
|
(pktin = ssh2_userauth_server_pop(s)) != NULL);
|
|
if (pktin->type != SSH2_MSG_USERAUTH_INFO_RESPONSE) {
|
|
ssh_proto_error(
|
|
s->ppl.ssh, "Received unexpected packet when "
|
|
"expecting USERAUTH_INFO_RESPONSE, type %d (%s)",
|
|
pktin->type,
|
|
ssh2_pkt_type(s->ppl.bpp->pls->kctx,
|
|
s->ppl.bpp->pls->actx, pktin->type));
|
|
return;
|
|
}
|
|
|
|
n = get_uint32(pktin);
|
|
if (n != s->aki->nprompts) {
|
|
ssh_proto_error(
|
|
s->ppl.ssh, "Received %u keyboard-interactive "
|
|
"responses after sending %u prompts",
|
|
n, s->aki->nprompts);
|
|
return;
|
|
}
|
|
|
|
{
|
|
ptrlen *responses = snewn(s->aki->nprompts, ptrlen);
|
|
for (i = 0; i < s->aki->nprompts; i++)
|
|
responses[i] = get_string(pktin);
|
|
ok = auth_kbdint_responses(s->authpolicy, responses);
|
|
sfree(responses);
|
|
}
|
|
|
|
free_auth_kbdint(s->aki);
|
|
s->aki = NULL;
|
|
} while (ok == 0);
|
|
|
|
if (ok <= 0)
|
|
goto failure;
|
|
} else {
|
|
goto failure;
|
|
}
|
|
|
|
/*
|
|
* If we get here, we've successfully completed this
|
|
* authentication step.
|
|
*/
|
|
if (auth_successful(s->authpolicy, s->username, s->this_method)) {
|
|
/*
|
|
* ... and it was the last one, so we're completely done.
|
|
*/
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_SUCCESS);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
break;
|
|
} else {
|
|
/*
|
|
* ... but another is required, so fall through to
|
|
* generation of USERAUTH_FAILURE, having first refreshed
|
|
* the bit mask of available methods.
|
|
*/
|
|
s->methods = auth_methods(s->authpolicy);
|
|
}
|
|
s->partial_success = true;
|
|
|
|
failure:
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_USERAUTH_FAILURE);
|
|
{
|
|
strbuf *list = strbuf_new();
|
|
if (s->methods & AUTHMETHOD_NONE)
|
|
add_to_commasep(list, "none");
|
|
if (s->methods & AUTHMETHOD_PASSWORD)
|
|
add_to_commasep(list, "password");
|
|
if (s->methods & AUTHMETHOD_PUBLICKEY)
|
|
add_to_commasep(list, "publickey");
|
|
if (s->methods & AUTHMETHOD_KBDINT)
|
|
add_to_commasep(list, "keyboard-interactive");
|
|
put_stringsb(pktout, list);
|
|
}
|
|
put_bool(pktout, s->partial_success);
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
}
|
|
|
|
/*
|
|
* Finally, hand over to our successor layer, and return
|
|
* immediately without reaching the crFinishV: ssh_ppl_replace
|
|
* will have freed us, so crFinishV's zeroing-out of crState would
|
|
* be a use-after-free bug.
|
|
*/
|
|
{
|
|
PacketProtocolLayer *successor = s->successor_layer;
|
|
s->successor_layer = NULL; /* avoid freeing it ourself */
|
|
ssh_ppl_replace(&s->ppl, successor);
|
|
return; /* we've just freed s, so avoid even touching s->crState */
|
|
}
|
|
|
|
crFinishV;
|
|
}
|