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