Home | History | Annotate | Download | only in sshd
      1 /*
      2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
      3  * Use is subject to license terms.
      4  */
      5 
      6 #include "includes.h"
      7 
      8 RCSID("$Id: auth2-pam.c,v 1.14 2002/06/28 16:48:12 mouring Exp $");
      9 
     10 #pragma ident	"%Z%%M%	%I%	%E% SMI"
     11 
     12 #ifdef USE_PAM
     13 #include <security/pam_appl.h>
     14 
     15 #include "ssh.h"
     16 #include "ssh2.h"
     17 #include "auth.h"
     18 #include "auth-pam.h"
     19 #include "auth-options.h"
     20 #include "packet.h"
     21 #include "xmalloc.h"
     22 #include "dispatch.h"
     23 #include "canohost.h"
     24 #include "log.h"
     25 #include "servconf.h"
     26 #include "misc.h"
     27 
     28 #ifdef HAVE_BSM
     29 #include "bsmaudit.h"
     30 #endif /* HAVE_BSM */
     31 
     32 extern u_int utmp_len;
     33 extern ServerOptions options;
     34 
     35 extern Authmethod method_kbdint;
     36 extern Authmethod method_passwd;
     37 
     38 #define SSHD_PAM_KBDINT_SVC "sshd-kbdint"
     39 
     40 static int do_pam_conv_kbd_int(int num_msg,
     41     struct pam_message **msg, struct pam_response **resp,
     42     void *appdata_ptr);
     43 static void input_userauth_info_response_pam(int type,
     44 					     u_int32_t seqnr,
     45 					     void *ctxt);
     46 
     47 static struct pam_conv conv2 = {
     48 	do_pam_conv_kbd_int,
     49 	NULL,
     50 };
     51 
     52 static void do_pam_kbdint_cleanup(pam_handle_t *pamh);
     53 static void do_pam_kbdint(Authctxt *authctxt);
     54 
     55 void
     56 auth2_pam(Authctxt *authctxt)
     57 {
     58 	if (authctxt->user == NULL)
     59 		fatal("auth2_pam: internal error: no user");
     60 	if (authctxt->method == NULL)
     61 		fatal("auth2_pam: internal error: no method");
     62 
     63 	conv2.appdata_ptr = authctxt;
     64 	new_start_pam(authctxt, &conv2);
     65 
     66 	authctxt->method->method_data = NULL; /* freed in the conv func */
     67 	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE,
     68 	    &input_userauth_info_response_pam);
     69 
     70 	/*
     71 	 * Since password userauth and keyboard-interactive userauth
     72 	 * both use PAM, and since keyboard-interactive is so much
     73 	 * better than password userauth, we should not allow the user
     74 	 * to try password userauth after trying keyboard-interactive.
     75 	 */
     76 	if (method_passwd.enabled)
     77 		*method_passwd.enabled = 0;
     78 
     79 	do_pam_kbdint(authctxt);
     80 
     81 	dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL);
     82 }
     83 
     84 static void
     85 do_pam_kbdint(Authctxt *authctxt)
     86 {
     87 	int		 retval, retval2;
     88 	pam_handle_t	*pamh = authctxt->pam->h;
     89 	const char	*where = "authenticating";
     90 	char		*text = NULL;
     91 
     92 	debug2("Calling pam_authenticate()");
     93 	retval = pam_authenticate(pamh,
     94 	    options.permit_empty_passwd ? 0 :
     95 	    PAM_DISALLOW_NULL_AUTHTOK);
     96 
     97 	if (retval != PAM_SUCCESS)
     98 		goto cleanup;
     99 
    100 	debug2("kbd-int: pam_authenticate() succeeded");
    101 	where = "authorizing";
    102 	retval = pam_acct_mgmt(pamh, 0);
    103 
    104 	if (retval == PAM_NEW_AUTHTOK_REQD) {
    105 		if (authctxt->valid && authctxt->pw != NULL) {
    106 			/* send password expiration warning */
    107 			message_cat(&text,
    108 			    gettext("Warning: Your password has expired,"
    109 			    " please change it now."));
    110 			packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
    111 			packet_put_cstring("");		/* name */
    112 			packet_put_cstring(text);	/* instructions */
    113 			packet_put_cstring("");		/* language, unused */
    114 			packet_put_int(0);
    115 			packet_send();
    116 			packet_write_wait();
    117 			debug("expiration message sent");
    118 			if (text)
    119 				xfree(text);
    120 			/*
    121 			 * wait for the response so it does not mix
    122 			 * with the upcoming PAM conversation
    123 			 */
    124 			packet_read_expect(SSH2_MSG_USERAUTH_INFO_RESPONSE);
    125 			/*
    126 			 * Can't use temporarily_use_uid() and restore_uid()
    127 			 * here because we need (euid == 0 && ruid == pw_uid)
    128 			 * whereas temporarily_use_uid() arranges for
    129 			 * (suid = 0 && euid == pw_uid && ruid == pw_uid).
    130 			 */
    131 			(void) setreuid(authctxt->pw->pw_uid, -1);
    132 			debug2("kbd-int: changing expired password");
    133 			where = "changing authentication tokens (password)";
    134 			retval = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
    135 			audit_sshd_chauthtok(retval, authctxt->pw->pw_uid,
    136 				authctxt->pw->pw_gid);
    137 			(void) setreuid(0, -1);
    138 		} else {
    139 			retval = PAM_PERM_DENIED;
    140 		}
    141 	}
    142 
    143 	if (retval != PAM_SUCCESS)
    144 		goto cleanup;
    145 
    146 	authctxt->pam->state |= PAM_S_DONE_ACCT_MGMT;
    147 
    148 	retval = finish_userauth_do_pam(authctxt);
    149 
    150 	if (retval != PAM_SUCCESS)
    151 		goto cleanup;
    152 
    153 	/*
    154 	 * PAM handle stays around so we can call pam_close_session()
    155 	 * on it later.
    156 	 */
    157 	authctxt->method->authenticated = 1;
    158 	debug2("kbd-int: success (pam->state == %x)", authctxt->pam->state);
    159 	return;
    160 
    161 cleanup:
    162 	/*
    163 	 * Check for abandonment and cleanup.  When kbdint is abandoned
    164 	 * authctxt->pam->h is NULLed and by this point a new handle may
    165 	 * be allocated.
    166 	 */
    167 	if (authctxt->pam->h != pamh) {
    168 		log("Keyboard-interactive (PAM) userauth abandoned "
    169 		    "while %s", where);
    170 		if ((retval2 = pam_end(pamh, retval)) != PAM_SUCCESS) {
    171 			log("Cannot close PAM handle after "
    172 			    "kbd-int userauth abandonment[%d]: %.200s",
    173 			    retval2, PAM_STRERROR(pamh, retval2));
    174 		}
    175 		authctxt->method->abandoned = 1;
    176 
    177 		/*
    178 		 * Avoid double counting; these are incremented in
    179 		 * kbdint_pam_abandon() so that they reflect the correct
    180 		 * count when userauth_finish() is called before
    181 		 * unwinding the dispatch_run() loop, but they are
    182 		 * incremented again in input_userauth_request() when
    183 		 * the loop is unwound, right here.
    184 		 */
    185 		if (authctxt->method->abandons)
    186 			authctxt->method->abandons--;
    187 		if (authctxt->method->attempts)
    188 			authctxt->method->attempts--;
    189 	}
    190 	else {
    191 		/* Save error value for pam_end() */
    192 		authctxt->pam->last_pam_retval = retval;
    193 		log("Keyboard-interactive (PAM) userauth failed[%d] "
    194 		    "while %s: %.200s", retval, where,
    195 		    PAM_STRERROR(pamh, retval));
    196 		/* pam handle can be reused elsewhere, so no pam_end() here */
    197 	}
    198 
    199 	return;
    200 }
    201 
    202 static int
    203 do_pam_conv_kbd_int(int num_msg, struct pam_message **msg,
    204     struct pam_response **resp, void *appdata_ptr)
    205 {
    206 	int i, j;
    207 	char *text;
    208 	Convctxt *conv_ctxt;
    209 	Authctxt *authctxt = (Authctxt *)appdata_ptr;
    210 
    211 	if (!authctxt || !authctxt->method) {
    212 		debug("Missing state during PAM conversation");
    213 		return PAM_CONV_ERR;
    214 	}
    215 
    216 	conv_ctxt = xmalloc(sizeof(Convctxt));
    217 	(void) memset(conv_ctxt, 0, sizeof(Convctxt));
    218 	conv_ctxt->finished = 0;
    219 	conv_ctxt->num_received = 0;
    220 	conv_ctxt->num_expected = 0;
    221 	conv_ctxt->prompts = xmalloc(sizeof(int) * num_msg);
    222 	conv_ctxt->responses = xmalloc(sizeof(struct pam_response) * num_msg);
    223 	(void) memset(conv_ctxt->responses, 0, sizeof(struct pam_response) * num_msg);
    224 
    225 	text = NULL;
    226 	for (i = 0, conv_ctxt->num_expected = 0; i < num_msg; i++) {
    227 		int style = PAM_MSG_MEMBER(msg, i, msg_style);
    228 		switch (style) {
    229 		case PAM_PROMPT_ECHO_ON:
    230 			debug2("PAM echo on prompt: %s",
    231 				PAM_MSG_MEMBER(msg, i, msg));
    232 			conv_ctxt->num_expected++;
    233 			break;
    234 		case PAM_PROMPT_ECHO_OFF:
    235 			debug2("PAM echo off prompt: %s",
    236 				PAM_MSG_MEMBER(msg, i, msg));
    237 			conv_ctxt->num_expected++;
    238 			break;
    239 		case PAM_TEXT_INFO:
    240 			debug2("PAM text info prompt: %s",
    241 				PAM_MSG_MEMBER(msg, i, msg));
    242 			message_cat(&text, PAM_MSG_MEMBER(msg, i, msg));
    243 			break;
    244 		case PAM_ERROR_MSG:
    245 			debug2("PAM error prompt: %s",
    246 				PAM_MSG_MEMBER(msg, i, msg));
    247 			message_cat(&text, PAM_MSG_MEMBER(msg, i, msg));
    248 			break;
    249 		default:
    250 			/* Capture all these messages to be sent at once */
    251 			message_cat(&text, PAM_MSG_MEMBER(msg, i, msg));
    252 			break;
    253 		}
    254 	}
    255 
    256 	if (conv_ctxt->num_expected == 0 && text == NULL) {
    257 		xfree(conv_ctxt->prompts);
    258 		xfree(conv_ctxt->responses);
    259 		xfree(conv_ctxt);
    260 		return PAM_SUCCESS;
    261 	}
    262 
    263 	authctxt->method->method_data = (void *) conv_ctxt;
    264 
    265 	packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
    266 	packet_put_cstring("");	/* Name */
    267 	packet_put_cstring(text ? text : "");	/* Instructions */
    268 	packet_put_cstring("");	/* Language */
    269 	packet_put_int(conv_ctxt->num_expected);
    270 
    271 	if (text)
    272 		xfree(text);
    273 
    274 	for (i = 0, j = 0; i < num_msg; i++) {
    275 		int style = PAM_MSG_MEMBER(msg, i, msg_style);
    276 
    277 		/* Skip messages which don't need a reply */
    278 		if (style != PAM_PROMPT_ECHO_ON && style != PAM_PROMPT_ECHO_OFF)
    279 			continue;
    280 
    281 		conv_ctxt->prompts[j++] = i;
    282 		packet_put_cstring(PAM_MSG_MEMBER(msg, i, msg));
    283 		packet_put_char(style == PAM_PROMPT_ECHO_ON);
    284 	}
    285 	packet_send();
    286 	packet_write_wait();
    287 
    288 	/*
    289 	 * Here the dispatch_run() loop is nested.  It should be unwound
    290 	 * if keyboard-interactive userauth is abandoned (or restarted;
    291 	 * same thing).
    292 	 *
    293 	 * The condition for breaking out of the nested dispatch_run() loop is
    294 	 *     ((got kbd-int info reponse) || (kbd-int abandoned))
    295 	 *
    296 	 * conv_ctxt->finished is set in either of those cases.
    297 	 *
    298 	 * When abandonment is detected the conv_ctxt->finished is set as
    299 	 * is conv_ctxt->abandoned, causing this function to signal
    300 	 * userauth nested dispatch_run() loop unwinding and to return
    301 	 * PAM_CONV_ERR;
    302 	 */
    303 	debug2("Nesting dispatch_run loop");
    304 	dispatch_run(DISPATCH_BLOCK, &conv_ctxt->finished, appdata_ptr);
    305 	debug2("Nested dispatch_run loop exited");
    306 
    307 	if (conv_ctxt->abandoned) {
    308 		authctxt->unwind_dispatch_loop = 1;
    309 		xfree(conv_ctxt->prompts);
    310 		xfree(conv_ctxt->responses);
    311 		xfree(conv_ctxt);
    312 		debug("PAM conv function returns PAM_CONV_ERR");
    313 		return PAM_CONV_ERR;
    314 	}
    315 
    316 	if (conv_ctxt->num_received == conv_ctxt->num_expected) {
    317 		*resp = conv_ctxt->responses;
    318 		xfree(conv_ctxt->prompts);
    319 		xfree(conv_ctxt);
    320 		debug("PAM conv function returns PAM_SUCCESS");
    321 		return PAM_SUCCESS;
    322 	}
    323 
    324 	debug("PAM conv function returns PAM_CONV_ERR");
    325 	xfree(conv_ctxt->prompts);
    326 	xfree(conv_ctxt->responses);
    327 	xfree(conv_ctxt);
    328 	return PAM_CONV_ERR;
    329 }
    330 
    331 static void
    332 input_userauth_info_response_pam(int type, u_int32_t seqnr, void *ctxt)
    333 {
    334 	Authctxt *authctxt = ctxt;
    335 	Convctxt *conv_ctxt;
    336 	unsigned int nresp = 0, rlen = 0, i = 0;
    337 	char *resp;
    338 
    339 	if (authctxt == NULL)
    340 		fatal("input_userauth_info_response_pam: no authentication context");
    341 
    342 	/* Check for spurious/unexpected info response */
    343 	if (method_kbdint.method_data == NULL) {
    344 		debug("input_userauth_info_response_pam: no method context");
    345 		return;
    346 	}
    347 
    348 	conv_ctxt = (Convctxt *) method_kbdint.method_data;
    349 
    350 	nresp = packet_get_int();	/* Number of responses. */
    351 	debug("got %d responses", nresp);
    352 
    353 
    354 #if 0
    355 	if (nresp != conv_ctxt->num_expected)
    356 		fatal("%s: Received incorrect number of responses "
    357 		    "(expected %d, received %u)", __func__,
    358 		    conv_ctxt->num_expected, nresp);
    359 #endif
    360 
    361 	if (nresp > 100)
    362 		fatal("%s: too many replies", __func__);
    363 
    364 	for (i = 0; i < nresp && i < conv_ctxt->num_expected ; i++) {
    365 		int j = conv_ctxt->prompts[i];
    366 
    367 		resp = packet_get_string(&rlen);
    368 		if (i < conv_ctxt->num_expected) {
    369 			conv_ctxt->responses[j].resp_retcode = PAM_SUCCESS;
    370 			conv_ctxt->responses[j].resp = xstrdup(resp);
    371 			conv_ctxt->num_received++;
    372 		}
    373 		xfree(resp);
    374 	}
    375 
    376 	if (nresp < conv_ctxt->num_expected)
    377 		fatal("%s: too few replies (%d < %d)", __func__,
    378 		    nresp, conv_ctxt->num_expected);
    379 
    380 	/* XXX - This could make a covert channel... */
    381 	if (nresp > conv_ctxt->num_expected)
    382 		debug("Ignoring additional PAM replies");
    383 
    384 	conv_ctxt->finished = 1;
    385 
    386 	packet_check_eom();
    387 }
    388 
    389 #if 0
    390 int
    391 kbdint_pam_abandon_chk(Authctxt *authctxt, Authmethod *method)
    392 {
    393 	if (!method)
    394 		return 0; /* fatal(), really; it'll happen somewhere else */
    395 
    396 	if (!method->method_data)
    397 		return 0;
    398 
    399 	return 1;
    400 }
    401 #endif
    402 
    403 void
    404 kbdint_pam_abandon(Authctxt *authctxt, Authmethod *method)
    405 {
    406 	Convctxt *conv_ctxt;
    407 
    408 	/*
    409 	 * But, if it ever becomes desirable and possible to support
    410 	 * kbd-int userauth abandonment, here's what must be done.
    411 	 */
    412 	if (!method)
    413 		return;
    414 
    415 	if (!method->method_data)
    416 		return;
    417 
    418 	conv_ctxt = (Convctxt *) method->method_data;
    419 
    420 	/* dispatch_run() loop will exit */
    421 	conv_ctxt->abandoned = 1;
    422 	conv_ctxt->finished = 1;
    423 
    424 	/*
    425 	 * The method_data will be free in the corresponding, active
    426 	 * conversation function
    427 	 */
    428 	method->method_data = NULL;
    429 
    430 	/* update counts that can't be updated elsewhere */
    431 	method->abandons++;
    432 	method->attempts++;
    433 
    434 	/* Finally, we cannot re-use the current current PAM handle */
    435 	authctxt->pam->h = NULL;    /* Let the conv function cleanup */
    436 }
    437 #endif
    438