xref: /titanic_44/usr/src/cmd/ssh/sshd/auth2-pam.c (revision 628e3cbed6489fa1db545d8524a06cd6535af456)
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