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