Mercurial > dropbear
diff svr-authpam.c @ 925:bae0b34bc059 pam
Better PAM through recursion
author | Matt Johnston <matt@ucc.asn.au> |
---|---|
date | Wed, 12 Mar 2014 23:40:02 +0800 |
parents | fee485ce81eb |
children | 696205e3dc99 |
line wrap: on
line diff
--- 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 <pam/pam_appl.h> #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 */);