xref: /titanic_44/usr/src/cmd/ssh/sshd/auth2-pam.c (revision 7257d1b4d25bfac0c802847390e98a464fd787ac)
1 /*
2  * Copyright 2007 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 	if ((retval = pam_authenticate(pamh, 0)) != PAM_SUCCESS)
94 		goto cleanup;
95 
96 	debug2("kbd-int: pam_authenticate() succeeded");
97 	where = "authorizing";
98 	retval = pam_acct_mgmt(pamh, 0);
99 
100 	if (retval == PAM_NEW_AUTHTOK_REQD) {
101 		if (authctxt->valid && authctxt->pw != NULL) {
102 			/* send password expiration warning */
103 			message_cat(&text,
104 			    gettext("Warning: Your password has expired,"
105 			    " please change it now."));
106 			packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
107 			packet_put_cstring("");		/* name */
108 			packet_put_cstring(text);	/* instructions */
109 			packet_put_cstring("");		/* language, unused */
110 			packet_put_int(0);
111 			packet_send();
112 			packet_write_wait();
113 			debug("expiration message sent");
114 			if (text)
115 				xfree(text);
116 			/*
117 			 * wait for the response so it does not mix
118 			 * with the upcoming PAM conversation
119 			 */
120 			packet_read_expect(SSH2_MSG_USERAUTH_INFO_RESPONSE);
121 			/*
122 			 * Can't use temporarily_use_uid() and restore_uid()
123 			 * here because we need (euid == 0 && ruid == pw_uid)
124 			 * whereas temporarily_use_uid() arranges for
125 			 * (suid = 0 && euid == pw_uid && ruid == pw_uid).
126 			 */
127 			(void) setreuid(authctxt->pw->pw_uid, -1);
128 			debug2("kbd-int: changing expired password");
129 			where = "changing authentication tokens (password)";
130 			retval = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
131 			audit_sshd_chauthtok(retval, authctxt->pw->pw_uid,
132 				authctxt->pw->pw_gid);
133 			(void) setreuid(0, -1);
134 		} else {
135 			retval = PAM_PERM_DENIED;
136 		}
137 	}
138 
139 	if (retval != PAM_SUCCESS)
140 		goto cleanup;
141 
142 	authctxt->pam->state |= PAM_S_DONE_ACCT_MGMT;
143 
144 	retval = finish_userauth_do_pam(authctxt);
145 
146 	if (retval != PAM_SUCCESS)
147 		goto cleanup;
148 
149 	/*
150 	 * PAM handle stays around so we can call pam_close_session()
151 	 * on it later.
152 	 */
153 	authctxt->method->authenticated = 1;
154 	debug2("kbd-int: success (pam->state == %x)", authctxt->pam->state);
155 	return;
156 
157 cleanup:
158 	/*
159 	 * Check for abandonment and cleanup.  When kbdint is abandoned
160 	 * authctxt->pam->h is NULLed and by this point a new handle may
161 	 * be allocated.
162 	 */
163 	if (authctxt->pam->h != pamh) {
164 		log("Keyboard-interactive (PAM) userauth abandoned "
165 		    "while %s", where);
166 		if ((retval2 = pam_end(pamh, retval)) != PAM_SUCCESS) {
167 			log("Cannot close PAM handle after "
168 			    "kbd-int userauth abandonment[%d]: %.200s",
169 			    retval2, PAM_STRERROR(pamh, retval2));
170 		}
171 		authctxt->method->abandoned = 1;
172 
173 		/*
174 		 * Avoid double counting; these are incremented in
175 		 * kbdint_pam_abandon() so that they reflect the correct
176 		 * count when userauth_finish() is called before
177 		 * unwinding the dispatch_run() loop, but they are
178 		 * incremented again in input_userauth_request() when
179 		 * the loop is unwound, right here.
180 		 */
181 		if (authctxt->method->abandons)
182 			authctxt->method->abandons--;
183 		if (authctxt->method->attempts)
184 			authctxt->method->attempts--;
185 	}
186 	else {
187 		/* Save error value for pam_end() */
188 		authctxt->pam->last_pam_retval = retval;
189 		log("Keyboard-interactive (PAM) userauth failed[%d] "
190 		    "while %s: %.200s", retval, where,
191 		    PAM_STRERROR(pamh, retval));
192 		/* pam handle can be reused elsewhere, so no pam_end() here */
193 	}
194 
195 	return;
196 }
197 
198 static int
199 do_pam_conv_kbd_int(int num_msg, struct pam_message **msg,
200     struct pam_response **resp, void *appdata_ptr)
201 {
202 	int i, j;
203 	char *text;
204 	Convctxt *conv_ctxt;
205 	Authctxt *authctxt = (Authctxt *)appdata_ptr;
206 
207 	if (!authctxt || !authctxt->method) {
208 		debug("Missing state during PAM conversation");
209 		return PAM_CONV_ERR;
210 	}
211 
212 	conv_ctxt = xmalloc(sizeof(Convctxt));
213 	(void) memset(conv_ctxt, 0, sizeof(Convctxt));
214 	conv_ctxt->finished = 0;
215 	conv_ctxt->num_received = 0;
216 	conv_ctxt->num_expected = 0;
217 	conv_ctxt->prompts = xmalloc(sizeof(int) * num_msg);
218 	conv_ctxt->responses = xmalloc(sizeof(struct pam_response) * num_msg);
219 	(void) memset(conv_ctxt->responses, 0, sizeof(struct pam_response) * num_msg);
220 
221 	text = NULL;
222 	for (i = 0, conv_ctxt->num_expected = 0; i < num_msg; i++) {
223 		int style = PAM_MSG_MEMBER(msg, i, msg_style);
224 		switch (style) {
225 		case PAM_PROMPT_ECHO_ON:
226 			debug2("PAM echo on prompt: %s",
227 				PAM_MSG_MEMBER(msg, i, msg));
228 			conv_ctxt->num_expected++;
229 			break;
230 		case PAM_PROMPT_ECHO_OFF:
231 			debug2("PAM echo off prompt: %s",
232 				PAM_MSG_MEMBER(msg, i, msg));
233 			conv_ctxt->num_expected++;
234 			break;
235 		case PAM_TEXT_INFO:
236 			debug2("PAM text info prompt: %s",
237 				PAM_MSG_MEMBER(msg, i, msg));
238 			message_cat(&text, PAM_MSG_MEMBER(msg, i, msg));
239 			break;
240 		case PAM_ERROR_MSG:
241 			debug2("PAM error prompt: %s",
242 				PAM_MSG_MEMBER(msg, i, msg));
243 			message_cat(&text, PAM_MSG_MEMBER(msg, i, msg));
244 			break;
245 		default:
246 			/* Capture all these messages to be sent at once */
247 			message_cat(&text, PAM_MSG_MEMBER(msg, i, msg));
248 			break;
249 		}
250 	}
251 
252 	if (conv_ctxt->num_expected == 0 && text == NULL) {
253 		xfree(conv_ctxt->prompts);
254 		xfree(conv_ctxt->responses);
255 		xfree(conv_ctxt);
256 		return PAM_SUCCESS;
257 	}
258 
259 	authctxt->method->method_data = (void *) conv_ctxt;
260 
261 	packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST);
262 	packet_put_cstring("");	/* Name */
263 	packet_put_cstring(text ? text : "");	/* Instructions */
264 	packet_put_cstring("");	/* Language */
265 	packet_put_int(conv_ctxt->num_expected);
266 
267 	if (text)
268 		xfree(text);
269 
270 	for (i = 0, j = 0; i < num_msg; i++) {
271 		int style = PAM_MSG_MEMBER(msg, i, msg_style);
272 
273 		/* Skip messages which don't need a reply */
274 		if (style != PAM_PROMPT_ECHO_ON && style != PAM_PROMPT_ECHO_OFF)
275 			continue;
276 
277 		conv_ctxt->prompts[j++] = i;
278 		packet_put_cstring(PAM_MSG_MEMBER(msg, i, msg));
279 		packet_put_char(style == PAM_PROMPT_ECHO_ON);
280 	}
281 	packet_send();
282 	packet_write_wait();
283 
284 	/*
285 	 * Here the dispatch_run() loop is nested.  It should be unwound
286 	 * if keyboard-interactive userauth is abandoned (or restarted;
287 	 * same thing).
288 	 *
289 	 * The condition for breaking out of the nested dispatch_run() loop is
290 	 *     ((got kbd-int info reponse) || (kbd-int abandoned))
291 	 *
292 	 * conv_ctxt->finished is set in either of those cases.
293 	 *
294 	 * When abandonment is detected the conv_ctxt->finished is set as
295 	 * is conv_ctxt->abandoned, causing this function to signal
296 	 * userauth nested dispatch_run() loop unwinding and to return
297 	 * PAM_CONV_ERR;
298 	 */
299 	debug2("Nesting dispatch_run loop");
300 	dispatch_run(DISPATCH_BLOCK, &conv_ctxt->finished, appdata_ptr);
301 	debug2("Nested dispatch_run loop exited");
302 
303 	if (conv_ctxt->abandoned) {
304 		authctxt->unwind_dispatch_loop = 1;
305 		xfree(conv_ctxt->prompts);
306 		xfree(conv_ctxt->responses);
307 		xfree(conv_ctxt);
308 		debug("PAM conv function returns PAM_CONV_ERR");
309 		return PAM_CONV_ERR;
310 	}
311 
312 	if (conv_ctxt->num_received == conv_ctxt->num_expected) {
313 		*resp = conv_ctxt->responses;
314 		xfree(conv_ctxt->prompts);
315 		xfree(conv_ctxt);
316 		debug("PAM conv function returns PAM_SUCCESS");
317 		return PAM_SUCCESS;
318 	}
319 
320 	debug("PAM conv function returns PAM_CONV_ERR");
321 	xfree(conv_ctxt->prompts);
322 	xfree(conv_ctxt->responses);
323 	xfree(conv_ctxt);
324 	return PAM_CONV_ERR;
325 }
326 
327 static void
328 input_userauth_info_response_pam(int type, u_int32_t seqnr, void *ctxt)
329 {
330 	Authctxt *authctxt = ctxt;
331 	Convctxt *conv_ctxt;
332 	unsigned int nresp = 0, rlen = 0, i = 0;
333 	char *resp;
334 
335 	if (authctxt == NULL)
336 		fatal("input_userauth_info_response_pam: no authentication context");
337 
338 	/* Check for spurious/unexpected info response */
339 	if (method_kbdint.method_data == NULL) {
340 		debug("input_userauth_info_response_pam: no method context");
341 		return;
342 	}
343 
344 	conv_ctxt = (Convctxt *) method_kbdint.method_data;
345 
346 	nresp = packet_get_int();	/* Number of responses. */
347 	debug("got %d responses", nresp);
348 
349 
350 #if 0
351 	if (nresp != conv_ctxt->num_expected)
352 		fatal("%s: Received incorrect number of responses "
353 		    "(expected %d, received %u)", __func__,
354 		    conv_ctxt->num_expected, nresp);
355 #endif
356 
357 	if (nresp > 100)
358 		fatal("%s: too many replies", __func__);
359 
360 	for (i = 0; i < nresp && i < conv_ctxt->num_expected ; i++) {
361 		int j = conv_ctxt->prompts[i];
362 
363 		resp = packet_get_string(&rlen);
364 		if (i < conv_ctxt->num_expected) {
365 			conv_ctxt->responses[j].resp_retcode = PAM_SUCCESS;
366 			conv_ctxt->responses[j].resp = xstrdup(resp);
367 			conv_ctxt->num_received++;
368 		}
369 		xfree(resp);
370 	}
371 
372 	if (nresp < conv_ctxt->num_expected)
373 		fatal("%s: too few replies (%d < %d)", __func__,
374 		    nresp, conv_ctxt->num_expected);
375 
376 	/* XXX - This could make a covert channel... */
377 	if (nresp > conv_ctxt->num_expected)
378 		debug("Ignoring additional PAM replies");
379 
380 	conv_ctxt->finished = 1;
381 
382 	packet_check_eom();
383 }
384 
385 #if 0
386 int
387 kbdint_pam_abandon_chk(Authctxt *authctxt, Authmethod *method)
388 {
389 	if (!method)
390 		return 0; /* fatal(), really; it'll happen somewhere else */
391 
392 	if (!method->method_data)
393 		return 0;
394 
395 	return 1;
396 }
397 #endif
398 
399 void
400 kbdint_pam_abandon(Authctxt *authctxt, Authmethod *method)
401 {
402 	Convctxt *conv_ctxt;
403 
404 	/*
405 	 * But, if it ever becomes desirable and possible to support
406 	 * kbd-int userauth abandonment, here's what must be done.
407 	 */
408 	if (!method)
409 		return;
410 
411 	if (!method->method_data)
412 		return;
413 
414 	conv_ctxt = (Convctxt *) method->method_data;
415 
416 	/* dispatch_run() loop will exit */
417 	conv_ctxt->abandoned = 1;
418 	conv_ctxt->finished = 1;
419 
420 	/*
421 	 * The method_data will be free in the corresponding, active
422 	 * conversation function
423 	 */
424 	method->method_data = NULL;
425 
426 	/* update counts that can't be updated elsewhere */
427 	method->abandons++;
428 	method->attempts++;
429 
430 	/* Finally, we cannot re-use the current current PAM handle */
431 	authctxt->pam->h = NULL;    /* Let the conv function cleanup */
432 }
433 #endif
434