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