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