mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 09:27:59 +00:00
cd97b7e7ea
Ever since I reworked the SSH code to have multiple internal packet queues, there's been a long-standing FIXME in ssh_sendbuffer() saying that we ought to include the data buffered in those queues as part of reporting how much data is buffered on standard input. Recently a user reported that 'proftpd', or rather its 'mod_sftp' add-on that implements an SFTP-only SSH server, exposes a bug related to that missing piece of code. The xfer_upload system in sftp.c starts by pushing SFTP write messages into the SSH code for as long as sftp_sendbuffer() (which ends up at ssh_sendbuffer()) reports that not too much data is buffered locally. In fact what happens is that all those messages end up on the packet queues between SSH protocol layers, so they're not counted by sftp_sendbuffer(), so we just keep going until there's some other reason to stop. Usually the reason we stop is because we've filled up the SFTP channel's SSH-layer window, so we need the server to send us a WINDOW_ADJUST before we're allowed to send any more data. So we return to the main event loop and start waiting for reply packets. And when the window is moderate (e.g. OpenSSH currently seems to present about 2MB), this isn't really noticeable. But proftpd presents the maximum-size window of 2^32-1 bytes, and as a result we just keep shovelling more and more packets into the internal packet queues until PSFTP has grown to 4GB in size, and only then do we even return to the event loop and start actually sending them down the network. Moreover, this happens again at rekey time, because while a rekey is in progress, ssh2transport stops emptying the queue of outgoing packets sent by its higher layer - so, again, everything just keeps buffering up somewhere that sftp_sendbuffer can't see it. But this commit fixes it! Each PacketProtocolLayer now provides a vtable method for asking how much data it currently has queued. Most of them share a default implementation which just returns the newly added total_size field from their pq_out; the exception is ssh2transport, which also has to account for data queued in its higher layer. And ssh_sendbuffer() adds that on to the quantity it already knew about in other locations, to give a more realistic idea of the currently buffered data.
376 lines
12 KiB
C
376 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 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_ppl_default_queued_data_size,
|
|
"ssh-userauth",
|
|
};
|
|
|
|
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;
|
|
}
|