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