diff options
Diffstat (limited to 'ssh/userauth2-server.c')
-rw-r--r-- | ssh/userauth2-server.c | 379 |
1 files changed, 379 insertions, 0 deletions
diff --git a/ssh/userauth2-server.c b/ssh/userauth2-server.c new file mode 100644 index 00000000..bfe258ce --- /dev/null +++ b/ssh/userauth2-server.c @@ -0,0 +1,379 @@ +/* + * 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 "bpp.h" +#include "ppl.h" +#include "sshcr.h" +#include "server.h" + +#ifndef NO_GSSAPI +#include "gssc.h" +#include "gss.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, send_pk_ok, key_really_ok; + 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) || + s->ssc->stunt_return_success_to_pubkey_offer; + algorithm = get_string(pktin); + blob = get_string(pktin); + + key_really_ok = auth_publickey(s->authpolicy, s->username, blob); + send_pk_ok = key_really_ok || + s->ssc->stunt_pretend_to_accept_any_pubkey; + + if (!has_signature) { + if (!send_pk_ok) + goto failure; + + 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 */ + } + + if (!key_really_ok) + goto failure; + + keyalg = find_pubkey_alg_len(algorithm); + if (!keyalg) + goto failure; + key = ssh_key_new_pub(keyalg, blob); + if (!key) + goto failure; + + 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)) || + s->ssc->stunt_return_success_to_pubkey_offer; + 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; +} |