1 /*
2  * SPDX-License-Identifier: ISC
3  *
4  * Copyright (c) 1999-2005, 2007-2020 Todd C. Miller <Todd.Miller@sudo.ws>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  *
18  * Sponsored in part by the Defense Advanced Research Projects
19  * Agency (DARPA) and Air Force Research Laboratory, Air Force
20  * Materiel Command, USAF, under agreement number F39502-99-1-0512.
21  */
22 
23 /*
24  * This is an open source non-commercial project. Dear PVS-Studio, please check it.
25  * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
26  */
27 
28 #include <config.h>
29 
30 #ifdef HAVE_PAM
31 
32 #include <sys/types.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <pwd.h>
38 #include <errno.h>
39 
40 #ifdef HAVE_PAM_PAM_APPL_H
41 # include <pam/pam_appl.h>
42 #else
43 # include <security/pam_appl.h>
44 #endif
45 
46 #ifdef __hpux
47 # include <nl_types.h>
48 #endif
49 
50 #ifdef HAVE_LIBINTL_H
51 # if defined(__LINUX_PAM__)
52 #  define PAM_TEXT_DOMAIN	"Linux-PAM"
53 # elif defined(__sun__)
54 #  define PAM_TEXT_DOMAIN	"SUNW_OST_SYSOSPAM"
55 # endif
56 #endif
57 
58 /* We don't want to translate the strings in the calls to dgt(). */
59 #ifdef PAM_TEXT_DOMAIN
60 # define dgt(d, t)	dgettext(d, t)
61 #endif
62 
63 #include "sudoers.h"
64 #include "sudo_auth.h"
65 
66 /* Only OpenPAM and Linux PAM use const qualifiers. */
67 #ifdef PAM_SUN_CODEBASE
68 # define PAM_CONST
69 #else
70 # define PAM_CONST	const
71 #endif
72 
73 /* Ambiguity in spec: is it an array of pointers or a pointer to an array? */
74 #ifdef PAM_SUN_CODEBASE
75 # define PAM_MSG_GET(msg, n) (*(msg) + (n))
76 #else
77 # define PAM_MSG_GET(msg, n) ((msg)[(n)])
78 #endif
79 
80 #ifndef PAM_DATA_SILENT
81 #define PAM_DATA_SILENT	0
82 #endif
83 
84 struct conv_filter {
85     char *msg;
86     size_t msglen;
87 };
88 
89 static int converse(int, PAM_CONST struct pam_message **,
90 		    struct pam_response **, void *);
91 static struct sudo_conv_callback *conv_callback;
92 static struct pam_conv pam_conv = { converse, &conv_callback };
93 static char *def_prompt = PASSPROMPT;
94 static bool getpass_error;
95 static pam_handle_t *pamh;
96 static struct conv_filter *conv_filter;
97 
98 static void
conv_filter_init(void)99 conv_filter_init(void)
100 {
101     debug_decl(conv_filter_init, SUDOERS_DEBUG_AUTH);
102 
103 #ifdef __hpux
104     /*
105      * HP-UX displays last login information as part of either account
106      * management (in trusted mode) or session management (regular mode).
107      * Filter those out in the conversation function unless running a shell.
108      */
109     if (!ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL)) {
110 	int i, nfilt = 0, maxfilters = 0;
111 	struct conv_filter *newfilt;
112 	nl_catd catd;
113 	char *msg;
114 
115 	/*
116 	 * Messages from PAM account management when trusted mode is enabled:
117 	 *  1 Last   successful login for %s: %s
118 	 *  2 Last   successful login for %s: %s on %s
119 	 *  3 Last unsuccessful login for %s: %s
120 	 *  4 Last unsuccessful login for %s: %s on %s
121 	 */
122 	if ((catd = catopen("pam_comsec", NL_CAT_LOCALE)) != -1) {
123 	    maxfilters += 4;
124 	    newfilt = reallocarray(conv_filter, maxfilters + 1,
125 		sizeof(*conv_filter));
126 	    if (newfilt != NULL) {
127 		conv_filter = newfilt;
128 		for (i = 1; i < 5; i++) {
129 		    if ((msg = catgets(catd, 1, i, NULL)) == NULL)
130 			break;
131 		    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
132 			"adding \"%s\" to conversation filter", msg);
133 		    if ((conv_filter[nfilt].msg = strdup(msg)) == NULL)
134 			break;
135 		    conv_filter[nfilt].msglen = strcspn(msg, "%");
136 		    nfilt++;
137 		}
138 	    }
139 	}
140 	/*
141 	 * Messages from PAM session management when trusted mode is disabled:
142 	 *  3 Last successful login:       %s %s %s %s
143 	 *  4 Last authentication failure: %s %s %s %s
144 	 */
145 	if ((catd = catopen("pam_hpsec", NL_CAT_LOCALE)) != -1) {
146 	    maxfilters += 2;
147 	    newfilt = reallocarray(conv_filter, maxfilters + 1,
148 		sizeof(*conv_filter));
149 	    if (newfilt != NULL) {
150 		conv_filter = newfilt;
151 		for (i = 3; i < 5; i++) {
152 		    if ((msg = catgets(catd, 1, i, NULL)) == NULL)
153 			break;
154 		    sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
155 			"adding \"%s\" to conversation filter", msg);
156 		    if ((conv_filter[nfilt].msg = strdup(msg)) == NULL)
157 			break;
158 		    conv_filter[nfilt].msglen = strcspn(msg, "%");
159 		    nfilt++;
160 		}
161 	    }
162 	}
163 	if (conv_filter != NULL) {
164 	    conv_filter[nfilt].msg = NULL;
165 	    conv_filter[nfilt].msglen = 0;
166 	}
167     }
168 #endif /* __hpux */
169     debug_return;
170 }
171 
172 /*
173  * Like pam_strerror() but never returns NULL and uses strerror(errno)
174  * for PAM_SYSTEM_ERR.
175  */
176 static const char *
sudo_pam_strerror(pam_handle_t * handle,int errnum)177 sudo_pam_strerror(pam_handle_t *handle, int errnum)
178 {
179     const char *errstr;
180     static char errbuf[32];
181 
182     if (errnum == PAM_SYSTEM_ERR)
183 	return strerror(errno);
184     if ((errstr = pam_strerror(handle, errnum)) == NULL)
185 	(void)snprintf(errbuf, sizeof(errbuf), "PAM error %d", errnum);
186     return errstr;
187 }
188 
189 static int
sudo_pam_init2(struct passwd * pw,sudo_auth * auth,bool quiet)190 sudo_pam_init2(struct passwd *pw, sudo_auth *auth, bool quiet)
191 {
192     static int pam_status = PAM_SUCCESS;
193     const char *ttypath = user_ttypath;
194     const char *errstr, *pam_service;
195     int rc;
196     debug_decl(sudo_pam_init, SUDOERS_DEBUG_AUTH);
197 
198     /* Stash pointer to last pam status. */
199     auth->data = &pam_status;
200 
201     if (pamh != NULL) {
202 	/* Already initialized (may happen with AIX or with sub-commands). */
203 	debug_return_int(AUTH_SUCCESS);
204     }
205 
206     /* Initial PAM. */
207     pam_service = ISSET(sudo_mode, MODE_LOGIN_SHELL) ?
208 	def_pam_login_service : def_pam_service;
209     pam_status = pam_start(pam_service, pw->pw_name, &pam_conv, &pamh);
210     if (pam_status != PAM_SUCCESS) {
211 	errstr = sudo_pam_strerror(NULL, pam_status);
212 	sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
213 	    "pam_start(%s, %s, %p, %p): %s", pam_service, pw->pw_name,
214 	    &pam_conv, &pamh, errstr);
215 	if (!quiet)
216 	    log_warningx(0, N_("unable to initialize PAM: %s"), errstr);
217 	debug_return_int(AUTH_FATAL);
218     }
219 
220     /* Initialize conversation function message filter. */
221     conv_filter_init();
222 
223     /*
224      * Set PAM_RUSER to the invoking user (the "from" user).
225      * Solaris 7 and below require PAM_RHOST to be set if PAM_RUSER is.
226      * Note: PAM_RHOST may cause a DNS lookup on Linux in libaudit.
227      */
228     if (def_pam_ruser) {
229 	rc = pam_set_item(pamh, PAM_RUSER, user_name);
230 	if (rc != PAM_SUCCESS) {
231 	    errstr = sudo_pam_strerror(pamh, rc);
232 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
233 		"pam_set_item(pamh, PAM_RUSER, %s): %s", user_name, errstr);
234 	}
235     }
236     if (def_pam_rhost) {
237 	rc = pam_set_item(pamh, PAM_RHOST, user_host);
238 	if (rc != PAM_SUCCESS) {
239 	    errstr = sudo_pam_strerror(pamh, rc);
240 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
241 		"pam_set_item(pamh, PAM_RHOST, %s): %s", user_host, errstr);
242 	}
243     }
244 
245 #if defined(__LINUX_PAM__) || defined(__sun__)
246     /*
247      * Some PAM modules assume PAM_TTY is set and will misbehave (or crash)
248      * if it is not.  Known offenders include pam_lastlog and pam_time.
249      */
250     if (ttypath == NULL)
251 	ttypath = "";
252 #endif
253     if (ttypath != NULL) { // -V547
254 	rc = pam_set_item(pamh, PAM_TTY, ttypath);
255 	if (rc != PAM_SUCCESS) {
256 	    errstr = sudo_pam_strerror(pamh, rc);
257 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
258 		"pam_set_item(pamh, PAM_TTY, %s): %s", ttypath, errstr);
259 	}
260     }
261 
262     /*
263      * If PAM session and setcred support is disabled we don't
264      * need to keep a sudo process around to close the session.
265      */
266     if (!def_pam_session && !def_pam_setcred)
267 	auth->end_session = NULL;
268 
269     debug_return_int(AUTH_SUCCESS);
270 }
271 
272 int
sudo_pam_init(struct passwd * pw,sudo_auth * auth)273 sudo_pam_init(struct passwd *pw, sudo_auth *auth)
274 {
275     return sudo_pam_init2(pw, auth, false);
276 }
277 
278 #ifdef _AIX
279 int
sudo_pam_init_quiet(struct passwd * pw,sudo_auth * auth)280 sudo_pam_init_quiet(struct passwd *pw, sudo_auth *auth)
281 {
282     return sudo_pam_init2(pw, auth, true);
283 }
284 #endif /* _AIX */
285 
286 int
sudo_pam_verify(struct passwd * pw,char * prompt,sudo_auth * auth,struct sudo_conv_callback * callback)287 sudo_pam_verify(struct passwd *pw, char *prompt, sudo_auth *auth, struct sudo_conv_callback *callback)
288 {
289 	const char *envccname;
290     const char *s;
291     int *pam_status = (int *) auth->data;
292     debug_decl(sudo_pam_verify, SUDOERS_DEBUG_AUTH);
293 
294     def_prompt = prompt;	/* for converse */
295     getpass_error = false;	/* set by converse if user presses ^C */
296     conv_callback = callback;	/* passed to conversation function */
297 
298 	/* Set KRB5CCNAME from the user environment if not set to propagate this
299 	 * information to PAM modules that may use it to authentication. */
300 	envccname = sudo_getenv("KRB5CCNAME");
301 	if (envccname == NULL && user_ccname != NULL) {
302 		if (sudo_setenv("KRB5CCNAME", user_ccname, true) != 0) {
303 			sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
304 			"unable to set KRB5CCNAME");
305 			debug_return_int(AUTH_FAILURE);
306 		}
307 	}
308 
309     /* PAM_SILENT prevents the authentication service from generating output. */
310     *pam_status = pam_authenticate(pamh, PAM_SILENT);
311 
312     /* Restore def_prompt, the passed-in prompt may be freed later. */
313     def_prompt = PASSPROMPT;
314 
315 	/* Restore KRB5CCNAME to its original value. */
316 	if (envccname == NULL && sudo_unsetenv("KRB5CCNAME") != 0) {
317 		sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
318 		"unable to restore KRB5CCNAME");
319 		debug_return_int(AUTH_FAILURE);
320 	}
321 
322     if (getpass_error) {
323 	/* error or ^C from tgetpass() */
324 	debug_return_int(AUTH_INTR);
325     }
326     switch (*pam_status) {
327 	case PAM_SUCCESS:
328 	    debug_return_int(AUTH_SUCCESS);
329 	case PAM_AUTH_ERR:
330 	case PAM_AUTHINFO_UNAVAIL:
331 	case PAM_MAXTRIES:
332 	case PAM_PERM_DENIED:
333 	    sudo_debug_printf(SUDO_DEBUG_WARN|SUDO_DEBUG_LINENO,
334 		"pam_authenticate: %d", *pam_status);
335 	    debug_return_int(AUTH_FAILURE);
336 	default:
337 	    s = sudo_pam_strerror(pamh, *pam_status);
338 	    log_warningx(0, N_("PAM authentication error: %s"), s);
339 	    debug_return_int(AUTH_FATAL);
340     }
341 }
342 
343 int
sudo_pam_approval(struct passwd * pw,sudo_auth * auth,bool exempt)344 sudo_pam_approval(struct passwd *pw, sudo_auth *auth, bool exempt)
345 {
346     const char *s;
347     int rc, status = AUTH_SUCCESS;
348     int *pam_status = (int *) auth->data;
349     debug_decl(sudo_pam_approval, SUDOERS_DEBUG_AUTH);
350 
351     if (def_pam_acct_mgmt) {
352 	rc = pam_acct_mgmt(pamh, PAM_SILENT);
353 	switch (rc) {
354 	    case PAM_SUCCESS:
355 		break;
356 	    case PAM_AUTH_ERR:
357 		log_warningx(0, N_("account validation failure, "
358 		    "is your account locked?"));
359 		status = AUTH_FATAL;
360 		break;
361 	    case PAM_NEW_AUTHTOK_REQD:
362 		/* Ignore if user is exempt from password restrictions. */
363 		if (exempt) {
364 		    rc = *pam_status;
365 		    break;
366 		}
367 		/* New password required, try to change it. */
368 		log_warningx(0, N_("Account or password is "
369 		    "expired, reset your password and try again"));
370 		rc = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK);
371 		if (rc == PAM_SUCCESS)
372 		    break;
373 		s = pam_strerror(pamh, rc);
374 		log_warningx(0, N_("unable to change expired password: %s"), s);
375 		status = AUTH_FAILURE;
376 		break;
377 	    case PAM_AUTHTOK_EXPIRED:
378 		/* Ignore if user is exempt from password restrictions. */
379 		if (exempt) {
380 		    rc = *pam_status;
381 		    break;
382 		}
383 		/* Password expired, cannot be updated by user. */
384 		log_warningx(0,
385 		    N_("Password expired, contact your system administrator"));
386 		status = AUTH_FATAL;
387 		break;
388 	    case PAM_ACCT_EXPIRED:
389 		log_warningx(0,
390 		    N_("Account expired or PAM config lacks an \"account\" "
391 		    "section for sudo, contact your system administrator"));
392 		status = AUTH_FATAL;
393 		break;
394 	    case PAM_AUTHINFO_UNAVAIL:
395 	    case PAM_MAXTRIES:
396 	    case PAM_PERM_DENIED:
397 		s = sudo_pam_strerror(pamh, rc);
398 		log_warningx(0, N_("PAM account management error: %s"), s);
399 		status = AUTH_FAILURE;
400 		break;
401 	    default:
402 		s = sudo_pam_strerror(pamh, rc);
403 		log_warningx(0, N_("PAM account management error: %s"), s);
404 		status = AUTH_FATAL;
405 		break;
406 	}
407 	*pam_status = rc;
408     }
409     debug_return_int(status);
410 }
411 
412 int
sudo_pam_cleanup(struct passwd * pw,sudo_auth * auth,bool force)413 sudo_pam_cleanup(struct passwd *pw, sudo_auth *auth, bool force)
414 {
415     int *pam_status = (int *) auth->data;
416     debug_decl(sudo_pam_cleanup, SUDOERS_DEBUG_AUTH);
417 
418     /* If successful, we can't close the session until sudo_pam_end_session() */
419     if (force || *pam_status != PAM_SUCCESS || auth->end_session == NULL) {
420 	*pam_status = pam_end(pamh, *pam_status | PAM_DATA_SILENT);
421 	pamh = NULL;
422     }
423     debug_return_int(*pam_status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE);
424 }
425 
426 int
sudo_pam_begin_session(struct passwd * pw,char ** user_envp[],sudo_auth * auth)427 sudo_pam_begin_session(struct passwd *pw, char **user_envp[], sudo_auth *auth)
428 {
429     int rc, status = AUTH_SUCCESS;
430     int *pam_status = (int *) auth->data;
431     const char *errstr;
432     debug_decl(sudo_pam_begin_session, SUDOERS_DEBUG_AUTH);
433 
434     /*
435      * If there is no valid user we cannot open a PAM session.
436      * This is not an error as sudo can run commands with arbitrary
437      * uids, it just means we are done from a session management standpoint.
438      */
439     if (pw == NULL) {
440 	if (pamh != NULL) {
441 	    rc = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT);
442 	    if (rc != PAM_SUCCESS) {
443 		errstr = sudo_pam_strerror(pamh, rc);
444 		sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
445 		    "pam_end: %s", errstr);
446 	    }
447 	    pamh = NULL;
448 	}
449 	goto done;
450     }
451 
452     /*
453      * Update PAM_USER to reference the user we are running the command
454      * as, as opposed to the user we authenticated as.
455      */
456     rc = pam_set_item(pamh, PAM_USER, pw->pw_name);
457     if (rc != PAM_SUCCESS) {
458 	errstr = sudo_pam_strerror(pamh, rc);
459 	sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
460 	    "pam_set_item(pamh, PAM_USER, %s): %s", pw->pw_name, errstr);
461     }
462 
463     /*
464      * Reinitialize credentials when changing the user.
465      * We don't worry about a failure from pam_setcred() since with
466      * stacked PAM auth modules a failure from one module may override
467      * PAM_SUCCESS from another.  For example, given a non-local user,
468      * pam_unix will fail but pam_ldap or pam_sss may succeed, but if
469      * pam_unix is first in the stack, pam_setcred() will fail.
470      */
471     if (def_pam_setcred) {
472 	rc = pam_setcred(pamh, PAM_REINITIALIZE_CRED);
473 	if (rc != PAM_SUCCESS) {
474 	    errstr = sudo_pam_strerror(pamh, rc);
475 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
476 		"pam_setcred: %s", errstr);
477 	    def_pam_setcred = false;
478 	}
479     }
480 
481     if (def_pam_session) {
482 	/*
483 	 * We use PAM_SILENT to prevent pam_lastlog from printing last login
484 	 * information except when explicitly running a shell.
485 	 */
486 	const bool silent = !ISSET(sudo_mode, MODE_SHELL|MODE_LOGIN_SHELL);
487 	rc = pam_open_session(pamh, silent ? PAM_SILENT : 0);
488 	switch (rc) {
489 	case PAM_SUCCESS:
490 	    break;
491 	case PAM_SESSION_ERR:
492 	    /* Treat PAM_SESSION_ERR as a non-fatal error. */
493 	    errstr = sudo_pam_strerror(pamh, rc);
494 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
495 		"pam_open_session: %s", errstr);
496 	    /* Avoid closing session that was not opened. */
497 	    def_pam_session = false;
498 	    break;
499 	default:
500 	    /* Unexpected session failure, treat as fatal error. */
501 	    *pam_status = rc;
502 	    errstr = sudo_pam_strerror(pamh, rc);
503 	    log_warningx(0, N_("%s: %s"), "pam_open_session", errstr);
504 	    rc = pam_end(pamh, *pam_status | PAM_DATA_SILENT);
505 	    if (rc != PAM_SUCCESS) {
506 		errstr = sudo_pam_strerror(pamh, rc);
507 		sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
508 		    "pam_end: %s", errstr);
509 	    }
510 	    pamh = NULL;
511 	    status = AUTH_FATAL;
512 	    goto done;
513 	}
514     }
515 
516 #ifdef HAVE_PAM_GETENVLIST
517     /*
518      * Update environment based on what is stored in pamh.
519      * If no authentication is done we will only have environment
520      * variables if pam_env is called via session.
521      */
522     if (user_envp != NULL) {
523 	char **pam_envp = pam_getenvlist(pamh);
524 	if (pam_envp != NULL) {
525 	    /* Merge pam env with user env. */
526 	    if (!env_init(*user_envp) || !env_merge(pam_envp))
527 		status = AUTH_FATAL;
528 	    *user_envp = env_get();
529 	    free(pam_envp);
530 	    /* XXX - we leak any duplicates that were in pam_envp */
531 	}
532     }
533 #endif /* HAVE_PAM_GETENVLIST */
534 
535 done:
536     debug_return_int(status);
537 }
538 
539 int
sudo_pam_end_session(struct passwd * pw,sudo_auth * auth)540 sudo_pam_end_session(struct passwd *pw, sudo_auth *auth)
541 {
542     int rc, status = AUTH_SUCCESS;
543     const char *errstr;
544     debug_decl(sudo_pam_end_session, SUDOERS_DEBUG_AUTH);
545 
546     if (pamh != NULL) {
547 	/*
548 	 * Update PAM_USER to reference the user we are running the command
549 	 * as, as opposed to the user we authenticated as.
550 	 * XXX - still needed now that session init is in parent?
551 	 */
552 	rc = pam_set_item(pamh, PAM_USER, pw->pw_name);
553 	if (rc != PAM_SUCCESS) {
554 	    errstr = sudo_pam_strerror(pamh, rc);
555 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
556 		"pam_set_item(pamh, PAM_USER, %s): %s", pw->pw_name, errstr);
557 	}
558 	if (def_pam_session) {
559 	    rc = pam_close_session(pamh, PAM_SILENT);
560 	    if (rc != PAM_SUCCESS) {
561 		errstr = sudo_pam_strerror(pamh, rc);
562 		sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
563 		    "pam_close_session: %s", errstr);
564 	    }
565 	}
566 	if (def_pam_setcred) {
567 	    rc = pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT);
568 	    if (rc != PAM_SUCCESS) {
569 		errstr = sudo_pam_strerror(pamh, rc);
570 		sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
571 		    "pam_setcred: %s", errstr);
572 	    }
573 	}
574 	rc = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT);
575 	if (rc != PAM_SUCCESS) {
576 	    errstr = sudo_pam_strerror(pamh, rc);
577 	    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
578 		"pam_end: %s", errstr);
579 	    status = AUTH_FATAL;
580 	}
581 	pamh = NULL;
582     }
583 
584     debug_return_int(status);
585 }
586 
587 #define PROMPT_IS_PASSWORD(_p) \
588     (strncmp((_p), "Password:", 9) == 0 && \
589 	((_p)[9] == '\0' || ((_p)[9] == ' ' && (_p)[10] == '\0')))
590 
591 #ifdef PAM_TEXT_DOMAIN
592 # define PAM_PROMPT_IS_PASSWORD(_p) \
593     (strcmp((_p), dgt(PAM_TEXT_DOMAIN, "Password:")) == 0 || \
594 	strcmp((_p), dgt(PAM_TEXT_DOMAIN, "Password: ")) == 0 || \
595 	PROMPT_IS_PASSWORD(_p))
596 #else
597 # define PAM_PROMPT_IS_PASSWORD(_p)	PROMPT_IS_PASSWORD(_p)
598 #endif /* PAM_TEXT_DOMAIN */
599 
600 /*
601  * We use the PAM prompt in preference to sudo's as long
602  * as passprompt_override is not set and:
603  *  a) the (translated) sudo prompt matches /^Password: ?/
604  * or:
605  *  b) the PAM prompt itself *doesn't* match /^Password: ?/
606  *     or /^username's Password: ?/
607  *
608  * The intent is to use the PAM prompt for things like
609  * challenge-response, otherwise use sudo's prompt.
610  * There may also be cases where a localized translation
611  * of "Password: " exists for PAM but not for sudo.
612  */
613 static bool
use_pam_prompt(const char * pam_prompt)614 use_pam_prompt(const char *pam_prompt)
615 {
616     size_t user_len;
617     debug_decl(use_pam_prompt, SUDOERS_DEBUG_AUTH);
618 
619     /* Always use sudo prompt if passprompt_override is set. */
620     if (def_passprompt_override)
621 	debug_return_bool(false);
622 
623     /* If sudo prompt matches "^Password: ?$", use PAM prompt. */
624     if (PROMPT_IS_PASSWORD(def_prompt))
625 	debug_return_bool(true);
626 
627     /* If PAM prompt matches "^Password: ?$", use sudo prompt. */
628     if (PAM_PROMPT_IS_PASSWORD(pam_prompt))
629 	debug_return_bool(false);
630 
631     /*
632      * Some PAM modules use "^username's Password: ?$" instead of
633      * "^Password: ?" so check for that too.
634      */
635     user_len = strlen(user_name);
636     if (strncmp(pam_prompt, user_name, user_len) == 0) {
637 	const char *cp = pam_prompt + user_len;
638 	if (strncmp(cp, "'s Password:", 12) == 0 &&
639 	    (cp[12] == '\0' || (cp[12] == ' ' && cp[13] == '\0')))
640 	    debug_return_bool(false);
641     }
642 
643     /* Otherwise, use the PAM prompt. */
644     debug_return_bool(true);
645 }
646 
647 static bool
is_filtered(const char * msg)648 is_filtered(const char *msg)
649 {
650     bool filtered = false;
651 
652     if (conv_filter != NULL) {
653 	struct conv_filter *filt = conv_filter;
654 	while (filt->msg != NULL) {
655 	    if (strncmp(msg, filt->msg, filt->msglen) == 0) {
656 		filtered = true;
657 		break;
658 	    }
659 	    filt++;
660 	}
661     }
662     return filtered;
663 }
664 
665 /*
666  * ``Conversation function'' for PAM <-> human interaction.
667  */
668 static int
converse(int num_msg,PAM_CONST struct pam_message ** msg,struct pam_response ** reply_out,void * vcallback)669 converse(int num_msg, PAM_CONST struct pam_message **msg,
670     struct pam_response **reply_out, void *vcallback)
671 {
672     struct sudo_conv_callback *callback = NULL;
673     struct pam_response *reply;
674     const char *prompt;
675     char *pass;
676     int n, type;
677     int ret = PAM_SUCCESS;
678     debug_decl(converse, SUDOERS_DEBUG_AUTH);
679 
680     if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG) {
681 	sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
682 	    "invalid number of PAM messages: %d", num_msg);
683 	debug_return_int(PAM_CONV_ERR);
684     }
685     sudo_debug_printf(SUDO_DEBUG_DEBUG|SUDO_DEBUG_LINENO,
686 	"number of PAM messages: %d", num_msg);
687 
688     if ((reply = calloc(num_msg, sizeof(struct pam_response))) == NULL) {
689 	sudo_warnx(U_("%s: %s"), __func__, U_("unable to allocate memory"));
690 	debug_return_int(PAM_BUF_ERR);
691     }
692     *reply_out = reply;
693 
694     if (vcallback != NULL)
695 	callback = *((struct sudo_conv_callback **)vcallback);
696 
697     for (n = 0; n < num_msg; n++) {
698 	PAM_CONST struct pam_message *pm = PAM_MSG_GET(msg, n);
699 
700 	type = SUDO_CONV_PROMPT_ECHO_OFF;
701 	switch (pm->msg_style) {
702 	    case PAM_PROMPT_ECHO_ON:
703 		type = SUDO_CONV_PROMPT_ECHO_ON;
704 		FALLTHROUGH;
705 	    case PAM_PROMPT_ECHO_OFF:
706 		/* Error out if the last password read was interrupted. */
707 		if (getpass_error)
708 		    goto done;
709 
710 		/* Choose either the sudo prompt or the PAM one. */
711 		prompt = use_pam_prompt(pm->msg) ? pm->msg : def_prompt;
712 
713 		/* Read the password unless interrupted. */
714 		pass = auth_getpass(prompt, type, callback);
715 		if (pass == NULL) {
716 		    /* Error (or ^C) reading password, don't try again. */
717 		    getpass_error = true;
718 		    ret = PAM_CONV_ERR;
719 		    goto done;
720 		}
721 		if (strlen(pass) >= PAM_MAX_RESP_SIZE) {
722 		    sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
723 			"password longer than %d", PAM_MAX_RESP_SIZE);
724 		    ret = PAM_CONV_ERR;
725 		    explicit_bzero(pass, strlen(pass));
726 		    goto done;
727 		}
728 		reply[n].resp = pass;	/* auth_getpass() malloc's a copy */
729 		break;
730 	    case PAM_TEXT_INFO:
731 		if (pm->msg != NULL && !is_filtered(pm->msg))
732 		    sudo_printf(SUDO_CONV_INFO_MSG|SUDO_CONV_PREFER_TTY,
733 			"%s\n", pm->msg);
734 		break;
735 	    case PAM_ERROR_MSG:
736 		if (pm->msg != NULL)
737 		    sudo_printf(SUDO_CONV_ERROR_MSG|SUDO_CONV_PREFER_TTY,
738 			"%s\n", pm->msg);
739 		break;
740 	    default:
741 		sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
742 		    "unsupported message style: %d", pm->msg_style);
743 		ret = PAM_CONV_ERR;
744 		goto done;
745 	}
746     }
747 
748 done:
749     if (ret != PAM_SUCCESS) {
750 	/* Zero and free allocated memory and return an error. */
751 	for (n = 0; n < num_msg; n++) {
752 	    struct pam_response *pr = &reply[n];
753 
754 	    if (pr->resp != NULL) {
755 		freezero(pr->resp, strlen(pr->resp));
756 		pr->resp = NULL;
757 	    }
758 	}
759 	free(reply);
760 	*reply_out = NULL;
761     }
762     debug_return_int(ret);
763 }
764 
765 #endif /* HAVE_PAM */
766