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