view 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 source

/*
 * Dropbear SSH
 * 
 * Copyright (c) 2004 Martin Carlsson
 * Portions (c) 2004 Matt Johnston
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE. */

/* Validates a user password using PAM */

#include "includes.h"
#include "session.h"
#include "buffer.h"
#include "dbutil.h"
#include "auth.h"
#include "ssh.h"

#ifdef ENABLE_SVR_PAM_AUTH

#if defined(HAVE_SECURITY_PAM_APPL_H)
#include <security/pam_appl.h>
#elif defined (HAVE_PAM_PAM_APPL_H)
#include <pam/pam_appl.h>
#endif

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 **msgs,
		struct pam_response **respp, 
		void *UNUSED(appdata_ptr)) {

	int ret = PAM_SYSTEM_ERR;

	TRACE(("enter pamConvFunc"))

	if (ses.recursion_count != 1) {
		dropbear_exit("PAM failure");
	}

	*respp = m_malloc(sizeof(struct pam_response) * num_msg);

	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;
	}

	return ret;
}

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", 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;
	}

	/* just to set it to something */
	if ((rc = pam_set_item(pamHandlep, PAM_TTY, "ssh") != PAM_SUCCESS)) {
		dropbear_log(LOG_WARNING, "pam_set_item() failed, rc=%d, %s",
				rc, pam_strerror(pamHandlep, rc));
		goto cleanup;
	}

#ifdef HAVE_PAM_FAIL_DELAY
	/* We have our own random delay code already, disable PAM's */
	(void) pam_fail_delay(pamHandlep, 0 /* musec_delay */);
#endif

	/* (void) pam_set_item(pamHandlep, PAM_FAIL_DELAY, (void*) pamDelayFunc); */

	if ((rc = pam_authenticate(pamHandlep, 0)) != PAM_SUCCESS) {
		dropbear_log(LOG_WARNING, "pam_authenticate() failed, rc=%d, %s", 
				rc, pam_strerror(pamHandlep, rc));
		dropbear_log(LOG_WARNING,
				"Bad PAM password attempt for '%s' from %s",
				ses.authstate.pw_name,
				svr_ses.addrstring);
		send_msg_userauth_failure(0, 1);
		goto cleanup;
	}

	if ((rc = pam_acct_mgmt(pamHandlep, 0)) != PAM_SUCCESS) {
		dropbear_log(LOG_WARNING, "pam_acct_mgmt() failed, rc=%d, %s", 
				rc, pam_strerror(pamHandlep, rc));
		dropbear_log(LOG_WARNING,
				"Bad PAM password attempt for '%s' from %s",
				ses.authstate.pw_name,
				svr_ses.addrstring);
		send_msg_userauth_failure(0, 1);
		goto cleanup;
	}

	/* successful authentication */
	dropbear_log(LOG_NOTICE, "PAM password auth succeeded for '%s' from %s",
			ses.authstate.pw_name,
			svr_ses.addrstring);
	send_msg_userauth_success();

cleanup:
	if (pamHandlep != NULL) {
		TRACE(("pam_end"))
		(void) pam_end(pamHandlep, 0 /* pam_status */);
	}
}

#endif /* ENABLE_SVR_PAM_AUTH */