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 */);