xref: /titanic_50/usr/src/cmd/ssh/sshd/auth-pam.c (revision 696be233fd50b992c5f28974cd022f078f832272)
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 	if (!(authctxt->pam->state & PAM_S_DONE_OPEN_SESSION)) {
343 		retval = pam_open_session(authctxt->pam->h, 0);
344 		authctxt->pam->last_pam_retval = retval;
345 		if (retval != PAM_SUCCESS)
346 			return retval;
347 		authctxt->pam->state |= PAM_S_DONE_OPEN_SESSION;
348 	}
349 
350 	/*
351 	 * All PAM work done successfully.
352 	 *
353 	 * PAM handle stays around so we can call pam_close_session() on
354 	 * it later.
355 	 */
356 	return PAM_SUCCESS;
357 }
358 
359 /*
360  * PAM conversation function for non-interactive userauth methods that
361  * really cannot do any prompting.  Password userauth and CHANGEREQ can
362  * always set the PAM_AUTHTOK and PAM_OLDAUTHTOK items to avoid
363  * conversation (and if they do and nonetheless some module tries to
364  * converse, then password userauth / CHANGEREQ MUST fail).
365  *
366  * Except, PAM_TEXT_INFO and PAM_ERROR_MSG prompts can be squirelled
367  * away and shown to the user later.
368  *
369  * Keyboard-interactive userauth has its own much more interesting
370  * conversation function.
371  *
372  */
373 static int
374 do_pam_conversation(int num_msg, const struct pam_message **msg,
375 	struct pam_response **resp, void *appdata_ptr)
376 {
377 	struct pam_response *reply;
378 	int count;
379 
380 	/* PAM will free this later */
381 	reply = xmalloc(num_msg * sizeof(*reply));
382 
383 	(void) memset(reply, 0, num_msg * sizeof(*reply));
384 
385 	for (count = 0; count < num_msg; count++) {
386 		/*
387 		 * We can't use stdio yet, queue messages for
388 		 * printing later
389 		 */
390 		switch(PAM_MSG_MEMBER(msg, count, msg_style)) {
391 		case PAM_PROMPT_ECHO_ON:
392 			xfree(reply);
393 			return PAM_CONV_ERR;
394 		case PAM_PROMPT_ECHO_OFF:
395 			xfree(reply);
396 			return PAM_CONV_ERR;
397 			break;
398 		case PAM_ERROR_MSG:
399 		case PAM_TEXT_INFO:
400 			if (PAM_MSG_MEMBER(msg, count, msg) != NULL) {
401 				message_cat(&__pam_msg,
402 				    PAM_MSG_MEMBER(msg, count, msg));
403 			}
404 			reply[count].resp = xstrdup("");
405 			reply[count].resp_retcode = PAM_SUCCESS;
406 			break;
407 		default:
408 			xfree(reply);
409 			return PAM_CONV_ERR;
410 		}
411 	}
412 
413 	*resp = reply;
414 
415 	return PAM_SUCCESS;
416 }
417 
418 /* Called at exit to cleanly shutdown PAM */
419 static void
420 do_pam_cleanup_proc(void *context)
421 {
422 	int pam_retval;
423 	pam_stuff *pam = (pam_stuff *) context;
424 
425 	if (pam == NULL)
426 		return;
427 
428 	if (pam->authctxt != NULL && pam->authctxt->pam == pam) {
429 		pam->authctxt->pam_retval = pam->last_pam_retval;
430 		pam->authctxt->pam = NULL;
431 		pam->authctxt = NULL;
432 	}
433 
434 	if (pam->h == NULL)
435 		return;
436 
437 	/*
438 	 * We're in fatal_cleanup() or not in userauth or without a
439 	 * channel -- can't converse now, too bad.
440 	 */
441 	pam_retval = pam_set_item(pam->h, PAM_CONV, NULL);
442 	if (pam_retval != PAM_SUCCESS) {
443 		log("Cannot remove PAM conv, close session or delete creds[%d]: %.200s",
444 			pam_retval, PAM_STRERROR(pam->h, pam_retval));
445 		goto cleanup;
446 	}
447 
448 	if (pam->state & PAM_S_DONE_OPEN_SESSION) {
449 		pam_retval = pam_close_session(pam->h, 0);
450 		if (pam_retval != PAM_SUCCESS)
451 			log("Cannot close PAM session[%d]: %.200s",
452 			    pam_retval, PAM_STRERROR(pam->h, pam_retval));
453 	}
454 
455 	if (pam->state & PAM_S_DONE_SETCRED) {
456 		pam_retval = pam_setcred(pam->h, PAM_DELETE_CRED);
457 		if (pam_retval != PAM_SUCCESS)
458 			debug("Cannot delete credentials[%d]: %.200s",
459 			    pam_retval, PAM_STRERROR(pam->h, pam_retval));
460 	}
461 
462 cleanup:
463 
464 	/* Use the previous PAM result, if not PAM_SUCCESS for pam_end() */
465 	if (pam->last_pam_retval != PAM_SUCCESS)
466 		pam_retval = pam_end(pam->h, pam->last_pam_retval);
467 	else if (pam_retval != PAM_SUCCESS)
468 		pam_retval = pam_end(pam->h, pam_retval);
469 	else
470 		pam_retval = pam_end(pam->h, PAM_ABORT);
471 
472 	if (pam_retval != PAM_SUCCESS)
473 		log("Cannot release PAM authentication[%d]: %.200s",
474 		    pam_retval, PAM_STRERROR(pam->h, pam_retval));
475 
476 	xfree(pam);
477 }
478 
479 /* Attempt password authentation using PAM */
480 int
481 auth_pam_password(Authctxt *authctxt, const char *password)
482 {
483 	int retval;
484 
485 	/* Ensure we have a fresh PAM handle / state */
486 	new_start_pam(authctxt, &conv);
487 
488 	retval = pam_set_item(authctxt->pam->h, PAM_AUTHTOK, password);
489 	if (retval != PAM_SUCCESS) {
490 		authctxt->pam->last_pam_retval = retval;
491 		return 1;
492 	}
493 
494 	retval = pam_authenticate(authctxt->pam->h,
495 			options.permit_empty_passwd ?  0 :
496 			PAM_DISALLOW_NULL_AUTHTOK);
497 
498 	if (retval != PAM_SUCCESS) {
499 		authctxt->pam->last_pam_retval = retval;
500 		return 0;
501 	}
502 
503 	if ((retval = finish_userauth_do_pam(authctxt)) != PAM_SUCCESS)
504 		return 0;
505 
506 	if (authctxt->method)
507 		authctxt->method->authenticated = 1;	/* SSHv2 */
508 
509 	return 1;
510 }
511 
512 int
513 do_pam_non_initial_userauth(Authctxt *authctxt)
514 {
515 	new_start_pam(authctxt, &conv);
516 	return (finish_userauth_do_pam(authctxt) == PAM_SUCCESS);
517 }
518 
519 /* Cleanly shutdown PAM */
520 void finish_pam(Authctxt *authctxt)
521 {
522 	fatal_remove_cleanup(&do_pam_cleanup_proc, authctxt->pam);
523 	do_pam_cleanup_proc(authctxt->pam);
524 }
525 
526 static
527 char **
528 find_env(char **env, char *var)
529 {
530 	char **p;
531 	int len;
532 
533 	if (strchr(var, '=') == NULL)
534 		len = strlen(var);
535 	else
536 		len = (strchr(var, '=') - var) + 1;
537 
538 	for ( p = env ; p != NULL && *p != NULL ; p++ ) {
539 		if (strncmp(*p, var, len) == 0)
540 			return (p);
541 	}
542 
543 	return (NULL);
544 }
545 
546 /* Return list of PAM environment strings */
547 char **
548 fetch_pam_environment(Authctxt *authctxt)
549 {
550 #ifdef HAVE_PAM_GETENVLIST
551 	char	**penv;
552 
553 	if (authctxt == NULL || authctxt->pam == NULL ||
554 	    authctxt->pam->h == NULL)
555 		return (NULL);
556 
557 	penv = pam_getenvlist(authctxt->pam->h);
558 
559 	return (penv);
560 #else /* HAVE_PAM_GETENVLIST */
561 	return(NULL);
562 #endif /* HAVE_PAM_GETENVLIST */
563 }
564 
565 void free_pam_environment(char **env)
566 {
567 	int i;
568 
569 	if (env != NULL) {
570 		for (i = 0; env[i] != NULL; i++)
571 			xfree(env[i]);
572 	}
573 
574 	xfree(env);
575 }
576 
577 /* Print any messages that have been generated during authentication */
578 /* or account checking to stderr */
579 void print_pam_messages(void)
580 {
581 	if (__pam_msg != NULL)
582 		(void) fputs(__pam_msg, stderr);
583 }
584 
585 /* Append a message to buffer */
586 void message_cat(char **p, const char *a)
587 {
588 	char *cp;
589 	size_t new_len;
590 
591 	new_len = strlen(a);
592 
593 	if (*p) {
594 		size_t len = strlen(*p);
595 
596 		*p = xrealloc(*p, new_len + len + 2);
597 		cp = *p + len;
598 	} else
599 		*p = cp = xmalloc(new_len + 2);
600 
601 	(void) memcpy(cp, a, new_len);
602 	cp[new_len] = '\n';
603 	cp[new_len + 1] = '\0';
604 }
605 
606 #endif /* USE_PAM */
607