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