# HG changeset patch # User Matt Johnston # Date 1394638802 -28800 # Node ID bae0b34bc059c12d14ea37756b5fa05af9317192 # Parent 25b7ed9fe8546bbaeeaa3351cb3bc9a85441bad4 Better PAM through recursion diff -r 25b7ed9fe854 -r bae0b34bc059 auth.h --- a/auth.h Sat Mar 08 21:00:57 2014 +0800 +++ b/auth.h Wed Mar 12 23:40:02 2014 +0800 @@ -40,6 +40,8 @@ void svr_auth_password(); void svr_auth_pubkey(); void svr_auth_pam(); +/* For PAM/interactive auth */ +void recv_msg_userauth_info_response(); #ifdef ENABLE_SVR_PUBKEY_OPTIONS int svr_pubkey_allows_agentfwd(); @@ -122,9 +124,14 @@ #ifdef ENABLE_SVR_PUBKEY_OPTIONS struct PubKeyOptions* pubkey_options; #endif + +#ifdef ENABLE_SVR_PAM_AUTH + int pam_status; + unsigned int pam_num_response; + struct pam_response ** pam_response; +#endif }; -#ifdef ENABLE_SVR_PUBKEY_OPTIONS struct PubKeyOptions; struct PubKeyOptions { /* Flags */ @@ -135,6 +142,5 @@ /* "command=" option. */ unsigned char * forced_command; }; -#endif #endif /* _AUTH_H_ */ diff -r 25b7ed9fe854 -r bae0b34bc059 buffer.c --- a/buffer.c Sat Mar 08 21:00:57 2014 +0800 +++ b/buffer.c Wed Mar 12 23:40:02 2014 +0800 @@ -180,6 +180,11 @@ buf->pos++; } +void buf_putbool(buffer* buf, unsigned int val) { + char truth = val ? 1 : 0; + buf_putbyte(buf, truth); +} + /* returns an in-place pointer to the buffer, checking that * the next len bytes from that position can be used */ unsigned char* buf_getptr(buffer* buf, unsigned int len) { diff -r 25b7ed9fe854 -r bae0b34bc059 buffer.h --- a/buffer.h Sat Mar 08 21:00:57 2014 +0800 +++ b/buffer.h Wed Mar 12 23:40:02 2014 +0800 @@ -52,6 +52,7 @@ unsigned char buf_getbyte(buffer* buf); unsigned char buf_getbool(buffer* buf); void buf_putbyte(buffer* buf, unsigned char val); +void buf_putbool(buffer* buf, unsigned int val); unsigned char* buf_getptr(buffer* buf, unsigned int len); unsigned char* buf_getwriteptr(buffer* buf, unsigned int len); unsigned char* buf_getstring(buffer* buf, unsigned int *retlen); diff -r 25b7ed9fe854 -r bae0b34bc059 cli-session.c --- a/cli-session.c Sat Mar 08 21:00:57 2014 +0800 +++ b/cli-session.c Wed Mar 12 23:40:02 2014 +0800 @@ -148,6 +148,7 @@ /* For printing "remote host closed" for the user */ ses.remoteclosed = cli_remoteclosed; + ses.loop_handler = cli_sessionloop; ses.extra_session_cleanup = cli_session_cleanup; /* packet handlers */ diff -r 25b7ed9fe854 -r bae0b34bc059 common-session.c --- a/common-session.c Sat Mar 08 21:00:57 2014 +0800 +++ b/common-session.c Wed Mar 12 23:40:02 2014 +0800 @@ -125,14 +125,17 @@ TRACE(("leave session_init")) } -void session_loop(void(*loophandler)()) { +void session_loop() { fd_set readfd, writefd; struct timeval timeout; int val; + assert(ses.recursion_count <= 1); + ses.recursion_count++; + /* main loop, select()s for all sockets in use */ - for(;;) { + while (!ses.exit_recursion) { timeout.tv_sec = select_timeout(); timeout.tv_usec = 0; @@ -218,13 +221,13 @@ * during rekeying ) */ channelio(&readfd, &writefd); - if (loophandler) { - loophandler(); + if (ses.loop_handler) { + ses.loop_handler(); } } /* for(;;) */ - - /* Not reached */ + ses.recursion_count--; + ses.exit_recursion = 0; } /* clean up a session on exit */ diff -r 25b7ed9fe854 -r bae0b34bc059 options.h --- a/options.h Sat Mar 08 21:00:57 2014 +0800 +++ b/options.h Wed Mar 12 23:40:02 2014 +0800 @@ -185,16 +185,9 @@ /* Authentication Types - at least one required. RFC Draft requires pubkey auth, and recommends password */ -/* Note: PAM auth is quite simple and only works for PAM modules which just do - * a simple "Login: " "Password: " (you can edit the strings in svr-authpam.c). - * It's useful for systems like OS X where standard password crypts don't work - * but there's an interface via a PAM module. It won't work for more complex - * PAM challenge/response. - * You can't enable both PASSWORD and PAM. */ - #define ENABLE_SVR_PASSWORD_AUTH /* PAM requires ./configure --enable-pam */ -/*#define ENABLE_SVR_PAM_AUTH */ +#define ENABLE_SVR_PAM_AUTH #define ENABLE_SVR_PUBKEY_AUTH /* Whether to take public key options in diff -r 25b7ed9fe854 -r bae0b34bc059 process-packet.c --- a/process-packet.c Sat Mar 08 21:00:57 2014 +0800 +++ b/process-packet.c Wed Mar 12 23:40:02 2014 +0800 @@ -35,7 +35,7 @@ #include "auth.h" #include "channel.h" -#define MAX_UNAUTH_PACKET_TYPE SSH_MSG_USERAUTH_PK_OK +#define MAX_UNAUTH_PACKET_TYPE 61 static void recv_unimplemented(); @@ -144,8 +144,10 @@ recv_unimplemented(); out: - buf_free(ses.payload); - ses.payload = NULL; + if (ses.payload) { + buf_free(ses.payload); + ses.payload = NULL; + } TRACE2(("leave process_packet")) } diff -r 25b7ed9fe854 -r bae0b34bc059 session.h --- a/session.h Sat Mar 08 21:00:57 2014 +0800 +++ b/session.h Wed Mar 12 23:40:02 2014 +0800 @@ -43,7 +43,7 @@ extern int exitflag; void common_session_init(int sock_in, int sock_out); -void session_loop(void(*loophandler)()); +void session_loop(); void session_cleanup(); void send_session_identification(); void send_msg_ignore(); @@ -102,7 +102,7 @@ struct sshsession { /* Is it a client or server? */ - unsigned char isserver; + unsigned int isserver; time_t connect_time; /* time the connection was established (cleared after auth once we're not @@ -132,16 +132,20 @@ const packettype * packettypes; /* Packet handler mappings for this session, see process-packet.c */ - unsigned dataallowed : 1; /* whether we can send data packets or we are in + unsigned int recursion_count; /* Set when the Dropbear main loop is called + recursively for PAM auth */ + unsigned int exit_recursion; + + unsigned int dataallowed; /* whether we can send data packets or we are in the middle of a KEX or something */ - unsigned char requirenext; /* byte indicating what packets we require next, + unsigned int requirenext; /* byte indicating what packets we require next, or 0x00 for any. */ - unsigned char ignorenext; /* whether to ignore the next packet, + unsigned int ignorenext; /* whether to ignore the next packet, used for kex_follows stuff */ - unsigned char lastpacket; /* What the last received packet type was */ + unsigned int lastpacket; /* What the last received packet type was */ int signal_pipe[2]; /* stores endpoints of a self-pipe used for race-free signal handling */ @@ -175,6 +179,7 @@ void(*remoteclosed)(); /* A callback to handle closure of the remote connection */ + void(*loop_handler)(); void(*extra_session_cleanup)(); /* client or server specific cleanup */ void(*send_kex_first_guess)(); diff -r 25b7ed9fe854 -r bae0b34bc059 svr-auth.c --- a/svr-auth.c Sat Mar 08 21:00:57 2014 +0800 +++ b/svr-auth.c Wed Mar 12 23:40:02 2014 +0800 @@ -59,11 +59,14 @@ #ifdef ENABLE_SVR_PUBKEY_AUTH ses.authstate.authtypes |= AUTH_TYPE_PUBKEY; #endif -#if defined(ENABLE_SVR_PASSWORD_AUTH) || defined(ENABLE_SVR_PAM_AUTH) +#ifdef ENABLE_SVR_PASSWORD_AUTH if (!svr_opts.noauthpass) { ses.authstate.authtypes |= AUTH_TYPE_PASSWORD; } #endif +#ifdef ENABLE_SVR_PAM_AUTH + ses.authstate.authtypes |= AUTH_TYPE_INTERACT; +#endif if (ses.authstate.pw_name) { m_free(ses.authstate.pw_name); } @@ -185,12 +188,11 @@ #endif #ifdef ENABLE_SVR_PAM_AUTH - if (!svr_opts.noauthpass && - !(svr_opts.norootpass && ses.authstate.pw_uid == 0) ) { + if (!(svr_opts.norootpass && ses.authstate.pw_uid == 0) ) { /* user wants to try password auth */ - if (methodlen == AUTH_METHOD_PASSWORD_LEN && - strncmp(methodname, AUTH_METHOD_PASSWORD, - AUTH_METHOD_PASSWORD_LEN) == 0) { + if (methodlen == AUTH_METHOD_INTERACT_LEN && + strncmp(methodname, AUTH_METHOD_INTERACT, + AUTH_METHOD_INTERACT_LEN) == 0) { if (valid_user) { svr_auth_pam(); goto out; @@ -330,7 +332,7 @@ buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_FAILURE); /* put a list of allowed types */ - typebuf = buf_new(30); /* long enough for PUBKEY and PASSWORD */ + typebuf = buf_new(55); if (ses.authstate.authtypes & AUTH_TYPE_PUBKEY) { buf_putbytes(typebuf, AUTH_METHOD_PUBKEY, AUTH_METHOD_PUBKEY_LEN); @@ -341,6 +343,13 @@ if (ses.authstate.authtypes & AUTH_TYPE_PASSWORD) { buf_putbytes(typebuf, AUTH_METHOD_PASSWORD, AUTH_METHOD_PASSWORD_LEN); + if (ses.authstate.authtypes & AUTH_TYPE_INTERACT) { + buf_putbyte(typebuf, ','); + } + } + + if (ses.authstate.authtypes & AUTH_TYPE_INTERACT) { + buf_putbytes(typebuf, AUTH_METHOD_INTERACT, AUTH_METHOD_INTERACT_LEN); } buf_putbufstring(ses.writepayload, typebuf); @@ -350,7 +359,7 @@ buf_free(typebuf); - buf_putbyte(ses.writepayload, partial ? 1 : 0); + buf_putbool(ses.writepayload, partial); encrypt_packet(); if (incrfail) { diff -r 25b7ed9fe854 -r bae0b34bc059 svr-authpam.c --- a/svr-authpam.c Sat Mar 08 21:00:57 2014 +0800 +++ b/svr-authpam.c Wed Mar 12 23:40:02 2014 +0800 @@ -30,6 +30,7 @@ #include "buffer.h" #include "dbutil.h" #include "auth.h" +#include "ssh.h" #ifdef ENABLE_SVR_PAM_AUTH @@ -39,179 +40,181 @@ #include #endif -struct UserDataS { - char* user; - char* passwd; +enum +{ + DROPBEAR_PAM_RETCODE_FILL = 100, + DROPBEAR_PAM_RETCODE_SKIP = 101, }; + +void recv_msg_userauth_info_response() { + unsigned int i, p; + unsigned int num_ssh_resp; + if (!ses.authstate.pam_response) { + /* A response was sent unprompted */ + send_msg_userauth_failure(0, 1); + return; + } + + if (ses.recursion_count != 2) { + dropbear_exit("PAM failure"); + } + + num_ssh_resp = buf_getint(ses.payload); + ses.authstate.pam_status = DROPBEAR_SUCCESS; + + for (i = 0, p = 0; i < ses.authstate.pam_num_response; i++) { + struct pam_response *resp = ses.authstate.pam_response[i]; + resp->resp = NULL; + + if (resp->resp_retcode == DROPBEAR_PAM_RETCODE_FILL) { + if (p >= num_ssh_resp) { + TRACE(("Too many PAM responses")) + ses.authstate.pam_status = DROPBEAR_FAILURE; + } else { + /* TODO convert to UTF8? */ + resp->resp = buf_getstring(ses.payload, NULL); + } + p++; + } + } + + if (p != num_ssh_resp) { + TRACE(("Not enough PAM responses")) + ses.authstate.pam_status = DROPBEAR_FAILURE; + } + + ses.exit_recursion = 1; +} + +static void +send_msg_userauth_info_request(unsigned int num_msg, const struct pam_message **msgs, + struct pam_response **respp) { + unsigned int i; + unsigned int pos, instruction_size, instruction_count; + CHECKCLEARTOWRITE(); + + buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_INFO_REQUEST); + + /* name */ + buf_putstring(ses.writepayload, ses.authstate.pw_name, 0); + + /* any informational messages are send as an instruction */ + pos = ses.writepayload->pos; + /* will be filled out later if required */ + buf_putint(ses.writepayload, 0); + instruction_size = 0; + instruction_count = 0; + for (i = 0; i < num_msg; i++) { + const struct pam_message *msg = msgs[i]; + if (msg->msg_style == PAM_ERROR_MSG) + { + buf_putbytes(ses.writepayload, "Error: ", strlen("Error: ")); + instruction_size += strlen("Error: "); + } + if (msg->msg_style == PAM_ERROR_MSG || msg->msg_style == PAM_TEXT_INFO) + { + buf_putbytes(ses.writepayload, msg->msg, strlen(msg->msg)); + buf_putbyte(ses.writepayload, '\n'); + instruction_size += strlen(msg->msg)+1; + instruction_count++; + respp[i]->resp_retcode = DROPBEAR_PAM_RETCODE_SKIP; + } + else + { + respp[i]->resp_retcode = DROPBEAR_PAM_RETCODE_FILL; + } + } + + if (instruction_size > 0) + { + /* Remove trailing newline */ + instruction_size--; + buf_incrlen(ses.writepayload, -1); + + /* Put the instruction string length */ + buf_setpos(ses.writepayload, pos); + buf_putint(ses.writepayload, instruction_size); + buf_setpos(ses.writepayload, ses.writepayload->len); + } + + /* language (deprecated) */ + buf_putstring(ses.writepayload, "", 0); + + /* num-prompts */ + buf_putint(ses.writepayload, num_msg-instruction_count); + + for (i = 0; i < num_msg; i++) { + const struct pam_message *msg = msgs[i]; + if (msg->msg_style != PAM_PROMPT_ECHO_OFF && msg->msg_style != PAM_PROMPT_ECHO_ON) { + /* was handled in "instruction" above */ + continue; + } + + /* prompt */ + buf_putstring(ses.writepayload, msg->msg, strlen(msg->msg)); + + /* echo */ + buf_putbool(ses.writepayload, msg->msg_style == PAM_PROMPT_ECHO_ON); + } + + encrypt_packet(); +} + /* PAM conversation function - for now we only handle one message */ int pamConvFunc(int num_msg, - const struct pam_message **msg, + const struct pam_message **msgs, struct pam_response **respp, - void *appdata_ptr) { + void *UNUSED(appdata_ptr)) { - int rc = PAM_SUCCESS; - struct pam_response* resp = NULL; - struct UserDataS* userDatap = (struct UserDataS*) appdata_ptr; - unsigned int msg_len = 0; - unsigned int i = 0; - char * compare_message = NULL; + int ret = PAM_SYSTEM_ERR; TRACE(("enter pamConvFunc")) - if (num_msg != 1) { - /* If you're getting here - Dropbear probably can't support your pam - * modules. This whole file is a bit of a hack around lack of - * asynchronocity in PAM anyway. */ - dropbear_log(LOG_INFO, "pamConvFunc() called with >1 messages: not supported."); - return PAM_CONV_ERR; - } - - /* make a copy we can strip */ - compare_message = m_strdup((*msg)->msg); - - /* Make the string lowercase. */ - msg_len = strlen(compare_message); - for (i = 0; i < msg_len; i++) { - compare_message[i] = tolower(compare_message[i]); - } - - /* If the string ends with ": ", remove the space. - ie "login: " vs "login:" */ - if (msg_len > 2 - && compare_message[msg_len-2] == ':' - && compare_message[msg_len-1] == ' ') { - compare_message[msg_len-1] = '\0'; + if (ses.recursion_count != 1) { + dropbear_exit("PAM failure"); } - switch((*msg)->msg_style) { - - case PAM_PROMPT_ECHO_OFF: + *respp = m_malloc(sizeof(struct pam_response) * num_msg); - if (!(strcmp(compare_message, "password:") == 0)) { - /* We don't recognise the prompt as asking for a password, - so can't handle it. Add more above as required for - different pam modules/implementations. If you need - to add an entry here please mail the Dropbear developer */ - dropbear_log(LOG_NOTICE, "PAM unknown prompt '%s' (no echo)", - compare_message); - rc = PAM_CONV_ERR; - break; - } + send_msg_userauth_info_request(num_msg, msgs, respp); - /* You have to read the PAM module-writers' docs (do we look like - * module writers? no.) to find out that the module will - * free the pam_response and its resp element - ie we _must_ malloc - * it here */ - resp = (struct pam_response*) m_malloc(sizeof(struct pam_response)); - memset(resp, 0, sizeof(struct pam_response)); - - resp->resp = m_strdup(userDatap->passwd); - m_burn(userDatap->passwd, strlen(userDatap->passwd)); - (*respp) = resp; - break; - - - case PAM_PROMPT_ECHO_ON: + ses.authstate.pam_num_response = num_msg; + ses.authstate.pam_response = respp; + ses.authstate.pam_status = DROPBEAR_FAILURE; + + buf_free(ses.payload); + ses.payload = NULL; - if (!( - (strcmp(compare_message, "login:" ) == 0) - || (strcmp(compare_message, "please enter username:") == 0) - || (strcmp(compare_message, "username:") == 0) - )) { - /* We don't recognise the prompt as asking for a username, - so can't handle it. Add more above as required for - different pam modules/implementations. If you need - to add an entry here please mail the Dropbear developer */ - dropbear_log(LOG_NOTICE, "PAM unknown prompt '%s' (with echo)", - compare_message); - rc = PAM_CONV_ERR; - break; - } - - /* You have to read the PAM module-writers' docs (do we look like - * module writers? no.) to find out that the module will - * free the pam_response and its resp element - ie we _must_ malloc - * it here */ - resp = (struct pam_response*) m_malloc(sizeof(struct pam_response)); - memset(resp, 0, sizeof(struct pam_response)); + /* Recurse! This will return once a SSH_MSG_USERAUTH_INFO_RESPONSE + has been received, with the ses.authstate.pam_* fields populated */ + session_loop(); - resp->resp = m_strdup(userDatap->user); - TRACE(("userDatap->user='%s'", userDatap->user)) - (*respp) = resp; - break; - - case PAM_ERROR_MSG: - case PAM_TEXT_INFO: - - if (msg_len > 0) { - buffer * pam_err = buf_new(msg_len + 4); - buf_setpos(pam_err, 0); - buf_putbytes(pam_err, "\r\n", 2); - buf_putbytes(pam_err, (*msg)->msg, msg_len); - buf_putbytes(pam_err, "\r\n", 2); - buf_setpos(pam_err, 0); - - send_msg_userauth_banner(pam_err); - buf_free(pam_err); - } - break; - - default: - TRACE(("Unknown message type")) - rc = PAM_CONV_ERR; - break; + if (ses.authstate.pam_status == DROPBEAR_FAILURE) { + ret = PAM_CONV_ERR; + m_free(*respp); + } else { + ses.authstate.pam_response = NULL; + ret = PAM_SUCCESS; } - m_free(compare_message); - TRACE(("leave pamConvFunc, rc %d", rc)) - - return rc; + return ret; } -/* Process a password auth request, sending success or failure messages as - * appropriate. To the client it looks like it's doing normal password auth (as - * opposed to keyboard-interactive or something), so the pam module has to be - * fairly standard (ie just "what's your username, what's your password, OK"). - * - * Keyboard interactive would be a lot nicer, but since PAM is synchronous, it - * gets very messy trying to send the interactive challenges, and read the - * interactive responses, over the network. */ void svr_auth_pam() { - - struct UserDataS userData = {NULL, NULL}; + int rc; struct pam_conv pamConv = { pamConvFunc, - &userData /* submitted to pamvConvFunc as appdata_ptr */ + NULL }; pam_handle_t* pamHandlep = NULL; - unsigned char * password = NULL; - unsigned int passwordlen; - - int rc = PAM_SUCCESS; - unsigned char changepw; - - /* check if client wants to change password */ - changepw = buf_getbool(ses.payload); - if (changepw) { - /* not implemented by this server */ - send_msg_userauth_failure(0, 1); - goto cleanup; - } - - password = buf_getstring(ses.payload, &passwordlen); - - /* used to pass data to the PAM conversation function - don't bother with - * strdup() etc since these are touched only by our own conversation - * function (above) which takes care of it */ - userData.user = ses.authstate.pw_name; - userData.passwd = password; + /* Ignore the payload, it has "language" and "submethods" */ /* Init pam */ - if ((rc = pam_start("sshd", NULL, &pamConv, &pamHandlep)) != PAM_SUCCESS) { + if ((rc = pam_start("sshd", ses.authstate.pw_name, &pamConv, &pamHandlep)) != PAM_SUCCESS) { dropbear_log(LOG_WARNING, "pam_start() failed, rc=%d, %s", rc, pam_strerror(pamHandlep, rc)); goto cleanup; @@ -260,10 +263,6 @@ send_msg_userauth_success(); cleanup: - if (password != NULL) { - m_burn(password, passwordlen); - m_free(password); - } if (pamHandlep != NULL) { TRACE(("pam_end")) (void) pam_end(pamHandlep, 0 /* pam_status */); diff -r 25b7ed9fe854 -r bae0b34bc059 svr-session.c --- a/svr-session.c Sat Mar 08 21:00:57 2014 +0800 +++ b/svr-session.c Wed Mar 12 23:40:02 2014 +0800 @@ -62,6 +62,9 @@ {SSH_MSG_CHANNEL_OPEN_CONFIRMATION, recv_msg_channel_open_confirmation}, {SSH_MSG_CHANNEL_OPEN_FAILURE, recv_msg_channel_open_failure}, #endif +#ifdef ENABLE_SVR_PAM_AUTH + {SSH_MSG_USERAUTH_INFO_RESPONSE, recv_msg_userauth_info_response}, +#endif {0, 0} /* End */ }; diff -r 25b7ed9fe854 -r bae0b34bc059 sysoptions.h --- a/sysoptions.h Sat Mar 08 21:00:57 2014 +0800 +++ b/sysoptions.h Wed Mar 12 23:40:02 2014 +0800 @@ -219,9 +219,7 @@ * with flushing compressed data */ #define DROPBEAR_ZLIB_MEM_LEVEL 8 -#if defined(ENABLE_SVR_PASSWORD_AUTH) && defined(ENABLE_SVR_PAM_AUTH) -#error "You can't turn on PASSWORD and PAM auth both at once. Fix it in options.h" -#endif +#define PAM_MAX_INFORMATION_SIZE 2000 /* We use dropbear_client and dropbear_server as shortcuts to avoid redundant * code, if we're just compiling as client or server */