1 /* 2 * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 3 * Use is subject to license terms. 4 */ 5 6 #include "includes.h" 7 8 RCSID("$Id: auth2-pam.c,v 1.14 2002/06/28 16:48:12 mouring Exp $"); 9 10 #ifdef USE_PAM 11 #include <security/pam_appl.h> 12 13 #include "ssh.h" 14 #include "ssh2.h" 15 #include "auth.h" 16 #include "auth-pam.h" 17 #include "auth-options.h" 18 #include "packet.h" 19 #include "xmalloc.h" 20 #include "dispatch.h" 21 #include "canohost.h" 22 #include "log.h" 23 #include "servconf.h" 24 #include "misc.h" 25 26 #ifdef HAVE_BSM 27 #include "bsmaudit.h" 28 #endif /* HAVE_BSM */ 29 30 extern u_int utmp_len; 31 extern ServerOptions options; 32 33 extern Authmethod method_kbdint; 34 extern Authmethod method_passwd; 35 36 #define SSHD_PAM_KBDINT_SVC "sshd-kbdint" 37 /* Maximum attempts for changing expired password */ 38 #define DEF_ATTEMPTS 3 39 40 static int do_pam_conv_kbd_int(int num_msg, 41 struct pam_message **msg, struct pam_response **resp, 42 void *appdata_ptr); 43 static void input_userauth_info_response_pam(int type, 44 u_int32_t seqnr, 45 void *ctxt); 46 47 static struct pam_conv conv2 = { 48 do_pam_conv_kbd_int, 49 NULL, 50 }; 51 extern char *__pam_msg; 52 53 static void do_pam_kbdint_cleanup(pam_handle_t *pamh); 54 static void do_pam_kbdint(Authctxt *authctxt); 55 56 void 57 auth2_pam(Authctxt *authctxt) 58 { 59 if (authctxt->user == NULL) 60 fatal("auth2_pam: internal error: no user"); 61 if (authctxt->method == NULL) 62 fatal("auth2_pam: internal error: no method"); 63 64 conv2.appdata_ptr = authctxt; 65 new_start_pam(authctxt, &conv2); 66 67 authctxt->method->method_data = NULL; /* freed in the conv func */ 68 dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, 69 &input_userauth_info_response_pam); 70 71 /* 72 * Since password userauth and keyboard-interactive userauth 73 * both use PAM, and since keyboard-interactive is so much 74 * better than password userauth, we should not allow the user 75 * to try password userauth after trying keyboard-interactive. 76 */ 77 if (method_passwd.enabled) 78 *method_passwd.enabled = 0; 79 80 do_pam_kbdint(authctxt); 81 82 dispatch_set(SSH2_MSG_USERAUTH_INFO_RESPONSE, NULL); 83 } 84 85 static void 86 do_pam_kbdint(Authctxt *authctxt) 87 { 88 int retval, retval2; 89 pam_handle_t *pamh = authctxt->pam->h; 90 const char *where = "authenticating"; 91 char *text = NULL; 92 93 debug2("Calling pam_authenticate()"); 94 retval = pam_authenticate(pamh, 95 options.permit_empty_passwd ? 0 : 96 PAM_DISALLOW_NULL_AUTHTOK); 97 98 if (retval != PAM_SUCCESS) 99 goto cleanup; 100 101 debug2("kbd-int: pam_authenticate() succeeded"); 102 where = "authorizing"; 103 retval = pam_acct_mgmt(pamh, 0); 104 105 if (retval == PAM_NEW_AUTHTOK_REQD) { 106 if (authctxt->valid && authctxt->pw != NULL) { 107 /* send password expiration warning */ 108 message_cat(&text, 109 gettext("Warning: Your password has expired," 110 " please change it now.")); 111 packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST); 112 packet_put_cstring(""); /* name */ 113 packet_put_utf8_cstring(text); /* instructions */ 114 packet_put_cstring(""); /* language, unused */ 115 packet_put_int(0); 116 packet_send(); 117 packet_write_wait(); 118 debug("expiration message sent"); 119 if (text) 120 xfree(text); 121 /* 122 * wait for the response so it does not mix 123 * with the upcoming PAM conversation 124 */ 125 packet_read_expect(SSH2_MSG_USERAUTH_INFO_RESPONSE); 126 /* 127 * Can't use temporarily_use_uid() and restore_uid() 128 * here because we need (euid == 0 && ruid == pw_uid) 129 * whereas temporarily_use_uid() arranges for 130 * (suid = 0 && euid == pw_uid && ruid == pw_uid). 131 */ 132 (void) setreuid(authctxt->pw->pw_uid, -1); 133 debug2("kbd-int: changing expired password"); 134 where = "changing authentication tokens (password)"; 135 /* 136 * Depending on error returned from pam_chauthtok, we 137 * need to try to change password a few times before 138 * we error out and return. 139 */ 140 int tries = 0; 141 while ((retval = pam_chauthtok(pamh, 142 PAM_CHANGE_EXPIRED_AUTHTOK)) != PAM_SUCCESS) { 143 if (tries++ < DEF_ATTEMPTS) { 144 if ((retval == PAM_AUTHTOK_ERR) || 145 (retval == PAM_TRY_AGAIN)) { 146 continue; 147 } 148 } 149 break; 150 } 151 audit_sshd_chauthtok(retval, authctxt->pw->pw_uid, 152 authctxt->pw->pw_gid); 153 (void) setreuid(0, -1); 154 } else { 155 retval = PAM_PERM_DENIED; 156 } 157 } 158 159 if (retval != PAM_SUCCESS) 160 goto cleanup; 161 162 authctxt->pam->state |= PAM_S_DONE_ACCT_MGMT; 163 164 retval = finish_userauth_do_pam(authctxt); 165 166 if (retval != PAM_SUCCESS) 167 goto cleanup; 168 169 /* 170 * PAM handle stays around so we can call pam_close_session() 171 * on it later. 172 */ 173 authctxt->method->authenticated = 1; 174 debug2("kbd-int: success (pam->state == %x)", authctxt->pam->state); 175 return; 176 177 cleanup: 178 /* 179 * Check for abandonment and cleanup. When kbdint is abandoned 180 * authctxt->pam->h is NULLed and by this point a new handle may 181 * be allocated. 182 */ 183 if (authctxt->pam->h != pamh) { 184 log("Keyboard-interactive (PAM) userauth abandoned " 185 "while %s", where); 186 if ((retval2 = pam_end(pamh, retval)) != PAM_SUCCESS) { 187 log("Cannot close PAM handle after " 188 "kbd-int userauth abandonment[%d]: %.200s", 189 retval2, PAM_STRERROR(pamh, retval2)); 190 } 191 authctxt->method->abandoned = 1; 192 193 /* 194 * Avoid double counting; these are incremented in 195 * kbdint_pam_abandon() so that they reflect the correct 196 * count when userauth_finish() is called before 197 * unwinding the dispatch_run() loop, but they are 198 * incremented again in input_userauth_request() when 199 * the loop is unwound, right here. 200 */ 201 if (authctxt->method->abandons) 202 authctxt->method->abandons--; 203 if (authctxt->method->attempts) 204 authctxt->method->attempts--; 205 } 206 else { 207 /* Save error value for pam_end() */ 208 authctxt->pam->last_pam_retval = retval; 209 log("Keyboard-interactive (PAM) userauth failed[%d] " 210 "while %s: %.200s", retval, where, 211 PAM_STRERROR(pamh, retval)); 212 /* pam handle can be reused elsewhere, so no pam_end() here */ 213 } 214 215 return; 216 } 217 218 static int 219 do_pam_conv_kbd_int(int num_msg, struct pam_message **msg, 220 struct pam_response **resp, void *appdata_ptr) 221 { 222 int i, j; 223 char *text; 224 Convctxt *conv_ctxt; 225 Authctxt *authctxt = (Authctxt *)appdata_ptr; 226 227 if (!authctxt || !authctxt->method) { 228 debug("Missing state during PAM conversation"); 229 return PAM_CONV_ERR; 230 } 231 232 conv_ctxt = xmalloc(sizeof(Convctxt)); 233 (void) memset(conv_ctxt, 0, sizeof(Convctxt)); 234 conv_ctxt->finished = 0; 235 conv_ctxt->num_received = 0; 236 conv_ctxt->num_expected = 0; 237 conv_ctxt->prompts = xmalloc(sizeof(int) * num_msg); 238 conv_ctxt->responses = xmalloc(sizeof(struct pam_response) * num_msg); 239 (void) memset(conv_ctxt->responses, 0, sizeof(struct pam_response) * num_msg); 240 241 text = NULL; 242 for (i = 0, conv_ctxt->num_expected = 0; i < num_msg; i++) { 243 int style = PAM_MSG_MEMBER(msg, i, msg_style); 244 switch (style) { 245 case PAM_PROMPT_ECHO_ON: 246 debug2("PAM echo on prompt: %s", 247 PAM_MSG_MEMBER(msg, i, msg)); 248 conv_ctxt->num_expected++; 249 break; 250 case PAM_PROMPT_ECHO_OFF: 251 debug2("PAM echo off prompt: %s", 252 PAM_MSG_MEMBER(msg, i, msg)); 253 conv_ctxt->num_expected++; 254 break; 255 case PAM_TEXT_INFO: 256 debug2("PAM text info prompt: %s", 257 PAM_MSG_MEMBER(msg, i, msg)); 258 message_cat(&__pam_msg, PAM_MSG_MEMBER(msg, i, msg)); 259 break; 260 case PAM_ERROR_MSG: 261 debug2("PAM error prompt: %s", 262 PAM_MSG_MEMBER(msg, i, msg)); 263 message_cat(&__pam_msg, PAM_MSG_MEMBER(msg, i, msg)); 264 break; 265 default: 266 /* Capture all these messages to be sent at once */ 267 message_cat(&text, PAM_MSG_MEMBER(msg, i, msg)); 268 break; 269 } 270 } 271 272 if (conv_ctxt->num_expected == 0 && text == NULL) { 273 xfree(conv_ctxt->prompts); 274 xfree(conv_ctxt->responses); 275 xfree(conv_ctxt); 276 return PAM_SUCCESS; 277 } 278 279 authctxt->method->method_data = (void *) conv_ctxt; 280 281 packet_start(SSH2_MSG_USERAUTH_INFO_REQUEST); 282 packet_put_cstring(""); /* Name */ 283 packet_put_utf8_cstring(text ? text : ""); /* Instructions */ 284 packet_put_cstring(""); /* Language */ 285 packet_put_int(conv_ctxt->num_expected); 286 287 if (text) 288 xfree(text); 289 290 for (i = 0, j = 0; i < num_msg; i++) { 291 int style = PAM_MSG_MEMBER(msg, i, msg_style); 292 293 /* Skip messages which don't need a reply */ 294 if (style != PAM_PROMPT_ECHO_ON && style != PAM_PROMPT_ECHO_OFF) 295 continue; 296 297 conv_ctxt->prompts[j++] = i; 298 packet_put_utf8_cstring(PAM_MSG_MEMBER(msg, i, msg)); 299 packet_put_char(style == PAM_PROMPT_ECHO_ON); 300 } 301 packet_send(); 302 packet_write_wait(); 303 304 /* 305 * Here the dispatch_run() loop is nested. It should be unwound 306 * if keyboard-interactive userauth is abandoned (or restarted; 307 * same thing). 308 * 309 * The condition for breaking out of the nested dispatch_run() loop is 310 * ((got kbd-int info reponse) || (kbd-int abandoned)) 311 * 312 * conv_ctxt->finished is set in either of those cases. 313 * 314 * When abandonment is detected the conv_ctxt->finished is set as 315 * is conv_ctxt->abandoned, causing this function to signal 316 * userauth nested dispatch_run() loop unwinding and to return 317 * PAM_CONV_ERR; 318 */ 319 debug2("Nesting dispatch_run loop"); 320 dispatch_run(DISPATCH_BLOCK, &conv_ctxt->finished, appdata_ptr); 321 debug2("Nested dispatch_run loop exited"); 322 323 if (conv_ctxt->abandoned) { 324 authctxt->unwind_dispatch_loop = 1; 325 xfree(conv_ctxt->prompts); 326 xfree(conv_ctxt->responses); 327 xfree(conv_ctxt); 328 debug("PAM conv function returns PAM_CONV_ERR"); 329 return PAM_CONV_ERR; 330 } 331 332 if (conv_ctxt->num_received == conv_ctxt->num_expected) { 333 *resp = conv_ctxt->responses; 334 xfree(conv_ctxt->prompts); 335 xfree(conv_ctxt); 336 debug("PAM conv function returns PAM_SUCCESS"); 337 return PAM_SUCCESS; 338 } 339 340 debug("PAM conv function returns PAM_CONV_ERR"); 341 xfree(conv_ctxt->prompts); 342 xfree(conv_ctxt->responses); 343 xfree(conv_ctxt); 344 return PAM_CONV_ERR; 345 } 346 347 static void 348 input_userauth_info_response_pam(int type, u_int32_t seqnr, void *ctxt) 349 { 350 Authctxt *authctxt = ctxt; 351 Convctxt *conv_ctxt; 352 unsigned int nresp = 0, rlen = 0, i = 0; 353 char *resp; 354 355 if (authctxt == NULL) 356 fatal("input_userauth_info_response_pam: no authentication context"); 357 358 /* Check for spurious/unexpected info response */ 359 if (method_kbdint.method_data == NULL) { 360 debug("input_userauth_info_response_pam: no method context"); 361 return; 362 } 363 364 conv_ctxt = (Convctxt *) method_kbdint.method_data; 365 366 nresp = packet_get_int(); /* Number of responses. */ 367 debug("got %d responses", nresp); 368 369 370 #if 0 371 if (nresp != conv_ctxt->num_expected) 372 fatal("%s: Received incorrect number of responses " 373 "(expected %d, received %u)", __func__, 374 conv_ctxt->num_expected, nresp); 375 #endif 376 377 if (nresp > 100) 378 fatal("%s: too many replies", __func__); 379 380 for (i = 0; i < nresp && i < conv_ctxt->num_expected ; i++) { 381 int j = conv_ctxt->prompts[i]; 382 383 /* 384 * We assume that ASCII charset is used for password 385 * although the protocol requires UTF-8 encoding for the 386 * password string. Therefore, we don't perform code 387 * conversion for the string. 388 */ 389 resp = packet_get_string(&rlen); 390 if (i < conv_ctxt->num_expected) { 391 conv_ctxt->responses[j].resp_retcode = PAM_SUCCESS; 392 conv_ctxt->responses[j].resp = xstrdup(resp); 393 conv_ctxt->num_received++; 394 } 395 xfree(resp); 396 } 397 398 if (nresp < conv_ctxt->num_expected) 399 fatal("%s: too few replies (%d < %d)", __func__, 400 nresp, conv_ctxt->num_expected); 401 402 /* XXX - This could make a covert channel... */ 403 if (nresp > conv_ctxt->num_expected) 404 debug("Ignoring additional PAM replies"); 405 406 conv_ctxt->finished = 1; 407 408 packet_check_eom(); 409 } 410 411 #if 0 412 int 413 kbdint_pam_abandon_chk(Authctxt *authctxt, Authmethod *method) 414 { 415 if (!method) 416 return 0; /* fatal(), really; it'll happen somewhere else */ 417 418 if (!method->method_data) 419 return 0; 420 421 return 1; 422 } 423 #endif 424 425 void 426 kbdint_pam_abandon(Authctxt *authctxt, Authmethod *method) 427 { 428 Convctxt *conv_ctxt; 429 430 /* 431 * But, if it ever becomes desirable and possible to support 432 * kbd-int userauth abandonment, here's what must be done. 433 */ 434 if (!method) 435 return; 436 437 if (!method->method_data) 438 return; 439 440 conv_ctxt = (Convctxt *) method->method_data; 441 442 /* dispatch_run() loop will exit */ 443 conv_ctxt->abandoned = 1; 444 conv_ctxt->finished = 1; 445 446 /* 447 * The method_data will be free in the corresponding, active 448 * conversation function 449 */ 450 method->method_data = NULL; 451 452 /* update counts that can't be updated elsewhere */ 453 method->abandons++; 454 method->attempts++; 455 456 /* Finally, we cannot re-use the current current PAM handle */ 457 authctxt->pam->h = NULL; /* Let the conv function cleanup */ 458 } 459 #endif