# HG changeset patch # User Matt Johnston # Date 1127237721 0 # Node ID efbaf6b03837c9effe7d8686c4ef7460de490464 # Parent bf64e666f99b936c71edb596595842bb0592ee89 added keyboard-interactive client support diff -r bf64e666f99b -r efbaf6b03837 Makefile.in --- a/Makefile.in Tue Sep 20 08:59:46 2005 +0000 +++ b/Makefile.in Tue Sep 20 17:35:21 2005 +0000 @@ -29,7 +29,7 @@ CLIOBJS=cli-algo.o cli-main.o cli-auth.o cli-authpasswd.o cli-kex.o \ cli-session.o cli-service.o cli-runopts.o cli-chansession.o \ - cli-authpubkey.o cli-tcpfwd.o cli-channel.o + cli-authpubkey.o cli-tcpfwd.o cli-channel.o cli-authinteract.o CLISVROBJS=common-session.o packet.o common-algo.o common-kex.o \ common-channel.o common-chansession.o termcodes.o loginrec.o \ diff -r bf64e666f99b -r efbaf6b03837 auth.h --- a/auth.h Tue Sep 20 08:59:46 2005 +0000 +++ b/auth.h Tue Sep 20 17:35:21 2005 +0000 @@ -41,28 +41,36 @@ /* Client functions */ void recv_msg_userauth_failure(); void recv_msg_userauth_success(); +void recv_msg_userauth_specific_60(); void recv_msg_userauth_pk_ok(); +void recv_msg_userauth_info_request(); void cli_get_user(); void cli_auth_getmethods(); void cli_auth_try(); void recv_msg_userauth_banner(); void cli_pubkeyfail(); -int cli_auth_password(); +void cli_auth_password(); int cli_auth_pubkey(); +void cli_auth_interactive(); #define MAX_USERNAME_LEN 25 /* arbitrary for the moment */ -#define AUTH_TYPE_PUBKEY 1 << 0 -#define AUTH_TYPE_PASSWORD 1 << 1 +#define AUTH_TYPE_NONE 1 +#define AUTH_TYPE_PUBKEY 1 << 1 +#define AUTH_TYPE_PASSWORD 1 << 2 +#define AUTH_TYPE_INTERACT 1 << 3 -/* auth types, "none" means we should return list of acceptable types */ -#define AUTH_METHOD_NONE "none" +#define AUTH_METHOD_NONE "none" #define AUTH_METHOD_NONE_LEN 4 #define AUTH_METHOD_PUBKEY "publickey" #define AUTH_METHOD_PUBKEY_LEN 9 #define AUTH_METHOD_PASSWORD "password" #define AUTH_METHOD_PASSWORD_LEN 8 +#define AUTH_METHOD_INTERACT "keyboard-interactive" +#define AUTH_METHOD_INTERACT_LEN 20 + + /* This structure is shared between server and client - it contains * relatively little extraneous bits when used for the client rather than the diff -r bf64e666f99b -r efbaf6b03837 cli-auth.c --- a/cli-auth.c Tue Sep 20 08:59:46 2005 +0000 +++ b/cli-auth.c Tue Sep 20 17:35:21 2005 +0000 @@ -32,7 +32,6 @@ #include "packet.h" #include "runopts.h" - void cli_authinitialise() { memset(&ses.authstate, 0, sizeof(ses.authstate)); @@ -99,6 +98,40 @@ TRACE(("leave recv_msg_userauth_banner")) } +/* This handles the message-specific types which + * all have a value of 60. These are + * SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, + * SSH_MSG_USERAUTH_PK_OK, & + * SSH_MSG_USERAUTH_INFO_REQUEST. */ +void recv_msg_userauth_specific_60() { + +#ifdef ENABLE_CLI_PUBKEY_AUTH + if (cli_ses.lastauthtype == AUTH_TYPE_PUBKEY) { + recv_msg_userauth_pk_ok(); + return; + } +#endif + +#ifdef ENABLE_CLI_INTERACT_AUTH + if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT) { + recv_msg_userauth_info_request(); + return; + } +#endif + +#ifdef ENABLE_CLI_PASSWORD_AUTH + if (cli_ses.lastauthtype == AUTH_TYPE_PASSWORD) { + /* Eventually there could be proper password-changing + * support. However currently few servers seem to + * implement it, and password auth is last-resort + * regardless - keyboard-interactive is more likely + * to be used anyway. */ + dropbear_close("Your password has expired."); + } +#endif + + dropbear_exit("Unexpected userauth packet"); +} void recv_msg_userauth_failure() { @@ -113,8 +146,7 @@ if (cli_ses.state != USERAUTH_REQ_SENT) { /* Perhaps we should be more fatal? */ - TRACE(("But we didn't send a userauth request!!!!!!")) - return; + dropbear_exit("Unexpected userauth failure"); } #ifdef ENABLE_CLI_PUBKEY_AUTH @@ -125,6 +157,19 @@ } #endif +#ifdef ENABLE_CLI_INTERACT_AUTH + /* If we get a failure message for keyboard interactive without + * receiving any request info packet, then we don't bother trying + * keyboard interactive again */ + if (cli_ses.lastauthtype == AUTH_TYPE_INTERACT + && !cli_ses.interact_request_received) { + TRACE(("setting auth_interact_failed = 1")) + cli_ses.auth_interact_failed = 1; + } +#endif + + cli_ses.lastauthtype = AUTH_TYPE_NONE; + methods = buf_getstring(ses.payload, &methlen); partial = buf_getbool(ses.payload); @@ -157,6 +202,12 @@ ses.authstate.authtypes |= AUTH_TYPE_PUBKEY; } #endif +#ifdef ENABLE_CLI_INTERACT_AUTH + if (strncmp(AUTH_METHOD_INTERACT, tok, + AUTH_METHOD_INTERACT_LEN) == 0) { + ses.authstate.authtypes |= AUTH_TYPE_INTERACT; + } +#endif #ifdef ENABLE_CLI_PASSWORD_AUTH if (strncmp(AUTH_METHOD_PASSWORD, tok, AUTH_METHOD_PASSWORD_LEN) == 0) { @@ -180,6 +231,7 @@ TRACE(("received msg_userauth_success")) ses.authstate.authdone = 1; cli_ses.state = USERAUTH_SUCCESS_RCVD; + cli_ses.lastauthtype = AUTH_TYPE_NONE; } void cli_auth_try() { @@ -189,7 +241,8 @@ CHECKCLEARTOWRITE(); - /* XXX We hardcode that we try a pubkey first */ + /* Order to try is pubkey, interactive, password. + * As soon as "finished" is set for one, we don't do any more. */ #ifdef ENABLE_CLI_PUBKEY_AUTH if (ses.authstate.authtypes & AUTH_TYPE_PUBKEY) { finished = cli_auth_pubkey(); @@ -197,13 +250,28 @@ } #endif +#ifdef ENABLE_CLI_INTERACT_AUTH + if (!finished && ses.authstate.authtypes & AUTH_TYPE_INTERACT) { + if (cli_ses.auth_interact_failed) { + finished = 0; + } else { + cli_auth_interactive(); + cli_ses.lastauthtype = AUTH_TYPE_INTERACT; + finished = 1; + } + } +#endif + #ifdef ENABLE_CLI_PASSWORD_AUTH if (!finished && ses.authstate.authtypes & AUTH_TYPE_PASSWORD) { - finished = cli_auth_password(); + cli_auth_password(); + finished = 1; cli_ses.lastauthtype = AUTH_TYPE_PASSWORD; } #endif + TRACE(("cli_auth_try lastauthtype %d", cli_ses.lastauthtype)) + if (!finished) { dropbear_exit("No auth methods could be used."); } diff -r bf64e666f99b -r efbaf6b03837 cli-authinteract.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cli-authinteract.c Tue Sep 20 17:35:21 2005 +0000 @@ -0,0 +1,168 @@ +/* + * Dropbear SSH + * + * Copyright (c) 2005 Matt Johnston + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ + +#include "includes.h" +#include "buffer.h" +#include "dbutil.h" +#include "session.h" +#include "ssh.h" +#include "runopts.h" + +#ifdef ENABLE_CLI_INTERACT_AUTH + +static unsigned char* get_response(unsigned char* prompt) +{ + FILE* tty = NULL; + unsigned char* response = NULL; + /* not a password, but a reasonable limit */ + char buf[DROPBEAR_MAX_CLI_PASS]; + char* ret = NULL; + + fprintf(stderr, "%s", prompt); + + tty = fopen(_PATH_TTY, "r"); + if (tty) { + ret = fgets(buf, sizeof(buf), tty); + fclose(tty); + } else { + ret = fgets(buf, sizeof(buf), stdin); + } + + if (ret == NULL) { + response = (unsigned char*)m_strdup(""); + } else { + unsigned int buflen = strlen(buf); + /* fgets includes newlines */ + if (buflen > 0 && buf[buflen-1] == '\n') + buf[buflen-1] = '\0'; + response = (unsigned char*)m_strdup(buf); + } + + m_burn(buf, sizeof(buf)); + + return response; +} + +void recv_msg_userauth_info_request() { + + unsigned char *name = NULL; + unsigned char *instruction = NULL; + unsigned int num_prompts = 0; + unsigned int i; + + unsigned char *prompt = NULL; + unsigned int echo = 0; + unsigned char *response = NULL; + + TRACE(("enter recv_msg_recv_userauth_info_request")) + + cli_ses.interact_request_received = 1; + + name = buf_getstring(ses.payload, NULL); + instruction = buf_getstring(ses.payload, NULL); + + /* language tag */ + buf_eatstring(ses.payload); + + num_prompts = buf_getint(ses.payload); + + if (num_prompts >= DROPBEAR_MAX_CLI_INTERACT_PROMPTS) { + dropbear_exit("Too many prompts received for keyboard-interactive"); + } + + /* we'll build the response as we go */ + CHECKCLEARTOWRITE(); + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_INFO_RESPONSE); + buf_putint(ses.writepayload, num_prompts); + + if (strlen(name) > 0) { + cleantext(name); + fprintf(stderr, "%s", name); + m_free(name); + } + if (strlen(instruction) > 0) { + cleantext(instruction); + fprintf(stderr, "%s", instruction); + m_free(instruction); + } + + for (i = 0; i < num_prompts; i++) { + unsigned int response_len = 0; + prompt = buf_getstring(ses.payload, NULL); + cleantext(prompt); + + echo = buf_getbool(ses.payload); + + if (echo) { + unsigned char* p = getpass(prompt); + response = m_strdup(p); + m_burn(p, strlen(p)); + } else { + response = get_response(prompt); + } + + response_len = strlen(response); + buf_putstring(ses.writepayload, response, response_len); + m_burn(response, response_len); + m_free(response); + } + + encrypt_packet(); + + + TRACE(("leave recv_msg_recv_userauth_info_request")) +} + +void cli_auth_interactive() { + + TRACE(("enter cli_auth_interactive")) + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_REQUEST); + + /* username */ + buf_putstring(ses.writepayload, cli_opts.username, + strlen(cli_opts.username)); + + /* service name */ + buf_putstring(ses.writepayload, SSH_SERVICE_CONNECTION, + SSH_SERVICE_CONNECTION_LEN); + + /* method */ + buf_putstring(ses.writepayload, AUTH_METHOD_INTERACT, + AUTH_METHOD_INTERACT_LEN); + + /* empty language tag */ + buf_putstring(ses.writepayload, "", 0); + + /* empty submethods */ + buf_putstring(ses.writepayload, "", 0); + + encrypt_packet(); + cli_ses.interact_request_received = 0; + + TRACE(("leave cli_auth_interactive")) + +} +#endif /* ENABLE_CLI_INTERACT_AUTH */ diff -r bf64e666f99b -r efbaf6b03837 cli-authpasswd.c --- a/cli-authpasswd.c Tue Sep 20 08:59:46 2005 +0000 +++ b/cli-authpasswd.c Tue Sep 20 17:35:21 2005 +0000 @@ -113,7 +113,7 @@ } #endif /* ENABLE_CLI_ASKPASS_HELPER */ -int cli_auth_password() { +void cli_auth_password() { char* password = NULL; @@ -149,7 +149,5 @@ m_burn(password, strlen(password)); TRACE(("leave cli_auth_password")) - return 1; /* Password auth can always be tried */ - } #endif /* ENABLE_CLI_PASSWORD_AUTH */ diff -r bf64e666f99b -r efbaf6b03837 cli-session.c --- a/cli-session.c Tue Sep 20 08:59:46 2005 +0000 +++ b/cli-session.c Tue Sep 20 17:35:21 2005 +0000 @@ -63,9 +63,7 @@ {SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation}, {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure}, {SSH_MSG_USERAUTH_BANNER, recv_msg_userauth_banner}, /* client */ -#ifdef ENABLE_CLI_PUBKEY_AUTH - {SSH_MSG_USERAUTH_PK_OK, recv_msg_userauth_pk_ok}, /* client */ -#endif + {SSH_MSG_USERAUTH_SPECIFIC_60, recv_msg_userauth_specific_60}, /* client */ {0, 0} /* End */ }; @@ -285,7 +283,8 @@ } /* Operates in-place turning dirty (untrusted potentially containing control - * characters) text into clean text. */ + * characters) text into clean text. + * Note: this is safe only with ascii - other charsets could have problems. */ void cleantext(unsigned char* dirtytext) { unsigned int i, j; diff -r bf64e666f99b -r efbaf6b03837 options.h --- a/options.h Tue Sep 20 08:59:46 2005 +0000 +++ b/options.h Tue Sep 20 17:35:21 2005 +0000 @@ -133,6 +133,7 @@ #define ENABLE_CLI_PASSWORD_AUTH #define ENABLE_CLI_PUBKEY_AUTH +#define ENABLE_CLI_INTERACT_AUTH /* Define this (as well as ENABLE_CLI_PASSWORD_AUTH) to allow the use of * a helper program for the ssh client. The helper program should be @@ -315,6 +316,10 @@ #define DROPBEAR_MAX_CLI_PASS 1024 +#define DROPBEAR_MAX_CLI_INTERACT_PROMPTS 80 /* The number of prompts we'll + accept for keyb-interactive + auth */ + #if defined(DROPBEAR_AES256_CBC) || defined(DROPBEAR_AES128_CBC) #define DROPBEAR_AES_CBC #endif diff -r bf64e666f99b -r efbaf6b03837 session.h --- a/session.h Tue Sep 20 08:59:46 2005 +0000 +++ b/session.h Tue Sep 20 17:35:21 2005 +0000 @@ -226,6 +226,13 @@ int lastauthtype; /* either AUTH_TYPE_PUBKEY or AUTH_TYPE_PASSWORD, for the last type of auth we tried */ +#ifdef ENABLE_CLI_INTERACT_AUTH + int auth_interact_failed; /* flag whether interactive auth can still + be used */ + int interact_request_received; /* flag whether we've received an + info request from the server for + interactive auth.*/ +#endif struct SignKeyList *lastprivkey; int retval; /* What the command exit status was - we emulate it */ diff -r bf64e666f99b -r efbaf6b03837 ssh.h --- a/ssh.h Tue Sep 20 08:59:46 2005 +0000 +++ b/ssh.h Tue Sep 20 17:35:21 2005 +0000 @@ -42,8 +42,19 @@ #define SSH_MSG_USERAUTH_FAILURE 51 #define SSH_MSG_USERAUTH_SUCCESS 52 #define SSH_MSG_USERAUTH_BANNER 53 + +/* packets 60-79 are method-specific, aren't one-one mapping */ +#define SSH_MSG_USERAUTH_SPECIFIC_60 60 + +#define SSH_MSG_USERAUTH_PASSWD_CHANGEREQ 60 + #define SSH_MSG_USERAUTH_PK_OK 60 +/* keyboard interactive auth */ +#define SSH_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH_MSG_USERAUTH_INFO_RESPONSE 61 + + /* If adding numbers here, check MAX_UNAUTH_PACKET_TYPE in process-packet.c * is still valid */