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 #include "includes.h" 26 27 #ifdef USE_PAM 28 #include "xmalloc.h" 29 #include "log.h" 30 #include "auth.h" 31 #include "auth-options.h" 32 #include "auth-pam.h" 33 #include "servconf.h" 34 #include "canohost.h" 35 #include "readpass.h" 36 37 extern char *__progname; 38 39 extern int use_privsep; 40 41 RCSID("$Id: auth-pam.c,v 1.54 2002/07/28 20:24:08 stevesk Exp $"); 42 RCSID("$FreeBSD$"); 43 44 #define NEW_AUTHTOK_MSG \ 45 "Warning: Your password has expired, please change it now." 46 #define NEW_AUTHTOK_MSG_PRIVSEP \ 47 "Your password has expired, the session cannot proceed." 48 49 static int do_pam_conversation(int num_msg, const struct pam_message **msg, 50 struct pam_response **resp, void *appdata_ptr); 51 52 /* module-local variables */ 53 static struct pam_conv conv = { 54 (int (*)())do_pam_conversation, 55 NULL 56 }; 57 static char *__pam_msg = NULL; 58 static pam_handle_t *__pamh = NULL; 59 static const char *__pampasswd = NULL; 60 61 /* states for do_pam_conversation() */ 62 enum { INITIAL_LOGIN, OTHER } pamstate = INITIAL_LOGIN; 63 /* remember whether pam_acct_mgmt() returned PAM_NEW_AUTHTOK_REQD */ 64 static int password_change_required = 0; 65 /* remember whether the last pam_authenticate() succeeded or not */ 66 static int was_authenticated = 0; 67 68 /* Remember what has been initialised */ 69 static int session_opened = 0; 70 static int creds_set = 0; 71 72 /* accessor which allows us to switch conversation structs according to 73 * the authentication method being used */ 74 void do_pam_set_conv(struct pam_conv *conv) 75 { 76 pam_set_item(__pamh, PAM_CONV, conv); 77 } 78 79 /* start an authentication run */ 80 int do_pam_authenticate(int flags) 81 { 82 int retval = pam_authenticate(__pamh, flags); 83 was_authenticated = (retval == PAM_SUCCESS); 84 return retval; 85 } 86 87 /* 88 * PAM conversation function. 89 * There are two states this can run in. 90 * 91 * INITIAL_LOGIN mode simply feeds the password from the client into 92 * PAM in response to PAM_PROMPT_ECHO_OFF, and collects output 93 * messages with into __pam_msg. This is used during initial 94 * authentication to bypass the normal PAM password prompt. 95 * 96 * OTHER mode handles PAM_PROMPT_ECHO_OFF with read_passphrase() 97 * and outputs messages to stderr. This mode is used if pam_chauthtok() 98 * is called to update expired passwords. 99 */ 100 static int do_pam_conversation(int num_msg, const struct pam_message **msg, 101 struct pam_response **resp, void *appdata_ptr) 102 { 103 struct pam_response *reply; 104 int count; 105 char buf[1024]; 106 107 /* PAM will free this later */ 108 reply = xmalloc(num_msg * sizeof(*reply)); 109 110 for (count = 0; count < num_msg; count++) { 111 if (pamstate == INITIAL_LOGIN) { 112 /* 113 * We can't use stdio yet, queue messages for 114 * printing later 115 */ 116 switch(PAM_MSG_MEMBER(msg, count, msg_style)) { 117 case PAM_PROMPT_ECHO_ON: 118 xfree(reply); 119 return PAM_CONV_ERR; 120 case PAM_PROMPT_ECHO_OFF: 121 if (__pampasswd == NULL) { 122 xfree(reply); 123 return PAM_CONV_ERR; 124 } 125 reply[count].resp = xstrdup(__pampasswd); 126 reply[count].resp_retcode = PAM_SUCCESS; 127 break; 128 case PAM_ERROR_MSG: 129 case PAM_TEXT_INFO: 130 if (PAM_MSG_MEMBER(msg, count, msg) != NULL) { 131 message_cat(&__pam_msg, 132 PAM_MSG_MEMBER(msg, count, msg)); 133 } 134 reply[count].resp = xstrdup(""); 135 reply[count].resp_retcode = PAM_SUCCESS; 136 break; 137 default: 138 xfree(reply); 139 return PAM_CONV_ERR; 140 } 141 } else { 142 /* 143 * stdio is connected, so interact directly 144 */ 145 switch(PAM_MSG_MEMBER(msg, count, msg_style)) { 146 case PAM_PROMPT_ECHO_ON: 147 fputs(PAM_MSG_MEMBER(msg, count, msg), stderr); 148 fgets(buf, sizeof(buf), stdin); 149 reply[count].resp = xstrdup(buf); 150 reply[count].resp_retcode = PAM_SUCCESS; 151 break; 152 case PAM_PROMPT_ECHO_OFF: 153 reply[count].resp = 154 read_passphrase(PAM_MSG_MEMBER(msg, count, 155 msg), RP_ALLOW_STDIN); 156 reply[count].resp_retcode = PAM_SUCCESS; 157 break; 158 case PAM_ERROR_MSG: 159 case PAM_TEXT_INFO: 160 if (PAM_MSG_MEMBER(msg, count, msg) != NULL) 161 fprintf(stderr, "%s\n", 162 PAM_MSG_MEMBER(msg, count, msg)); 163 reply[count].resp = xstrdup(""); 164 reply[count].resp_retcode = PAM_SUCCESS; 165 break; 166 default: 167 xfree(reply); 168 return PAM_CONV_ERR; 169 } 170 } 171 } 172 173 *resp = reply; 174 175 return PAM_SUCCESS; 176 } 177 178 /* Called at exit to cleanly shutdown PAM */ 179 void do_pam_cleanup_proc(void *context) 180 { 181 int pam_retval = PAM_SUCCESS; 182 183 if (__pamh && session_opened) { 184 pam_retval = pam_close_session(__pamh, 0); 185 if (pam_retval != PAM_SUCCESS) 186 log("Cannot close PAM session[%d]: %.200s", 187 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 188 } 189 190 if (__pamh && creds_set) { 191 pam_retval = pam_setcred(__pamh, PAM_DELETE_CRED); 192 if (pam_retval != PAM_SUCCESS) 193 debug("Cannot delete credentials[%d]: %.200s", 194 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 195 } 196 197 if (__pamh) { 198 pam_retval = pam_end(__pamh, pam_retval); 199 if (pam_retval != PAM_SUCCESS) 200 log("Cannot release PAM authentication[%d]: %.200s", 201 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 202 } 203 } 204 205 /* Attempt password authentation using PAM */ 206 int auth_pam_password(Authctxt *authctxt, const char *password) 207 { 208 extern ServerOptions options; 209 int pam_retval; 210 struct passwd *pw = authctxt->pw; 211 212 do_pam_set_conv(&conv); 213 214 /* deny if no user. */ 215 if (pw == NULL) 216 return 0; 217 if (pw->pw_uid == 0 && options.permit_root_login == PERMIT_NO_PASSWD) 218 return 0; 219 if (*password == '\0' && options.permit_empty_passwd == 0) 220 return 0; 221 222 __pampasswd = password; 223 224 pamstate = INITIAL_LOGIN; 225 pam_retval = do_pam_authenticate( 226 options.permit_empty_passwd == 0 ? PAM_DISALLOW_NULL_AUTHTOK : 0); 227 if (pam_retval == PAM_SUCCESS) { 228 debug("PAM Password authentication accepted for " 229 "user \"%.100s\"", pw->pw_name); 230 return 1; 231 } else { 232 debug("PAM Password authentication for \"%.100s\" " 233 "failed[%d]: %s", pw->pw_name, pam_retval, 234 PAM_STRERROR(__pamh, pam_retval)); 235 return 0; 236 } 237 } 238 239 /* Do account management using PAM */ 240 int do_pam_account(char *username, char *remote_user) 241 { 242 int pam_retval; 243 244 do_pam_set_conv(&conv); 245 246 if (remote_user) { 247 debug("PAM setting ruser to \"%.200s\"", remote_user); 248 pam_retval = pam_set_item(__pamh, PAM_RUSER, remote_user); 249 if (pam_retval != PAM_SUCCESS) 250 fatal("PAM set ruser failed[%d]: %.200s", pam_retval, 251 PAM_STRERROR(__pamh, pam_retval)); 252 } 253 254 pam_retval = pam_acct_mgmt(__pamh, 0); 255 debug2("pam_acct_mgmt() = %d", pam_retval); 256 switch (pam_retval) { 257 case PAM_SUCCESS: 258 /* This is what we want */ 259 break; 260 #if 0 261 case PAM_NEW_AUTHTOK_REQD: 262 message_cat(&__pam_msg, use_privsep ? 263 NEW_AUTHTOK_MSG_PRIVSEP : NEW_AUTHTOK_MSG); 264 /* flag that password change is necessary */ 265 password_change_required = 1; 266 /* disallow other functionality for now */ 267 no_port_forwarding_flag |= 2; 268 no_agent_forwarding_flag |= 2; 269 no_x11_forwarding_flag |= 2; 270 break; 271 #endif 272 default: 273 log("PAM rejected by account configuration[%d]: " 274 "%.200s", pam_retval, PAM_STRERROR(__pamh, 275 pam_retval)); 276 return(0); 277 } 278 279 return(1); 280 } 281 282 /* Do PAM-specific session initialisation */ 283 void do_pam_session(char *username, const char *ttyname) 284 { 285 int pam_retval; 286 287 do_pam_set_conv(&conv); 288 289 if (ttyname != NULL) { 290 debug("PAM setting tty to \"%.200s\"", ttyname); 291 pam_retval = pam_set_item(__pamh, PAM_TTY, ttyname); 292 if (pam_retval != PAM_SUCCESS) 293 fatal("PAM set tty failed[%d]: %.200s", 294 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 295 } 296 297 pam_retval = pam_open_session(__pamh, 0); 298 if (pam_retval != PAM_SUCCESS) 299 fatal("PAM session setup failed[%d]: %.200s", 300 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 301 302 session_opened = 1; 303 } 304 305 /* Set PAM credentials */ 306 void do_pam_setcred(int init) 307 { 308 int pam_retval; 309 310 if (__pamh == NULL) 311 return; 312 313 do_pam_set_conv(&conv); 314 315 debug("PAM establishing creds"); 316 pam_retval = pam_setcred(__pamh, 317 init ? PAM_ESTABLISH_CRED : PAM_REINITIALIZE_CRED); 318 if (pam_retval != PAM_SUCCESS) { 319 if (was_authenticated) 320 fatal("PAM setcred failed[%d]: %.200s", 321 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 322 else 323 debug("PAM setcred failed[%d]: %.200s", 324 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 325 } else 326 creds_set = 1; 327 } 328 329 /* accessor function for file scope static variable */ 330 int is_pam_password_change_required(void) 331 { 332 return password_change_required; 333 } 334 335 /* 336 * Have user change authentication token if pam_acct_mgmt() indicated 337 * it was expired. This needs to be called after an interactive 338 * session is established and the user's pty is connected to 339 * stdin/stdout/stderr. 340 */ 341 void do_pam_chauthtok(void) 342 { 343 int pam_retval; 344 345 do_pam_set_conv(&conv); 346 347 if (password_change_required) { 348 if (use_privsep) 349 fatal("Password changing is currently unsupported" 350 " with privilege separation"); 351 pamstate = OTHER; 352 pam_retval = pam_chauthtok(__pamh, PAM_CHANGE_EXPIRED_AUTHTOK); 353 if (pam_retval != PAM_SUCCESS) 354 fatal("PAM pam_chauthtok failed[%d]: %.200s", 355 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 356 #if 0 357 /* XXX: This would need to be done in the parent process, 358 * but there's currently no way to pass such request. */ 359 no_port_forwarding_flag &= ~2; 360 no_agent_forwarding_flag &= ~2; 361 no_x11_forwarding_flag &= ~2; 362 if (!no_port_forwarding_flag && options.allow_tcp_forwarding) 363 channel_permit_all_opens(); 364 #endif 365 } 366 } 367 368 /* Cleanly shutdown PAM */ 369 void finish_pam(void) 370 { 371 do_pam_cleanup_proc(NULL); 372 fatal_remove_cleanup(&do_pam_cleanup_proc, NULL); 373 } 374 375 /* Start PAM authentication for specified account */ 376 void start_pam(const char *user) 377 { 378 int pam_retval; 379 extern ServerOptions options; 380 extern u_int utmp_len; 381 const char *rhost; 382 383 debug("Starting up PAM with username \"%.200s\"", user); 384 385 pam_retval = pam_start(SSHD_PAM_SERVICE, user, &conv, &__pamh); 386 387 if (pam_retval != PAM_SUCCESS) 388 fatal("PAM initialisation failed[%d]: %.200s", 389 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 390 391 rhost = get_remote_name_or_ip(utmp_len, options.verify_reverse_mapping); 392 debug("PAM setting rhost to \"%.200s\"", rhost); 393 394 pam_retval = pam_set_item(__pamh, PAM_RHOST, rhost); 395 if (pam_retval != PAM_SUCCESS) 396 fatal("PAM set rhost failed[%d]: %.200s", pam_retval, 397 PAM_STRERROR(__pamh, pam_retval)); 398 #ifdef PAM_TTY_KLUDGE 399 /* 400 * Some PAM modules (e.g. pam_time) require a TTY to operate, 401 * and will fail in various stupid ways if they don't get one. 402 * sshd doesn't set the tty until too late in the auth process and may 403 * not even need one (for tty-less connections) 404 * Kludge: Set a fake PAM_TTY 405 */ 406 pam_retval = pam_set_item(__pamh, PAM_TTY, "NODEVssh"); 407 if (pam_retval != PAM_SUCCESS) 408 fatal("PAM set tty failed[%d]: %.200s", 409 pam_retval, PAM_STRERROR(__pamh, pam_retval)); 410 #endif /* PAM_TTY_KLUDGE */ 411 412 fatal_add_cleanup(&do_pam_cleanup_proc, NULL); 413 } 414 415 /* Return list of PAM environment strings */ 416 char **fetch_pam_environment(void) 417 { 418 #ifdef HAVE_PAM_GETENVLIST 419 return(pam_getenvlist(__pamh)); 420 #else /* HAVE_PAM_GETENVLIST */ 421 return(NULL); 422 #endif /* HAVE_PAM_GETENVLIST */ 423 } 424 425 void free_pam_environment(char **env) 426 { 427 int i; 428 429 if (env != NULL) { 430 for (i = 0; env[i] != NULL; i++) 431 xfree(env[i]); 432 } 433 } 434 435 /* Print any messages that have been generated during authentication */ 436 /* or account checking to stderr */ 437 void print_pam_messages(void) 438 { 439 if (__pam_msg != NULL) 440 fputs(__pam_msg, stderr); 441 } 442 443 /* Append a message to buffer */ 444 void message_cat(char **p, const char *a) 445 { 446 char *cp; 447 size_t new_len; 448 449 new_len = strlen(a); 450 451 if (*p) { 452 size_t len = strlen(*p); 453 454 *p = xrealloc(*p, new_len + len + 2); 455 cp = *p + len; 456 } else 457 *p = cp = xmalloc(new_len + 2); 458 459 memcpy(cp, a, new_len); 460 cp[new_len] = '\n'; 461 cp[new_len + 1] = '\0'; 462 } 463 464 #endif /* USE_PAM */ 465