diff options
author | Matt Johnston <matt@ucc.asn.au> | 2015-03-16 21:34:05 +0800 |
---|---|---|
committer | Matt Johnston <matt@ucc.asn.au> | 2015-03-16 21:34:05 +0800 |
commit | b1672d7f2ff204f6428392100e821ea7c49fe5c6 (patch) | |
tree | 5ebb1dc268be9ff9564bbe936e3b61ff77a5d2ad | |
parent | 95daedaaa0728033fa9ff5f6fadda72c476fe006 (diff) | |
parent | 8333636f62e396197b2de12fdaa27ac783fe4f30 (diff) | |
download | dropbear-b1672d7f2ff204f6428392100e821ea7c49fe5c6.tar.gz |
merge pam branch up to date
-rw-r--r-- | auth.h | 10 | ||||
-rw-r--r-- | buffer.c | 5 | ||||
-rw-r--r-- | buffer.h | 1 | ||||
-rw-r--r-- | cli-session.c | 1 | ||||
-rw-r--r-- | common-session.c | 11 | ||||
-rw-r--r-- | options.h | 9 | ||||
-rw-r--r-- | process-packet.c | 8 | ||||
-rw-r--r-- | session.h | 15 | ||||
-rw-r--r-- | svr-auth.c | 25 | ||||
-rw-r--r-- | svr-authpam.c | 293 | ||||
-rw-r--r-- | svr-session.c | 3 | ||||
-rw-r--r-- | sysoptions.h | 4 |
12 files changed, 205 insertions, 180 deletions
@@ -40,6 +40,8 @@ void send_msg_userauth_banner(buffer *msg); 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 @@ struct AuthState { #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 @@ struct PubKeyOptions { /* "command=" option. */ unsigned char * forced_command; }; -#endif #endif /* DROPBEAR_AUTH_H_ */ @@ -180,6 +180,11 @@ void buf_putbyte(buffer* buf, unsigned char val) { 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) { @@ -54,6 +54,7 @@ void buf_incrwritepos(buffer* buf, unsigned int incr); 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 --git a/cli-session.c b/cli-session.c index 815f5b6..893f459 100644 --- a/cli-session.c +++ b/cli-session.c @@ -172,6 +172,7 @@ static void cli_session_init() { /* 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 --git a/common-session.c b/common-session.c index 164dc85..cf5ff07 100644 --- a/common-session.c +++ b/common-session.c @@ -137,14 +137,17 @@ void common_session_init(int sock_in, int sock_out) { 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; @@ -244,8 +247,8 @@ void session_loop(void(*loophandler)()) { } } /* for(;;) */ - - /* Not reached */ + ses.recursion_count--; + ses.exit_recursion = 0; } static void cleanup_buf(buffer **buf) { @@ -194,16 +194,9 @@ much traffic. */ /* 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 --git a/process-packet.c b/process-packet.c index ddeb9ce..39dc496 100644 --- a/process-packet.c +++ b/process-packet.c @@ -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(); @@ -154,8 +154,10 @@ void process_packet() { recv_unimplemented(); out: - buf_free(ses.payload); - ses.payload = NULL; + if (ses.payload) { + buf_free(ses.payload); + ses.payload = NULL; + } TRACE2(("leave process_packet")) } @@ -107,7 +107,7 @@ struct packetlist { struct sshsession { /* Is it a client or server? */ - unsigned char isserver; + unsigned int isserver; int sock_in; int sock_out; @@ -137,16 +137,20 @@ struct sshsession { 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 */ @@ -185,6 +189,7 @@ struct sshsession { 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)(); @@ -59,11 +59,14 @@ static void authclear() { #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 @@ void recv_msg_userauth_request() { #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 @@ void send_msg_userauth_failure(int partial, int incrfail) { 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 @@ void send_msg_userauth_failure(int partial, int incrfail) { 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 @@ void send_msg_userauth_failure(int partial, int incrfail) { buf_free(typebuf); - buf_putbyte(ses.writepayload, partial ? 1 : 0); + buf_putbool(ses.writepayload, partial); encrypt_packet(); if (incrfail) { diff --git a/svr-authpam.c b/svr-authpam.c index 0b1d69f..5449463 100644 --- a/svr-authpam.c +++ b/svr-authpam.c @@ -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 <pam/pam_appl.h> #endif -struct UserDataS { - char* user; - char* passwd; +enum +{ + DROPBEAR_PAM_RETCODE_FILL = 100, + DROPBEAR_PAM_RETCODE_SKIP = 101, }; -/* PAM conversation function - for now we only handle one message */ -int -pamConvFunc(int num_msg, - const struct pam_message **msg, - struct pam_response **respp, - void *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; +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; + } - TRACE(("enter pamConvFunc")) + if (ses.recursion_count != 2) { + dropbear_exit("PAM failure"); + } + + num_ssh_resp = buf_getint(ses.payload); + ses.authstate.pam_status = DROPBEAR_SUCCESS; - 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; + 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++; + } } - /* 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 (p != num_ssh_resp) { + TRACE(("Not enough PAM responses")) + ses.authstate.pam_status = DROPBEAR_FAILURE; } - /* 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'; + 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; + } } - switch((*msg)->msg_style) { + if (instruction_size > 0) + { + /* Remove trailing newline */ + instruction_size--; + buf_incrlen(ses.writepayload, -1); - case PAM_PROMPT_ECHO_OFF: + /* Put the instruction string length */ + buf_setpos(ses.writepayload, pos); + buf_putint(ses.writepayload, instruction_size); + buf_setpos(ses.writepayload, ses.writepayload->len); + } - 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; - } + /* language (deprecated) */ + buf_putstring(ses.writepayload, "", 0); - /* 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: - - 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; - } + /* num-prompts */ + buf_putint(ses.writepayload, num_msg-instruction_count); - /* 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->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; + 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; + } - default: - TRACE(("Unknown message type")) - rc = PAM_CONV_ERR; - break; - } + /* prompt */ + buf_putstring(ses.writepayload, msg->msg, strlen(msg->msg)); - m_free(compare_message); - TRACE(("leave pamConvFunc, rc %d", rc)) + /* echo */ + buf_putbool(ses.writepayload, msg->msg_style == PAM_PROMPT_ECHO_ON); + } - return rc; + encrypt_packet(); } -/* 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() { +/* PAM conversation function - for now we only handle one message */ +int +pamConvFunc(int num_msg, + const struct pam_message **msgs, + struct pam_response **respp, + void *UNUSED(appdata_ptr)) { - struct UserDataS userData = {NULL, NULL}; - struct pam_conv pamConv = { - pamConvFunc, - &userData /* submitted to pamvConvFunc as appdata_ptr */ - }; + int ret = PAM_SYSTEM_ERR; - pam_handle_t* pamHandlep = NULL; + TRACE(("enter pamConvFunc")) - unsigned char * password = NULL; - unsigned int passwordlen; + if (ses.recursion_count != 1) { + dropbear_exit("PAM failure"); + } - int rc = PAM_SUCCESS; - unsigned char changepw; + *respp = m_malloc(sizeof(struct pam_response) * num_msg); - /* 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; + send_msg_userauth_info_request(num_msg, msgs, respp); + + 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; + + /* Recurse! This will return once a SSH_MSG_USERAUTH_INFO_RESPONSE + has been received, with the ses.authstate.pam_* fields populated */ + session_loop(); + + if (ses.authstate.pam_status == DROPBEAR_FAILURE) { + ret = PAM_CONV_ERR; + m_free(*respp); + } else { + ses.authstate.pam_response = NULL; + ret = PAM_SUCCESS; } - password = buf_getstring(ses.payload, &passwordlen); + return ret; +} - /* 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; +void svr_auth_pam() { + int rc; + struct pam_conv pamConv = { + pamConvFunc, + NULL + }; + + pam_handle_t* pamHandlep = NULL; + + /* 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 @@ void svr_auth_pam() { 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 --git a/svr-session.c b/svr-session.c index 8485905..24a5a36 100644 --- a/svr-session.c +++ b/svr-session.c @@ -66,6 +66,9 @@ static const packettype svr_packettypes[] = { {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 --git a/sysoptions.h b/sysoptions.h index 11dc10d..44af267 100644 --- a/sysoptions.h +++ b/sysoptions.h @@ -218,9 +218,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 */ |