mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
270 lines
8.4 KiB
C
270 lines
8.4 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;
|
||
|
|
||
|
ptrlen username, service, method;
|
||
|
unsigned methods, this_method;
|
||
|
int partial_success;
|
||
|
|
||
|
PacketProtocolLayer ppl;
|
||
|
};
|
||
|
|
||
|
static void ssh2_userauth_server_free(PacketProtocolLayer *);
|
||
|
static void ssh2_userauth_server_process_queue(PacketProtocolLayer *);
|
||
|
|
||
|
static const struct PacketProtocolLayerVtable ssh2_userauth_server_vtable = {
|
||
|
ssh2_userauth_server_free,
|
||
|
ssh2_userauth_server_process_queue,
|
||
|
NULL /* get_specials */,
|
||
|
NULL /* special_cmd */,
|
||
|
NULL /* want_user_input */,
|
||
|
NULL /* got_user_input */,
|
||
|
NULL /* reconfigure */,
|
||
|
"ssh-userauth",
|
||
|
};
|
||
|
|
||
|
PacketProtocolLayer *ssh2_userauth_server_new(
|
||
|
PacketProtocolLayer *successor_layer, AuthPolicy *authpolicy)
|
||
|
{
|
||
|
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;
|
||
|
|
||
|
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);
|
||
|
|
||
|
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_data(sigdata, s->session_id.ptr, s->session_id.len);
|
||
|
} 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);
|
||
|
|
||
|
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")) {
|
||
|
int changing;
|
||
|
ptrlen password;
|
||
|
|
||
|
s->this_method = AUTHMETHOD_PASSWORD;
|
||
|
if (!(s->methods & s->this_method))
|
||
|
goto failure;
|
||
|
|
||
|
changing = get_bool(pktin);
|
||
|
if (changing)
|
||
|
goto failure; /* FIXME: not yet supported */
|
||
|
|
||
|
password = get_string(pktin);
|
||
|
|
||
|
if (!auth_password(s->authpolicy, s->username, password))
|
||
|
goto failure;
|
||
|
} else if (ptrlen_eq_string(s->method, "publickey")) {
|
||
|
int 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 {
|
||
|
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");
|
||
|
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;
|
||
|
}
|