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