1/* 2 * Copyright (c) 1999-2005, 2007-2011 Todd C. Miller <Todd.Miller@courtesan.com> 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 * 16 * Sponsored in part by the Defense Advanced Research Projects 17 * Agency (DARPA) and Air Force Research Laboratory, Air Force 18 * Materiel Command, USAF, under agreement number F39502-99-1-0512. 19 */ 20 21#include <config.h> 22 23#include <sys/types.h> 24#include <sys/param.h> 25#include <stdio.h> 26#ifdef STDC_HEADERS 27# include <stdlib.h> 28# include <stddef.h> 29#else 30# ifdef HAVE_STDLIB_H 31# include <stdlib.h> 32# endif 33#endif /* STDC_HEADERS */ 34#ifdef HAVE_STRING_H 35# include <string.h> 36#endif /* HAVE_STRING_H */ 37#ifdef HAVE_STRINGS_H 38# include <strings.h> 39#endif /* HAVE_STRINGS_H */ 40#ifdef HAVE_UNISTD_H 41# include <unistd.h> 42#endif /* HAVE_UNISTD_H */ 43#include <pwd.h> 44#include <errno.h> 45 46#ifdef HAVE_PAM_PAM_APPL_H 47# include <pam/pam_appl.h> 48#else 49# include <security/pam_appl.h> 50#endif 51 52#ifdef HAVE_DGETTEXT 53# include <libintl.h> 54# if defined(__LINUX_PAM__) 55# define PAM_TEXT_DOMAIN "Linux-PAM" 56# elif defined(__sun__) 57# define PAM_TEXT_DOMAIN "SUNW_OST_SYSOSPAM" 58# endif 59#endif 60 61#include "sudo.h" 62#include "sudo_auth.h" 63 64/* Only OpenPAM and Linux PAM use const qualifiers. */ 65#if defined(_OPENPAM) || defined(OPENPAM_VERSION) || \ 66 defined(__LIBPAM_VERSION) || defined(__LINUX_PAM__) 67# define PAM_CONST const 68#else 69# define PAM_CONST 70#endif 71 72static int sudo_conv __P((int, PAM_CONST struct pam_message **, 73 struct pam_response **, void *)); 74static char *def_prompt = "Password:"; 75static int getpass_error; 76 77#ifndef PAM_DATA_SILENT 78#define PAM_DATA_SILENT 0 79#endif 80 81static pam_handle_t *pamh; 82 83int 84pam_init(pw, auth) 85 struct passwd *pw; 86 sudo_auth *auth; 87{ 88 static struct pam_conv pam_conv; 89 static int pam_status; 90 91 /* Initial PAM setup */ 92 if (auth != NULL) 93 auth->data = (void *) &pam_status; 94 pam_conv.conv = sudo_conv; 95#ifdef HAVE_PAM_LOGIN 96 if (ISSET(sudo_mode, MODE_LOGIN_SHELL)) 97 pam_status = pam_start("sudo-i", pw->pw_name, &pam_conv, &pamh); 98 else 99#endif 100 pam_status = pam_start("sudo", pw->pw_name, &pam_conv, &pamh); 101 102 if (pam_status != PAM_SUCCESS) { 103 log_error(USE_ERRNO|NO_MAIL, "unable to initialize PAM"); 104 return AUTH_FATAL; 105 } 106 107 /* 108 * Set PAM_RUSER to the invoking user (the "from" user). 109 * We set PAM_RHOST to avoid a bug in Solaris 7 and below. 110 */ 111 (void) pam_set_item(pamh, PAM_RUSER, user_name); 112#ifdef __sun__ 113 (void) pam_set_item(pamh, PAM_RHOST, user_host); 114#endif 115 116 /* 117 * Some versions of pam_lastlog have a bug that 118 * will cause a crash if PAM_TTY is not set so if 119 * there is no tty, set PAM_TTY to the empty string. 120 */ 121 if (user_ttypath == NULL) 122 (void) pam_set_item(pamh, PAM_TTY, ""); 123 else 124 (void) pam_set_item(pamh, PAM_TTY, user_ttypath); 125 126 return AUTH_SUCCESS; 127} 128 129int 130pam_verify(pw, prompt, auth) 131 struct passwd *pw; 132 char *prompt; 133 sudo_auth *auth; 134{ 135 const char *s; 136 int *pam_status = (int *) auth->data; 137 138 def_prompt = prompt; /* for sudo_conv */ 139 140 /* PAM_SILENT prevents the authentication service from generating output. */ 141 *pam_status = pam_authenticate(pamh, PAM_SILENT); 142 switch (*pam_status) { 143 case PAM_SUCCESS: 144 *pam_status = pam_acct_mgmt(pamh, PAM_SILENT); 145 switch (*pam_status) { 146 case PAM_SUCCESS: 147 return AUTH_SUCCESS; 148 case PAM_AUTH_ERR: 149 log_error(NO_MAIL, 150 "account validation failure, is your account locked?"); 151 return AUTH_FATAL; 152 case PAM_NEW_AUTHTOK_REQD: 153 log_error(NO_MAIL, "%s, %s", 154 "Account or password is expired", 155 "reset your password and try again"); 156 *pam_status = pam_chauthtok(pamh, 157 PAM_CHANGE_EXPIRED_AUTHTOK); 158 if (*pam_status == PAM_SUCCESS) 159 return AUTH_SUCCESS; 160 if ((s = pam_strerror(pamh, *pam_status))) 161 log_error(NO_MAIL, "pam_chauthtok: %s", s); 162 return AUTH_FAILURE; 163 case PAM_AUTHTOK_EXPIRED: 164 log_error(NO_MAIL, 165 "Password expired, contact your system administrator"); 166 return AUTH_FATAL; 167 case PAM_ACCT_EXPIRED: 168 log_error(NO_MAIL, "%s %s", 169 "Account expired or PAM config lacks an \"account\"", 170 "section for sudo, contact your system administrator"); 171 return AUTH_FATAL; 172 } 173 /* FALLTHROUGH */ 174 case PAM_AUTH_ERR: 175 case PAM_AUTHINFO_UNAVAIL: 176 if (getpass_error) { 177 /* error or ^C from tgetpass() */ 178 return AUTH_INTR; 179 } 180 /* FALLTHROUGH */ 181 case PAM_MAXTRIES: 182 case PAM_PERM_DENIED: 183 return AUTH_FAILURE; 184 default: 185 if ((s = pam_strerror(pamh, *pam_status))) 186 log_error(NO_MAIL, "pam_authenticate: %s", s); 187 return AUTH_FATAL; 188 } 189} 190 191int 192pam_cleanup(pw, auth) 193 struct passwd *pw; 194 sudo_auth *auth; 195{ 196 int *pam_status = (int *) auth->data; 197 198 /* If successful, we can't close the session until pam_end_session() */ 199 if (*pam_status == AUTH_SUCCESS) 200 return AUTH_SUCCESS; 201 202 *pam_status = pam_end(pamh, *pam_status | PAM_DATA_SILENT); 203 pamh = NULL; 204 return *pam_status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE; 205} 206 207int 208pam_begin_session(pw) 209 struct passwd *pw; 210{ 211#ifdef HAVE_PAM_GETENVLIST 212 char **pam_envp; 213#endif 214 int status = PAM_SUCCESS; 215 int eval; 216 217 /* If the user did not have to authenticate there is no pam handle yet. */ 218 if (pamh == NULL) { 219 if (AUTH_SUCCESS != (eval = pam_init(pw, NULL))) 220 return eval; 221 } 222 223 /* 224 * If there is no valid user we cannot open a PAM session. 225 * This is not an error as sudo can run commands with arbitrary 226 * uids, it just means we are done from a session management standpoint. 227 */ 228 if (pw == NULL) { 229 if (pamh != NULL) { 230 (void) pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT); 231 pamh = NULL; 232 } 233 goto done; 234 } 235 236 /* 237 * Update PAM_USER to reference the user we are running the command 238 * as, as opposed to the user we authenticated as. 239 */ 240 (void) pam_set_item(pamh, PAM_USER, pw->pw_name); 241 242 /* 243 * Set credentials (may include resource limits, device ownership, etc). 244 * We don't check the return value here because in Linux-PAM 0.75 245 * it returns the last saved return code, not the return code 246 * for the setcred module. Because we haven't called pam_authenticate(), 247 * this is not set and so pam_setcred() returns PAM_PERM_DENIED. 248 * We can't call pam_acct_mgmt() with Linux-PAM for a similar reason. 249 */ 250 (void) pam_setcred(pamh, PAM_ESTABLISH_CRED); 251 252#ifdef HAVE_PAM_GETENVLIST 253 /* 254 * Update environment based on what is stored in pamh. 255 * If no authentication is done we will only have environment 256 * variables if pam_env is called via session. 257 */ 258 if ((pam_envp = pam_getenvlist(pamh)) != NULL) { 259 /* Merge pam env with user env but do not overwrite. */ 260 env_merge(pam_envp, FALSE); 261 efree(pam_envp); 262 /* XXX - we leak any duplicates that were in pam_envp */ 263 } 264#endif /* HAVE_PAM_GETENVLIST */ 265 266#ifndef NO_PAM_SESSION 267 status = pam_open_session(pamh, 0); 268 if (status != PAM_SUCCESS) { 269 (void) pam_end(pamh, status | PAM_DATA_SILENT); 270 pamh = NULL; 271 } 272#endif 273 274done: 275 return status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE; 276} 277 278int 279pam_end_session(pw) 280 struct passwd *pw; 281{ 282 int status = PAM_SUCCESS; 283 284 if (pamh != NULL) { 285 /* 286 * Update PAM_USER to reference the user we are running the command 287 * as, as opposed to the user we authenticated as. 288 */ 289 (void) pam_set_item(pamh, PAM_USER, pw->pw_name); 290#ifndef NO_PAM_SESSION 291 (void) pam_close_session(pamh, PAM_SILENT); 292#endif 293 (void) pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); 294 status = pam_end(pamh, PAM_SUCCESS | PAM_DATA_SILENT); 295 pamh = NULL; 296 } 297 298 return status == PAM_SUCCESS ? AUTH_SUCCESS : AUTH_FAILURE; 299} 300 301/* 302 * ``Conversation function'' for PAM. 303 * XXX - does not handle PAM_BINARY_PROMPT 304 */ 305static int 306sudo_conv(num_msg, msg, response, appdata_ptr) 307 int num_msg; 308 PAM_CONST struct pam_message **msg; 309 struct pam_response **response; 310 void *appdata_ptr; 311{ 312 struct pam_response *pr; 313 PAM_CONST struct pam_message *pm; 314 const char *prompt; 315 char *pass; 316 int n, flags, std_prompt; 317 int ret = PAM_AUTH_ERR; 318 319 if ((*response = malloc(num_msg * sizeof(struct pam_response))) == NULL) 320 return PAM_SYSTEM_ERR; 321 zero_bytes(*response, num_msg * sizeof(struct pam_response)); 322 323 for (pr = *response, pm = *msg, n = num_msg; n--; pr++, pm++) { 324 flags = tgetpass_flags; 325 switch (pm->msg_style) { 326 case PAM_PROMPT_ECHO_ON: 327 SET(flags, TGP_ECHO); 328 /* FALLTHROUGH */ 329 case PAM_PROMPT_ECHO_OFF: 330 prompt = def_prompt; 331 332 /* Error out if the last password read was interrupted. */ 333 if (getpass_error) 334 goto done; 335 336 /* Is the sudo prompt standard? (If so, we'l just use PAM's) */ 337 std_prompt = strncmp(def_prompt, "Password:", 9) == 0 && 338 (def_prompt[9] == '\0' || 339 (def_prompt[9] == ' ' && def_prompt[10] == '\0')); 340 341 /* Only override PAM prompt if it matches /^Password: ?/ */ 342#if defined(PAM_TEXT_DOMAIN) && defined(HAVE_DGETTEXT) 343 if (!def_passprompt_override && (std_prompt || 344 (strcmp(pm->msg, dgettext(PAM_TEXT_DOMAIN, "Password: ")) && 345 strcmp(pm->msg, dgettext(PAM_TEXT_DOMAIN, "Password:"))))) 346 prompt = pm->msg; 347#else 348 if (!def_passprompt_override && (std_prompt || 349 strncmp(pm->msg, "Password:", 9) || (pm->msg[9] != '\0' 350 && (pm->msg[9] != ' ' || pm->msg[10] != '\0')))) 351 prompt = pm->msg; 352#endif 353 /* Read the password unless interrupted. */ 354 pass = tgetpass(prompt, def_passwd_timeout * 60, flags); 355 if (pass == NULL) { 356 /* Error (or ^C) reading password, don't try again. */ 357 getpass_error = 1; 358#if (defined(__darwin__) || defined(__APPLE__)) && !defined(OPENPAM_VERSION) 359 pass = ""; 360#else 361 goto done; 362#endif 363 } 364 pr->resp = estrdup(pass); 365 zero_bytes(pass, strlen(pass)); 366 break; 367 case PAM_TEXT_INFO: 368 if (pm->msg) 369 (void) puts(pm->msg); 370 break; 371 case PAM_ERROR_MSG: 372 if (pm->msg) { 373 (void) fputs(pm->msg, stderr); 374 (void) fputc('\n', stderr); 375 } 376 break; 377 default: 378 ret = PAM_CONV_ERR; 379 goto done; 380 } 381 } 382 ret = PAM_SUCCESS; 383 384done: 385 if (ret != PAM_SUCCESS) { 386 /* Zero and free allocated memory and return an error. */ 387 for (pr = *response, n = num_msg; n--; pr++) { 388 if (pr->resp != NULL) { 389 zero_bytes(pr->resp, strlen(pr->resp)); 390 free(pr->resp); 391 pr->resp = NULL; 392 } 393 } 394 zero_bytes(*response, num_msg * sizeof(struct pam_response)); 395 free(*response); 396 *response = NULL; 397 } 398 return ret; 399} 400