mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-07-01 11:32:48 -05:00
New feature: k-i authentication helper plugins.
In recent months I've had two requests from different people to build support into PuTTY for automatically handling complicated third-party auth protocols layered on top of keyboard-interactive - the kind of thing where you're asked to enter some auth response, and you have to refer to some external source like a web server to find out what the right response _is_, which is a pain to do by hand, so you'd prefer it to be automated in the SSH client. That seems like a reasonable thing for an end user to want, but I didn't think it was a good idea to build support for specific protocols of that kind directly into PuTTY, where there would no doubt be an ever-lengthening list, and maintenance needed on all of them. So instead, in collaboration with one of my correspondents, I've designed and implemented a protocol to be spoken between PuTTY and a plugin running as a subprocess. The plugin can opt to handle the keyboard-interactive authentication loop on behalf of the user, in which case PuTTY passes on all the INFO_REQUEST packets to it, and lets it make up responses. It can also ask questions of the user if necessary. The protocol spec is provided in a documentation appendix. The entire configuration for the end user consists of providing a full command line to use as the subprocess. In the contrib directory I've provided an example plugin written in Python. It gives a set of fixed responses suitable for getting through Uppity's made-up k-i system, because that was a reasonable thing I already had lying around to test against. But it also provides example code that someone else could pick up and insert their own live response-provider into the middle of, assuming they were happy with it being in Python.
This commit is contained in:
@ -114,7 +114,8 @@ PacketProtocolLayer *ssh2_userauth_new(
|
||||
bool show_banner, bool tryagent, bool notrivialauth,
|
||||
const char *default_username, bool change_username,
|
||||
bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth,
|
||||
bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss);
|
||||
bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss,
|
||||
const char *auth_plugin);
|
||||
PacketProtocolLayer *ssh2_connection_new(
|
||||
Ssh *ssh, ssh_sharing_state *connshare, bool is_simple,
|
||||
Conf *conf, const char *peer_verstring, bufchain *user_input,
|
||||
|
@ -267,14 +267,14 @@ static void ssh_got_ssh_version(struct ssh_version_receiver *rcv,
|
||||
conf_get_bool(ssh->conf, CONF_try_gssapi_auth),
|
||||
conf_get_bool(ssh->conf, CONF_try_gssapi_kex),
|
||||
conf_get_bool(ssh->conf, CONF_gssapifwd),
|
||||
&ssh->gss_state
|
||||
&ssh->gss_state,
|
||||
#else
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
NULL
|
||||
NULL,
|
||||
#endif
|
||||
);
|
||||
conf_get_str(ssh->conf, CONF_auth_plugin));
|
||||
ssh_connect_ppl(ssh, userauth_layer);
|
||||
transport_child_layer = userauth_layer;
|
||||
|
||||
|
@ -90,6 +90,15 @@ struct ssh2_userauth_state {
|
||||
StripCtrlChars *banner_scc;
|
||||
bool banner_scc_initialised;
|
||||
|
||||
char *authplugin_cmd;
|
||||
Socket *authplugin;
|
||||
uint32_t authplugin_version;
|
||||
Plug authplugin_plug;
|
||||
bufchain authplugin_bc;
|
||||
strbuf *authplugin_incoming_msg;
|
||||
bool authplugin_eof;
|
||||
bool authplugin_ki_active;
|
||||
|
||||
StripCtrlChars *ki_scc;
|
||||
bool ki_scc_initialised;
|
||||
bool ki_printed_header;
|
||||
@ -118,7 +127,7 @@ static PktOut *ssh2_userauth_gss_packet(
|
||||
struct ssh2_userauth_state *s, const char *authtype);
|
||||
#endif
|
||||
static bool ssh2_userauth_ki_setup_prompts(
|
||||
struct ssh2_userauth_state *s, BinarySource *src);
|
||||
struct ssh2_userauth_state *s, BinarySource *src, bool plugin);
|
||||
static bool ssh2_userauth_ki_run_prompts(struct ssh2_userauth_state *s);
|
||||
static void ssh2_userauth_ki_write_responses(
|
||||
struct ssh2_userauth_state *s, BinarySink *bs);
|
||||
@ -140,7 +149,8 @@ PacketProtocolLayer *ssh2_userauth_new(
|
||||
bool show_banner, bool tryagent, bool notrivialauth,
|
||||
const char *default_username, bool change_username,
|
||||
bool try_ki_auth, bool try_gssapi_auth, bool try_gssapi_kex_auth,
|
||||
bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss)
|
||||
bool gssapi_fwd, struct ssh_connection_shared_gss_state *shgss,
|
||||
const char *authplugin_cmd)
|
||||
{
|
||||
struct ssh2_userauth_state *s = snew(struct ssh2_userauth_state);
|
||||
memset(s, 0, sizeof(*s));
|
||||
@ -166,6 +176,8 @@ PacketProtocolLayer *ssh2_userauth_new(
|
||||
s->is_trivial_auth = true;
|
||||
bufchain_init(&s->banner);
|
||||
bufchain_sink_init(&s->banner_bs, &s->banner);
|
||||
s->authplugin_cmd = dupstr(authplugin_cmd);
|
||||
bufchain_init(&s->authplugin_bc);
|
||||
|
||||
return &s->ppl;
|
||||
}
|
||||
@ -218,6 +230,12 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl)
|
||||
stripctrl_free(s->banner_scc);
|
||||
if (s->ki_scc)
|
||||
stripctrl_free(s->ki_scc);
|
||||
sfree(s->authplugin_cmd);
|
||||
if (s->authplugin)
|
||||
sk_close(s->authplugin);
|
||||
bufchain_clear(&s->authplugin_bc);
|
||||
if (s->authplugin_incoming_msg)
|
||||
strbuf_free(s->authplugin_incoming_msg);
|
||||
sfree(s);
|
||||
}
|
||||
|
||||
@ -288,6 +306,125 @@ static bool ssh2_userauth_signflags(struct ssh2_userauth_state *s,
|
||||
return true;
|
||||
}
|
||||
|
||||
static void authplugin_plug_log(Plug *plug, PlugLogType type, SockAddr *addr,
|
||||
int port, const char *err_msg, int err_code)
|
||||
{
|
||||
struct ssh2_userauth_state *s = container_of(
|
||||
plug, struct ssh2_userauth_state, authplugin_plug);
|
||||
PacketProtocolLayer *ppl = &s->ppl; /* for ppl_logevent */
|
||||
|
||||
if (type == PLUGLOG_PROXY_MSG)
|
||||
ppl_logevent("%s", err_msg);
|
||||
}
|
||||
|
||||
static void authplugin_plug_closing(
|
||||
Plug *plug, PlugCloseType type, const char *error_msg)
|
||||
{
|
||||
struct ssh2_userauth_state *s = container_of(
|
||||
plug, struct ssh2_userauth_state, authplugin_plug);
|
||||
s->authplugin_eof = true;
|
||||
queue_idempotent_callback(&s->ppl.ic_process_queue);
|
||||
}
|
||||
|
||||
static void authplugin_plug_receive(
|
||||
Plug *plug, int urgent, const char *data, size_t len)
|
||||
{
|
||||
struct ssh2_userauth_state *s = container_of(
|
||||
plug, struct ssh2_userauth_state, authplugin_plug);
|
||||
bufchain_add(&s->authplugin_bc, data, len);
|
||||
queue_idempotent_callback(&s->ppl.ic_process_queue);
|
||||
}
|
||||
|
||||
static const PlugVtable authplugin_plugvt = {
|
||||
.log = authplugin_plug_log,
|
||||
.closing = authplugin_plug_closing,
|
||||
.receive = authplugin_plug_receive,
|
||||
.sent = nullplug_sent,
|
||||
};
|
||||
|
||||
static strbuf *authplugin_newmsg(uint8_t type)
|
||||
{
|
||||
strbuf *amsg = strbuf_new_nm();
|
||||
put_uint32(amsg, 0); /* fill in later */
|
||||
put_byte(amsg, type);
|
||||
return amsg;
|
||||
}
|
||||
|
||||
static void authplugin_send_free(struct ssh2_userauth_state *s, strbuf *amsg)
|
||||
{
|
||||
PUT_32BIT_MSB_FIRST(amsg->u, amsg->len - 4);
|
||||
assert(s->authplugin);
|
||||
sk_write(s->authplugin, amsg->u, amsg->len);
|
||||
strbuf_free(amsg);
|
||||
}
|
||||
|
||||
static bool authplugin_expect_msg(struct ssh2_userauth_state *s,
|
||||
unsigned *type, BinarySource *src)
|
||||
{
|
||||
if (s->authplugin_eof) {
|
||||
*type = PLUGIN_EOF;
|
||||
return true;
|
||||
}
|
||||
uint8_t len[4];
|
||||
if (!bufchain_try_fetch(&s->authplugin_bc, len, 4))
|
||||
return false;
|
||||
size_t size = GET_32BIT_MSB_FIRST(len);
|
||||
if (bufchain_size(&s->authplugin_bc) - 4 < size)
|
||||
return false;
|
||||
if (s->authplugin_incoming_msg) {
|
||||
strbuf_clear(s->authplugin_incoming_msg);
|
||||
} else {
|
||||
s->authplugin_incoming_msg = strbuf_new_nm();
|
||||
}
|
||||
bufchain_consume(&s->authplugin_bc, 4); /* eat length field */
|
||||
bufchain_fetch_consume(
|
||||
&s->authplugin_bc, strbuf_append(s->authplugin_incoming_msg, size),
|
||||
size);
|
||||
BinarySource_BARE_INIT_PL(
|
||||
src, ptrlen_from_strbuf(s->authplugin_incoming_msg));
|
||||
*type = get_byte(src);
|
||||
if (get_err(src))
|
||||
*type = PLUGIN_NOTYPE;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void authplugin_bad_packet(struct ssh2_userauth_state *s,
|
||||
unsigned type, const char *fmt, ...)
|
||||
{
|
||||
strbuf *msg = strbuf_new();
|
||||
switch (type) {
|
||||
case PLUGIN_EOF:
|
||||
put_dataz(msg, "Unexpected end of file from auth helper plugin");
|
||||
break;
|
||||
case PLUGIN_NOTYPE:
|
||||
put_dataz(msg, "Received malformed packet from auth helper plugin "
|
||||
"(too short to have a type code)");
|
||||
break;
|
||||
default:
|
||||
put_fmt(msg, "Received unknown message type %u "
|
||||
"from auth helper plugin", type);
|
||||
break;
|
||||
|
||||
#define CASEDECL(name, value) \
|
||||
case name: \
|
||||
put_fmt(msg, "Received unexpected %s message from auth helper " \
|
||||
"plugin", #name); \
|
||||
break;
|
||||
AUTHPLUGIN_MSG_NAMES(CASEDECL);
|
||||
#undef CASEDECL
|
||||
}
|
||||
if (fmt) {
|
||||
put_dataz(msg, " (");
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
put_fmt(msg, fmt, ap);
|
||||
va_end(ap);
|
||||
put_dataz(msg, ")");
|
||||
}
|
||||
ssh_sw_abort(s->ppl.ssh, "%s", msg->s);
|
||||
strbuf_free(msg);
|
||||
}
|
||||
|
||||
static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
||||
{
|
||||
struct ssh2_userauth_state *s =
|
||||
@ -502,6 +639,74 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
||||
done_agent_query:;
|
||||
}
|
||||
|
||||
s->got_username = false;
|
||||
|
||||
if (*s->authplugin_cmd) {
|
||||
s->authplugin_plug.vt = &authplugin_plugvt;
|
||||
s->authplugin = platform_start_subprocess(
|
||||
s->authplugin_cmd, &s->authplugin_plug, "plugin");
|
||||
ppl_logevent("Started authentication plugin: %s", s->authplugin_cmd);
|
||||
}
|
||||
|
||||
if (s->authplugin) {
|
||||
strbuf *amsg = authplugin_newmsg(PLUGIN_INIT);
|
||||
put_uint32(amsg, PLUGIN_PROTOCOL_MAX_VERSION);
|
||||
put_stringz(amsg, s->hostname);
|
||||
put_uint32(amsg, s->port);
|
||||
put_stringz(amsg, s->username ? s->username : "");
|
||||
authplugin_send_free(s, amsg);
|
||||
|
||||
BinarySource src[1];
|
||||
unsigned type;
|
||||
crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src));
|
||||
switch (type) {
|
||||
case PLUGIN_INIT_RESPONSE: {
|
||||
s->authplugin_version = get_uint32(src);
|
||||
ptrlen username = get_string(src);
|
||||
if (get_err(src)) {
|
||||
ssh_sw_abort(s->ppl.ssh, "Received malformed "
|
||||
"PLUGIN_INIT_RESPONSE from auth helper plugin");
|
||||
return;
|
||||
}
|
||||
if (s->authplugin_version > PLUGIN_PROTOCOL_MAX_VERSION) {
|
||||
ssh_sw_abort(s->ppl.ssh, "Auth helper plugin announced "
|
||||
"unsupported version number %"PRIu32,
|
||||
s->authplugin_version);
|
||||
return;
|
||||
}
|
||||
if (username.len) {
|
||||
sfree(s->default_username);
|
||||
s->default_username = mkstr(username);
|
||||
ppl_logevent("Authentication plugin set username '%s'",
|
||||
s->default_username);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PLUGIN_INIT_FAILURE: {
|
||||
ptrlen message = get_string(src);
|
||||
if (get_err(src)) {
|
||||
ssh_sw_abort(s->ppl.ssh, "Received malformed "
|
||||
"PLUGIN_INIT_FAILURE from auth helper plugin");
|
||||
return;
|
||||
}
|
||||
/* This is a controlled error, so we need not completely
|
||||
* abandon the connection. Instead, inform the user, and
|
||||
* proceed as if the plugin was not present */
|
||||
ppl_printf("Authentication plugin failed to initialise:\r\n");
|
||||
seat_set_trust_status(s->ppl.seat, false);
|
||||
ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message));
|
||||
seat_set_trust_status(s->ppl.seat, true);
|
||||
sk_close(s->authplugin);
|
||||
s->authplugin = NULL;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
authplugin_bad_packet(s, type, "expected PLUGIN_INIT_RESPONSE or "
|
||||
"PLUGIN_INIT_FAILURE");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We repeat this whole loop, including the username prompt,
|
||||
* until we manage a successful authentication. If the user
|
||||
@ -526,7 +731,6 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
||||
* the username they will want to be able to get back and
|
||||
* retype it!
|
||||
*/
|
||||
s->got_username = false;
|
||||
while (1) {
|
||||
/*
|
||||
* Get a username.
|
||||
@ -1341,6 +1545,64 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
||||
|
||||
ppl_logevent("Attempting keyboard-interactive authentication");
|
||||
|
||||
if (s->authplugin) {
|
||||
strbuf *amsg = authplugin_newmsg(PLUGIN_PROTOCOL);
|
||||
put_stringz(amsg, "keyboard-interactive");
|
||||
authplugin_send_free(s, amsg);
|
||||
|
||||
BinarySource src[1];
|
||||
unsigned type;
|
||||
crMaybeWaitUntilV(authplugin_expect_msg(s, &type, src));
|
||||
switch (type) {
|
||||
case PLUGIN_PROTOCOL_REJECT: {
|
||||
ptrlen message = PTRLEN_LITERAL("");
|
||||
if (s->authplugin_version >= 2) {
|
||||
/* draft protocol didn't include a message here */
|
||||
message = get_string(src);
|
||||
}
|
||||
if (get_err(src)) {
|
||||
ssh_sw_abort(s->ppl.ssh, "Received malformed "
|
||||
"PLUGIN_PROTOCOL_REJECT from auth "
|
||||
"helper plugin");
|
||||
return;
|
||||
}
|
||||
if (message.len) {
|
||||
/* If the plugin sent a message about
|
||||
* _why_ it didn't want to do k-i, pass
|
||||
* that message on to the user. (It might
|
||||
* say, for example, what went wrong when
|
||||
* it tried to open its config file.) */
|
||||
ppl_printf("Authentication plugin failed to set "
|
||||
"up keyboard-interactive "
|
||||
"authentication:\r\n");
|
||||
seat_set_trust_status(s->ppl.seat, false);
|
||||
ppl_printf("%.*s\r\n", PTRLEN_PRINTF(message));
|
||||
seat_set_trust_status(s->ppl.seat, true);
|
||||
ppl_logevent("Authentication plugin declined to "
|
||||
"help with keyboard-interactive: "
|
||||
"%.*s", PTRLEN_PRINTF(message));
|
||||
} else {
|
||||
ppl_logevent("Authentication plugin declined to "
|
||||
"help with keyboard-interactive");
|
||||
}
|
||||
s->authplugin_ki_active = false;
|
||||
break;
|
||||
}
|
||||
case PLUGIN_PROTOCOL_ACCEPT:
|
||||
s->authplugin_ki_active = true;
|
||||
ppl_logevent("Authentication plugin agreed to help "
|
||||
"with keyboard-interactive");
|
||||
break;
|
||||
default:
|
||||
authplugin_bad_packet(
|
||||
s, type, "expected PLUGIN_PROTOCOL_ACCEPT or "
|
||||
"PLUGIN_PROTOCOL_REJECT");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
s->authplugin_ki_active = false;
|
||||
}
|
||||
|
||||
if (!s->ki_scc_initialised) {
|
||||
s->ki_scc = seat_stripctrl_new(
|
||||
s->ppl.seat, NULL, SIC_KI_PROMPTS);
|
||||
@ -1364,44 +1626,123 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
||||
s->ki_printed_header = false;
|
||||
|
||||
/*
|
||||
* Loop while the server continues to send INFO_REQUESTs.
|
||||
* Loop while we still have prompts to send to the user.
|
||||
*/
|
||||
while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
|
||||
if (!ssh2_userauth_ki_setup_prompts(
|
||||
s, BinarySource_UPCAST(pktin)))
|
||||
return;
|
||||
crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s));
|
||||
if (!s->authplugin_ki_active) {
|
||||
/*
|
||||
* The simple case: INFO_REQUESTs are passed on to
|
||||
* the user, and responses are sent straight back
|
||||
* to the SSH server.
|
||||
*/
|
||||
while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
|
||||
if (!ssh2_userauth_ki_setup_prompts(
|
||||
s, BinarySource_UPCAST(pktin), false))
|
||||
return;
|
||||
crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s));
|
||||
|
||||
if (spr_is_abort(s->spr)) {
|
||||
/*
|
||||
* Failed to get responses. Terminate.
|
||||
*/
|
||||
free_prompts(s->cur_prompt);
|
||||
s->cur_prompt = NULL;
|
||||
ssh_bpp_queue_disconnect(
|
||||
s->ppl.bpp, "Unable to authenticate",
|
||||
SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
|
||||
ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-"
|
||||
"interactive authentication prompt");
|
||||
return;
|
||||
}
|
||||
|
||||
if (spr_is_abort(s->spr)) {
|
||||
/*
|
||||
* Failed to get responses. Terminate.
|
||||
* Send the response(s) to the server.
|
||||
*/
|
||||
free_prompts(s->cur_prompt);
|
||||
s->cur_prompt = NULL;
|
||||
ssh_bpp_queue_disconnect(
|
||||
s->ppl.bpp, "Unable to authenticate",
|
||||
SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
|
||||
ssh_spr_close(s->ppl.ssh, s->spr, "keyboard-"
|
||||
"interactive authentication prompt");
|
||||
return;
|
||||
s->pktout = ssh_bpp_new_pktout(
|
||||
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
|
||||
ssh2_userauth_ki_write_responses(
|
||||
s, BinarySink_UPCAST(s->pktout));
|
||||
s->pktout->minlen = 256;
|
||||
pq_push(s->ppl.out_pq, s->pktout);
|
||||
|
||||
/*
|
||||
* Get the next packet in case it's another
|
||||
* INFO_REQUEST.
|
||||
*/
|
||||
crMaybeWaitUntilV(
|
||||
(pktin = ssh2_userauth_pop(s)) != NULL);
|
||||
}
|
||||
|
||||
} else {
|
||||
/*
|
||||
* Send the response(s) to the server.
|
||||
* The case where a plugin is involved:
|
||||
* INFO_REQUEST from the server is sent to the
|
||||
* plugin, which sends responses that we hand back
|
||||
* to the server. But in the meantime, the plugin
|
||||
* might send USER_REQUEST for us to pass to the
|
||||
* user, and then we send responses to that.
|
||||
*/
|
||||
s->pktout = ssh_bpp_new_pktout(
|
||||
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
|
||||
ssh2_userauth_ki_write_responses(
|
||||
s, BinarySink_UPCAST(s->pktout));
|
||||
s->pktout->minlen = 256;
|
||||
pq_push(s->ppl.out_pq, s->pktout);
|
||||
while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
|
||||
strbuf *amsg = authplugin_newmsg(
|
||||
PLUGIN_KI_SERVER_REQUEST);
|
||||
put_datapl(amsg, get_data(pktin, get_avail(pktin)));
|
||||
authplugin_send_free(s, amsg);
|
||||
|
||||
/*
|
||||
* Get the next packet in case it's another
|
||||
* INFO_REQUEST.
|
||||
*/
|
||||
crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
|
||||
BinarySource src[1];
|
||||
unsigned type;
|
||||
while (true) {
|
||||
crMaybeWaitUntilV(authplugin_expect_msg(
|
||||
s, &type, src));
|
||||
if (type != PLUGIN_KI_USER_REQUEST)
|
||||
break;
|
||||
|
||||
if (!ssh2_userauth_ki_setup_prompts(s, src, true))
|
||||
return;
|
||||
crMaybeWaitUntilV(ssh2_userauth_ki_run_prompts(s));
|
||||
|
||||
if (spr_is_abort(s->spr)) {
|
||||
/*
|
||||
* Failed to get responses. Terminate.
|
||||
*/
|
||||
free_prompts(s->cur_prompt);
|
||||
s->cur_prompt = NULL;
|
||||
ssh_bpp_queue_disconnect(
|
||||
s->ppl.bpp, "Unable to authenticate",
|
||||
SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER);
|
||||
ssh_spr_close(
|
||||
s->ppl.ssh, s->spr, "keyboard-"
|
||||
"interactive authentication prompt");
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send the responses on to the plugin.
|
||||
*/
|
||||
strbuf *amsg = authplugin_newmsg(
|
||||
PLUGIN_KI_USER_RESPONSE);
|
||||
ssh2_userauth_ki_write_responses(
|
||||
s, BinarySink_UPCAST(amsg));
|
||||
authplugin_send_free(s, amsg);
|
||||
}
|
||||
|
||||
if (type != PLUGIN_KI_SERVER_RESPONSE) {
|
||||
authplugin_bad_packet(
|
||||
s, type, "expected PLUGIN_KI_SERVER_RESPONSE "
|
||||
"or PLUGIN_PROTOCOL_USER_REQUEST");
|
||||
return;
|
||||
}
|
||||
|
||||
s->pktout = ssh_bpp_new_pktout(
|
||||
s->ppl.bpp, SSH2_MSG_USERAUTH_INFO_RESPONSE);
|
||||
put_datapl(s->pktout, get_data(src, get_avail(src)));
|
||||
s->pktout->minlen = 256;
|
||||
pq_push(s->ppl.out_pq, s->pktout);
|
||||
|
||||
/*
|
||||
* Get the next packet in case it's another
|
||||
* INFO_REQUEST.
|
||||
*/
|
||||
crMaybeWaitUntilV(
|
||||
(pktin = ssh2_userauth_pop(s)) != NULL);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1411,7 +1752,9 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
||||
seat_set_trust_status(s->ppl.seat, true);
|
||||
seat_antispoof_msg(
|
||||
ppl_get_iseat(&s->ppl),
|
||||
"End of keyboard-interactive prompts from server");
|
||||
(s->authplugin_ki_active ?
|
||||
"End of keyboard-interactive prompts from plugin" :
|
||||
"End of keyboard-interactive prompts from server"));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1419,6 +1762,35 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
||||
*/
|
||||
pq_push_front(s->ppl.in_pq, pktin);
|
||||
|
||||
if (s->authplugin_ki_active) {
|
||||
/*
|
||||
* As our last communication with the plugin, tell
|
||||
* it whether the k-i authentication succeeded.
|
||||
*/
|
||||
int plugin_msg = -1;
|
||||
if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
|
||||
plugin_msg = PLUGIN_AUTH_SUCCESS;
|
||||
} else if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) {
|
||||
/*
|
||||
* Peek in the failure packet to see if it's a
|
||||
* partial success.
|
||||
*/
|
||||
BinarySource src[1];
|
||||
BinarySource_BARE_INIT(
|
||||
src, get_ptr(pktin), get_avail(pktin));
|
||||
get_string(pktin); /* skip methods */
|
||||
bool partial_success = get_bool(pktin);
|
||||
if (!get_err(src)) {
|
||||
plugin_msg = partial_success ?
|
||||
PLUGIN_AUTH_SUCCESS : PLUGIN_AUTH_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (plugin_msg >= 0) {
|
||||
strbuf *amsg = authplugin_newmsg(plugin_msg);
|
||||
authplugin_send_free(s, amsg);
|
||||
}
|
||||
}
|
||||
} else if (s->can_passwd) {
|
||||
s->is_trivial_auth = false;
|
||||
/*
|
||||
@ -1695,7 +2067,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
|
||||
}
|
||||
|
||||
static bool ssh2_userauth_ki_setup_prompts(
|
||||
struct ssh2_userauth_state *s, BinarySource *src)
|
||||
struct ssh2_userauth_state *s, BinarySource *src, bool plugin)
|
||||
{
|
||||
ptrlen name, inst;
|
||||
strbuf *sb;
|
||||
@ -1721,14 +2093,17 @@ static bool ssh2_userauth_ki_setup_prompts(
|
||||
bool echo = get_bool(src);
|
||||
|
||||
if (get_err(src)) {
|
||||
ssh_proto_error(s->ppl.ssh, "Server sent truncated "
|
||||
"SSH_MSG_USERAUTH_INFO_REQUEST packet");
|
||||
ssh_proto_error(s->ppl.ssh, "%s sent truncated %s packet",
|
||||
plugin ? "Plugin" : "Server",
|
||||
plugin ? "PLUGIN_KI_USER_REQUEST" :
|
||||
"SSH_MSG_USERAUTH_INFO_REQUEST");
|
||||
return false;
|
||||
}
|
||||
|
||||
sb = strbuf_new();
|
||||
if (!prompt.len) {
|
||||
put_datapl(sb, PTRLEN_LITERAL("<server failed to send prompt>: "));
|
||||
put_fmt(sb, "<%s failed to send prompt>: ",
|
||||
plugin ? "plugin" : "server");
|
||||
} else if (s->ki_scc) {
|
||||
stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb));
|
||||
put_datapl(s->ki_scc, prompt);
|
||||
@ -1756,8 +2131,11 @@ static bool ssh2_userauth_ki_setup_prompts(
|
||||
*/
|
||||
if (!s->ki_printed_header && s->ki_scc &&
|
||||
(s->num_prompts || name.len || inst.len)) {
|
||||
seat_antispoof_msg(ppl_get_iseat(&s->ppl), "Keyboard-interactive "
|
||||
"authentication prompts from server:");
|
||||
seat_antispoof_msg(
|
||||
ppl_get_iseat(&s->ppl),
|
||||
(plugin ?
|
||||
"Keyboard-interactive authentication prompts from plugin:" :
|
||||
"Keyboard-interactive authentication prompts from server:"));
|
||||
s->ki_printed_header = true;
|
||||
seat_set_trust_status(s->ppl.seat, false);
|
||||
}
|
||||
@ -1773,7 +2151,11 @@ static bool ssh2_userauth_ki_setup_prompts(
|
||||
}
|
||||
s->cur_prompt->name_reqd = true;
|
||||
} else {
|
||||
put_datapl(sb, PTRLEN_LITERAL("SSH server authentication"));
|
||||
if (plugin)
|
||||
put_datapl(sb, PTRLEN_LITERAL(
|
||||
"Communication with authentication plugin"));
|
||||
else
|
||||
put_datapl(sb, PTRLEN_LITERAL("SSH server authentication"));
|
||||
s->cur_prompt->name_reqd = false;
|
||||
}
|
||||
s->cur_prompt->name = strbuf_to_str(sb);
|
||||
|
Reference in New Issue
Block a user