xref: /titanic_50/usr/src/cmd/ssh/sshd/auth-pam.c (revision 4bff34e37def8a90f9194d81bc345c52ba20086a)
1 /*
2  * Copyright (c) 2000 Damien Miller.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24 /*
25  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
26  * Use is subject to license terms.
27  */
28 
29 #include "includes.h"
30 
31 #ifdef USE_PAM
32 #include "xmalloc.h"
33 #include "log.h"
34 #include "auth.h"
35 #include "auth-options.h"
36 #include "auth-pam.h"
37 #include "servconf.h"
38 #include "canohost.h"
39 #include "compat.h"
40 #include "misc.h"
41 #include "sshlogin.h"
42 #include "ssh-gss.h"
43 
44 #include <security/pam_appl.h>
45 
46 extern char *__progname;
47 
48 extern u_int utmp_len;
49 extern ServerOptions options;
50 
51 extern Authmethod method_kbdint;
52 
53 RCSID("$Id: auth-pam.c,v 1.54 2002/07/28 20:24:08 stevesk Exp $");
54 
55 #pragma ident	"%Z%%M%	%I%	%E% SMI"
56 
57 #define NEW_AUTHTOK_MSG \
58 	"Warning: Your password has expired, please change it now."
59 
60 /* PAM conversation for non-interactive userauth methods */
61 static int do_pam_conversation(int num_msg, const struct pam_message **msg,
62 	struct pam_response **resp, void *appdata_ptr);
63 
64 static void do_pam_cleanup_proc(void *context);
65 
66 static char *get_method_name(Authctxt *authctxt);
67 
68 /* PAM conversation for non-interactive userauth methods */
69 static struct pam_conv conv = {
70 	(int (*)())do_pam_conversation,
71 	NULL
72 };
73 static char *__pam_msg = NULL;
74 
75 static
76 char *
77 get_method_name(Authctxt *authctxt)
78 {
79 	if (!authctxt)
80 		return "(unknown)";
81 
82 	if (!compat20)
83 		return (authctxt->v1_auth_name) ? authctxt->v1_auth_name :
84 						  "(sshv1-unknown)";
85 
86 	if (!authctxt->method || !authctxt->method->name)
87 			return "(sshv2-unknown)";
88 
89 	return authctxt->method->name;
90 }
91 
92 const
93 char *
94 derive_pam_svc_name(Authmethod *method)
95 {
96 	if (compat20 && method) {
97 		char *method_name = method->name;
98 
99 		if (!method_name)
100 			fatal("Userauth method unknown while starting PAM");
101 
102 		/* For SSHv2 we use "sshd-<userauth name> */
103 		if (strcmp(method_name, "none") == 0) {
104 			return "sshd-none";
105 		}
106 		if (strcmp(method_name, "password") == 0) {
107 			return "sshd-password";
108 		}
109 		if (strcmp(method_name, "keyboard-interactive") == 0) {
110 			/* "keyboard-interactive" is too long, shorten it */
111 			return "sshd-kbdint";
112 		}
113 		if (strcmp(method_name, "publickey") == 0) {
114 			/* "publickey" is too long, shorten it */
115 			return "sshd-pubkey";
116 		}
117 		if (strcmp(method_name, "hostbased") == 0) {
118 			/* "hostbased" can't really be shortened... */
119 			return "sshd-hostbased";
120 		}
121 		if (strncmp(method_name, "gss", 3) == 0) {
122 			/* "gss" is too short, elongate it */
123 			return "sshd-gssapi";
124 		}
125 	}
126 
127 	return "sshd-v1"; /* SSHv1 doesn't get to be so cool */
128 }
129 
130 void
131 new_start_pam(Authctxt *authctxt, struct pam_conv *conv)
132 {
133 	int		retval;
134 	pam_handle_t	*pamh;
135 	const char	*rhost, *svc;
136 	char		*user = NULL;
137 	pam_stuff	*pam;
138 
139 	if (authctxt == NULL)
140 		fatal("Internal error during userauth");
141 
142 	if (compat20 && authctxt->method == NULL)
143 		fatal("Userauth method unknown while starting PAM");
144 
145 	/* PAM service selected here */
146 	svc = derive_pam_svc_name(authctxt->method);
147 	debug2("Starting PAM service %s for method %s", svc,
148 		get_method_name(authctxt));
149 
150 	if (authctxt->user != NULL)
151 		user = authctxt->user;
152 
153 	/* Cleanup previous PAM state */
154 	if (authctxt->pam != NULL) {
155 		fatal_remove_cleanup(&do_pam_cleanup_proc, authctxt->pam);
156 		do_pam_cleanup_proc(authctxt->pam);
157 	}
158 
159 	pam = xmalloc(sizeof(pam_stuff));
160 	(void) memset(pam, 0, sizeof(pam_stuff));
161 
162 	/*
163 	 * pam->last_pam_retval has to be and is considered
164 	 * along with pam->state.
165 	 *
166 	 * pam->state = 0; -> no PAM auth, account, etc, work
167 	 * done yet.  (Set by memset() above.)
168 	 *
169 	 * pam->last_pam_retval = PAM_SUCCESS; -> meaningless at
170 	 * this point.
171 	 *
172 	 * See finish_userauth_do_pam() below.
173 	 */
174 	pam->authctxt = authctxt;
175 	pam->last_pam_retval = PAM_SUCCESS;
176 
177 	authctxt->pam = pam;
178 
179 	/* Free any previously stored text/error PAM prompts */
180 	if (__pam_msg) {
181 		xfree(__pam_msg);
182 		__pam_msg = NULL;
183 	}
184 
185 	if ((retval = pam_start(svc, user, conv, &pamh)) != PAM_SUCCESS) {
186 		fatal("PAM initialization failed during %s userauth",
187 			get_method_name(authctxt));
188 	}
189 
190 	fatal_add_cleanup((void (*)(void *)) &do_pam_cleanup_proc,
191 			  (void *) authctxt->pam);
192 
193 	rhost = get_remote_name_or_ip(utmp_len, options.verify_reverse_mapping);
194 	if ((retval = pam_set_item(pamh, PAM_RHOST, rhost)) != PAM_SUCCESS) {
195 		(void) pam_end(pamh, retval);
196 		fatal("Could not set PAM_RHOST item during %s userauth",
197 			get_method_name(authctxt));
198 	}
199 
200 	if ((retval = pam_set_item(pamh, PAM_TTY, "sshd")) != PAM_SUCCESS) {
201 		(void) pam_end(pamh, retval);
202 		fatal("Could not set PAM_TTY item during %s userauth",
203 			get_method_name(authctxt));
204 	}
205 
206 	if (authctxt->cuser != NULL)
207 		if ((retval = pam_set_item(pamh, PAM_AUSER, authctxt->cuser)) != PAM_SUCCESS) {
208 			(void) pam_end(pamh, retval);
209 			fatal("Could not set PAM_AUSER item during %s userauth",
210 				get_method_name(authctxt));
211 		}
212 
213 	authctxt->pam->h = pamh;
214 }
215 
216 /*
217  * To be called from userauth methods, directly (as in keyboard-interactive) or
218  * indirectly (from auth_pam_password() or from do_pam_non_initial_userauth().
219  *
220  * The caller is responsible for calling new_start_pam() first.
221  *
222  * PAM state is not cleaned up here on error.  This is left to subsequent calls
223  * to new_start_pam() or to the cleanup function upon authentication error.
224  */
225 int
226 finish_userauth_do_pam(Authctxt *authctxt)
227 {
228 	int retval;
229 	char *user, *method;
230 
231 	/* Various checks; fail gracefully */
232 	if (authctxt == NULL || authctxt->pam == NULL)
233 		return PAM_SYSTEM_ERR;	/* shouldn't happen */
234 
235 	if (compat20) {
236 		if (authctxt->method == NULL || authctxt->method->name == NULL)
237 			return PAM_SYSTEM_ERR;	/* shouldn't happen */
238 		method = authctxt->method->name;
239 	} else if ((method = authctxt->v1_auth_name) == NULL)
240 		return PAM_SYSTEM_ERR;	/* shouldn't happen */
241 
242 	if (AUTHPAM_DONE(authctxt))
243 		return PAM_SYSTEM_ERR;	/* shouldn't happen */
244 
245 	if (!(authctxt->pam->state & PAM_S_DONE_ACCT_MGMT)) {
246 		retval = pam_acct_mgmt(authctxt->pam->h, 0);
247 		authctxt->pam->last_pam_retval = retval;
248 		if (retval == PAM_NEW_AUTHTOK_REQD) {
249 			userauth_force_kbdint();
250 			return retval;
251 		}
252 		if (retval != PAM_SUCCESS)
253 			return retval;
254 		authctxt->pam->state |= PAM_S_DONE_ACCT_MGMT;
255 	}
256 
257 	/*
258 	 * Handle PAM_USER change, if any.
259 	 *
260 	 * We do this before pam_open_session() because we need the PAM_USER's
261 	 * UID for:
262 	 *
263 	 * a) PermitRootLogin checking
264 	 * b) to get at the lastlog entry before pam_open_session() updates it.
265 	 */
266 	retval = pam_get_item(authctxt->pam->h, PAM_USER, (void **) &user);
267 	if (retval != PAM_SUCCESS) {
268 		fatal("PAM failure: pam_get_item(PAM_USER) "
269 		      "returned %d: %.200s", retval,
270 		      PAM_STRERROR(authctxt->pam->h, retval));
271 	}
272 
273 	if (user == NULL || *user == '\0') {
274 		debug("PAM set NULL PAM_USER");
275 		return PAM_PERM_DENIED;
276 	}
277 
278 	if (strcmp(user, authctxt->user) != 0) {
279 		log("PAM changed the SSH username");
280 		pwfree(&authctxt->pw);
281 		authctxt->pw = getpwnamallow(user);
282 		authctxt->valid = (authctxt->pw != NULL);
283 		xfree(authctxt->user);
284 		authctxt->user = xstrdup(user);
285 	}
286 
287 	if (!authctxt->valid) {
288 		debug2("PAM set PAM_USER to unknown user");
289 		/*
290 		 * Return success, userauth_finish() will catch
291 		 * this and send back a failure message.
292 		 */
293 		return PAM_SUCCESS;
294 	}
295 
296 	/* Check PermitRootLogin semantics */
297 	if (authctxt->pw->pw_uid == 0 && !auth_root_allowed(method))
298 		return PAM_PERM_DENIED;
299 
300 	if (!(authctxt->pam->state & PAM_S_DONE_SETCRED)) {
301 		retval = pam_setcred(authctxt->pam->h,
302 				     PAM_ESTABLISH_CRED);
303 		authctxt->pam->last_pam_retval = retval;
304 		if (retval != PAM_SUCCESS)
305 			return retval;
306 		authctxt->pam->state |= PAM_S_DONE_SETCRED;
307 
308 #ifdef GSSAPI
309 		/*
310 		 * Store GSS-API delegated creds after pam_setcred(), which may
311 		 * have set the current credential store.
312 		 */
313 		ssh_gssapi_storecreds(NULL, authctxt);
314 #endif /* GSSAPI */
315 	}
316 
317 	/*
318 	 * On Solaris pam_unix_session.so updates the lastlog, but does
319 	 * not converse a PAM_TEXT_INFO message about it.  So we need to
320 	 * fetch the lastlog entry here and save it for use later.
321 	 */
322 	authctxt->last_login_time =
323 		get_last_login_time(authctxt->pw->pw_uid,
324 			authctxt->pw->pw_name,
325 			authctxt->last_login_host,
326 			sizeof(authctxt->last_login_host));
327 
328 	if (!(authctxt->pam->state & PAM_S_DONE_OPEN_SESSION)) {
329 		retval = pam_open_session(authctxt->pam->h, 0);
330 		authctxt->pam->last_pam_retval = retval;
331 		if (retval != PAM_SUCCESS)
332 			return retval;
333 		authctxt->pam->state |= PAM_S_DONE_OPEN_SESSION;
334 	}
335 
336 	/*
337 	 * All PAM work done successfully.
338 	 *
339 	 * PAM handle stays around so we can call pam_close_session() on
340 	 * it later.
341 	 */
342 	return PAM_SUCCESS;
343 }
344 
345 /*
346  * PAM conversation function for non-interactive userauth methods that
347  * really cannot do any prompting.  Password userauth and CHANGEREQ can
348  * always set the PAM_AUTHTOK and PAM_OLDAUTHTOK items to avoid
349  * conversation (and if they do and nonetheless some module tries to
350  * converse, then password userauth / CHANGEREQ MUST fail).
351  *
352  * Except, PAM_TEXT_INFO and PAM_ERROR_MSG prompts can be squirelled
353  * away and shown to the user later.
354  *
355  * Keyboard-interactive userauth has its own much more interesting
356  * conversation function.
357  *
358  */
359 static int
360 do_pam_conversation(int num_msg, const struct pam_message **msg,
361 	struct pam_response **resp, void *appdata_ptr)
362 {
363 	struct pam_response *reply;
364 	int count;
365 
366 	/* PAM will free this later */
367 	reply = xmalloc(num_msg * sizeof(*reply));
368 
369 	(void) memset(reply, 0, num_msg * sizeof(*reply));
370 
371 	for (count = 0; count < num_msg; count++) {
372 		/*
373 		 * We can't use stdio yet, queue messages for
374 		 * printing later
375 		 */
376 		switch(PAM_MSG_MEMBER(msg, count, msg_style)) {
377 		case PAM_PROMPT_ECHO_ON:
378 			xfree(reply);
379 			return PAM_CONV_ERR;
380 		case PAM_PROMPT_ECHO_OFF:
381 			xfree(reply);
382 			return PAM_CONV_ERR;
383 			break;
384 		case PAM_ERROR_MSG:
385 		case PAM_TEXT_INFO:
386 			if (PAM_MSG_MEMBER(msg, count, msg) != NULL) {
387 				message_cat(&__pam_msg,
388 				    PAM_MSG_MEMBER(msg, count, msg));
389 			}
390 			reply[count].resp = xstrdup("");
391 			reply[count].resp_retcode = PAM_SUCCESS;
392 			break;
393 		default:
394 			xfree(reply);
395 			return PAM_CONV_ERR;
396 		}
397 	}
398 
399 	*resp = reply;
400 
401 	return PAM_SUCCESS;
402 }
403 
404 /* Called at exit to cleanly shutdown PAM */
405 static void
406 do_pam_cleanup_proc(void *context)
407 {
408 	int pam_retval;
409 	pam_stuff *pam = (pam_stuff *) context;
410 
411 	if (pam == NULL)
412 		return;
413 
414 	if (pam->authctxt != NULL && pam->authctxt->pam == pam) {
415 		pam->authctxt->pam_retval = pam->last_pam_retval;
416 		pam->authctxt->pam = NULL;
417 		pam->authctxt = NULL;
418 	}
419 
420 	if (pam->h == NULL)
421 		return;
422 
423 	/*
424 	 * We're in fatal_cleanup() or not in userauth or without a
425 	 * channel -- can't converse now, too bad.
426 	 */
427 	pam_retval = pam_set_item(pam->h, PAM_CONV, NULL);
428 	if (pam_retval != PAM_SUCCESS) {
429 		log("Cannot remove PAM conv, close session or delete creds[%d]: %.200s",
430 			pam_retval, PAM_STRERROR(pam->h, pam_retval));
431 		goto cleanup;
432 	}
433 
434 	if (pam->state & PAM_S_DONE_OPEN_SESSION) {
435 		pam_retval = pam_close_session(pam->h, 0);
436 		if (pam_retval != PAM_SUCCESS)
437 			log("Cannot close PAM session[%d]: %.200s",
438 			    pam_retval, PAM_STRERROR(pam->h, pam_retval));
439 	}
440 
441 	if (pam->state & PAM_S_DONE_SETCRED) {
442 		pam_retval = pam_setcred(pam->h, PAM_DELETE_CRED);
443 		if (pam_retval != PAM_SUCCESS)
444 			debug("Cannot delete credentials[%d]: %.200s",
445 			    pam_retval, PAM_STRERROR(pam->h, pam_retval));
446 	}
447 
448 cleanup:
449 
450 	/* Use the previous PAM result, if not PAM_SUCCESS for pam_end() */
451 	if (pam->last_pam_retval != PAM_SUCCESS)
452 		pam_retval = pam_end(pam->h, pam->last_pam_retval);
453 	else if (pam_retval != PAM_SUCCESS)
454 		pam_retval = pam_end(pam->h, pam_retval);
455 	else
456 		pam_retval = pam_end(pam->h, PAM_ABORT);
457 
458 	if (pam_retval != PAM_SUCCESS)
459 		log("Cannot release PAM authentication[%d]: %.200s",
460 		    pam_retval, PAM_STRERROR(pam->h, pam_retval));
461 
462 	xfree(pam);
463 }
464 
465 /* Attempt password authentation using PAM */
466 int
467 auth_pam_password(Authctxt *authctxt, const char *password)
468 {
469 	int retval;
470 
471 	/* Ensure we have a fresh PAM handle / state */
472 	new_start_pam(authctxt, &conv);
473 
474 	retval = pam_set_item(authctxt->pam->h, PAM_AUTHTOK, password);
475 	if (retval != PAM_SUCCESS)
476 		return 1;
477 
478 	retval = pam_authenticate(authctxt->pam->h,
479 			options.permit_empty_passwd ?  0 :
480 			PAM_DISALLOW_NULL_AUTHTOK);
481 
482 	if (retval != PAM_SUCCESS)
483 		return 0;
484 
485 	if ((retval = finish_userauth_do_pam(authctxt)) != PAM_SUCCESS)
486 		return 0;
487 
488 	if (authctxt->method)
489 		authctxt->method->authenticated = 1;	/* SSHv2 */
490 
491 	return 1;
492 }
493 
494 int
495 do_pam_non_initial_userauth(Authctxt *authctxt)
496 {
497 	new_start_pam(authctxt, NULL);
498 	return (finish_userauth_do_pam(authctxt) == PAM_SUCCESS);
499 }
500 
501 /* Cleanly shutdown PAM */
502 void finish_pam(Authctxt *authctxt)
503 {
504 	fatal_remove_cleanup(&do_pam_cleanup_proc, authctxt->pam);
505 	do_pam_cleanup_proc(authctxt->pam);
506 }
507 
508 static
509 char **
510 find_env(char **env, char *var)
511 {
512 	char **p;
513 	int len;
514 
515 	if (strchr(var, '=') == NULL)
516 		len = strlen(var);
517 	else
518 		len = (strchr(var, '=') - var) + 1;
519 
520 	for ( p = env ; p != NULL && *p != NULL ; p++ ) {
521 		if (strncmp(*p, var, len) == 0)
522 			return (p);
523 	}
524 
525 	return (NULL);
526 }
527 
528 /* Return list of PAM environment strings */
529 char **
530 fetch_pam_environment(Authctxt *authctxt)
531 {
532 #ifdef HAVE_PAM_GETENVLIST
533 	char	**penv;
534 
535 	if (authctxt == NULL || authctxt->pam == NULL ||
536 	    authctxt->pam->h == NULL)
537 		return (NULL);
538 
539 	penv = pam_getenvlist(authctxt->pam->h);
540 
541 	return (penv);
542 #else /* HAVE_PAM_GETENVLIST */
543 	return(NULL);
544 #endif /* HAVE_PAM_GETENVLIST */
545 }
546 
547 void free_pam_environment(char **env)
548 {
549 	int i;
550 
551 	if (env != NULL) {
552 		for (i = 0; env[i] != NULL; i++)
553 			xfree(env[i]);
554 	}
555 
556 	xfree(env);
557 }
558 
559 /* Print any messages that have been generated during authentication */
560 /* or account checking to stderr */
561 void print_pam_messages(void)
562 {
563 	if (__pam_msg != NULL)
564 		(void) fputs(__pam_msg, stderr);
565 }
566 
567 /* Append a message to buffer */
568 void message_cat(char **p, const char *a)
569 {
570 	char *cp;
571 	size_t new_len;
572 
573 	new_len = strlen(a);
574 
575 	if (*p) {
576 		size_t len = strlen(*p);
577 
578 		*p = xrealloc(*p, new_len + len + 2);
579 		cp = *p + len;
580 	} else
581 		*p = cp = xmalloc(new_len + 2);
582 
583 	(void) memcpy(cp, a, new_len);
584 	cp[new_len] = '\n';
585 	cp[new_len + 1] = '\0';
586 }
587 
588 #endif /* USE_PAM */
589