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