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