changeset 925:bae0b34bc059 pam

Better PAM through recursion
author Matt Johnston <matt@ucc.asn.au>
date Wed, 12 Mar 2014 23:40:02 +0800
parents 25b7ed9fe854
children 73ea0dce9a57
files auth.h buffer.c buffer.h cli-session.c common-session.c options.h process-packet.c session.h svr-auth.c svr-authpam.c svr-session.c sysoptions.h
diffstat 12 files changed, 209 insertions(+), 184 deletions(-) [+]
line wrap: on
line diff
--- 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_ */
--- 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) {
--- 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);
--- 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 */
--- 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 */
--- 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 
--- 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"))
 }
--- 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)();
 
--- 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) {
--- 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 */);
--- 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 */
 };
 
--- 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 */