1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  23  * Use is subject to license terms.
  24  */
  25 
  26 
  27 #include <sys/types.h>
  28 #include <sys/wait.h>
  29 #include <sys/stat.h>
  30 #include <fcntl.h>
  31 #include <stdlib.h>
  32 #include <security/pam_appl.h>
  33 #include <security/pam_modules.h>
  34 #include <security/pam_impl.h>
  35 #include <syslog.h>
  36 #include <pwd.h>
  37 #include <shadow.h>
  38 #include <lastlog.h>
  39 #include <ctype.h>
  40 #include <unistd.h>
  41 #include <stdlib.h>
  42 #include <stdio.h>
  43 #include <libintl.h>
  44 #include <signal.h>
  45 #include <thread.h>
  46 #include <synch.h>
  47 #include <errno.h>
  48 #include <time.h>
  49 #include <string.h>
  50 #include <crypt.h>
  51 #include <assert.h>
  52 #include <deflt.h>
  53 #include <libintl.h>
  54 #include <passwdutil.h>
  55 
  56 #define LOGINADMIN      "/etc/default/login"
  57 #define UNIX_AUTH_DATA          "SUNW-UNIX-AUTH-DATA"
  58 #define UNIX_AUTHTOK_DATA       "SUNW-UNIX-AUTHTOK-DATA"
  59 
  60 /*
  61  * Function Declarations
  62  */
  63 extern void             setusershell();
  64 extern int              _nfssys(int, void *);
  65 
  66 typedef struct _unix_authtok_data_ {
  67         int age_status;
  68 }unix_authtok_data;
  69 
  70 /*ARGSUSED*/
  71 static void
  72 unix_cleanup(
  73         pam_handle_t *pamh,
  74         void *data,
  75         int pam_status)
  76 {
  77         free((unix_authtok_data *)data);
  78 }
  79 
  80 /*
  81  * check_for_login_inactivity   - Check for login inactivity
  82  *
  83  */
  84 
  85 static int
  86 check_for_login_inactivity(
  87         uid_t           pw_uid,
  88         struct  spwd    *shpwd)
  89 {
  90         int             fdl;
  91         int             retval;
  92         offset_t        offset;
  93         time_t          lltime = 0;
  94 
  95         if (!(shpwd->sp_inact > 0))
  96                 return (0);
  97 
  98         if ((fdl = open(_PATH_LASTLOG, O_RDONLY)) >= 0) {
  99                 struct lastlog  ll;
 100                 offset = (offset_t)(pw_uid * sizeof (struct lastlog));
 101                 /*
 102                  * Read the last login (ll) time
 103                  */
 104                 if (llseek(fdl, offset, SEEK_SET) != offset) {
 105                         __pam_log(LOG_AUTH | LOG_ERR,
 106                             "pam_unix_acct: pam_sm_acct_mgmt: "
 107                             "can't obtain last login info on uid %d "
 108                             "(uid too large)", pw_uid);
 109                         (void) close(fdl);
 110                         return (0);
 111                 }
 112 
 113                 retval = read(fdl, (char *)&ll, sizeof (ll));
 114 
 115                 if (retval == sizeof (ll))
 116                         lltime = ll.ll_time;
 117 
 118                 (void) close(fdl);
 119         }
 120         /* Check for login inactivity */
 121         if (lltime) {
 122                 /*
 123                  * account inactive too long.
 124                  * and no update password set
 125                  * and no last pwd change date in shadow file
 126                  * and last pwd change more than inactive time
 127                  * then account inactive too long and no access.
 128                  */
 129                 if ((((lltime / DAY) + shpwd->sp_inact) < DAY_NOW) &&
 130                     (shpwd->sp_lstchg != 0) &&
 131                     (shpwd->sp_lstchg != -1) &&
 132                     ((shpwd->sp_lstchg + shpwd->sp_inact) < DAY_NOW)) {
 133                         /*
 134                          * Account inactive for too long
 135                          */
 136                         return (1);
 137                 }
 138         }
 139         return (0);
 140 }
 141 
 142 /*
 143  * new_password_check()
 144  *
 145  * check to see if the user needs to change their password
 146  */
 147 
 148 static int
 149 new_password_check(shpwd, flags)
 150         struct  spwd    *shpwd;
 151         int             flags;
 152 {
 153         time_t  now  = DAY_NOW;
 154 
 155         /*
 156          * We want to make sure that we change the password only if
 157          * passwords are required for the system, the user does not
 158          * have a password, AND the user's NULL password can be changed
 159          * according to its password aging information
 160          */
 161 
 162         if ((flags & PAM_DISALLOW_NULL_AUTHTOK) != 0) {
 163                 if (shpwd->sp_pwdp[0] == '\0') {
 164                         if (((shpwd->sp_max == -1) ||
 165                                 ((time_t)shpwd->sp_lstchg > now) ||
 166                                 ((now >= (time_t)(shpwd->sp_lstchg +
 167                                                         shpwd->sp_min)) &&
 168                                 (shpwd->sp_max >= shpwd->sp_min)))) {
 169                                         return (PAM_NEW_AUTHTOK_REQD);
 170                         }
 171                 }
 172         }
 173         return (PAM_SUCCESS);
 174 }
 175 
 176 /*
 177  * perform_passwd_aging_check
 178  *              - Check for password exipration.
 179  */
 180 static  int
 181 perform_passwd_aging_check(
 182         pam_handle_t *pamh,
 183         struct  spwd    *shpwd,
 184         int     flags)
 185 {
 186         time_t  now = DAY_NOW;
 187         int     idledays = -1;
 188         char    *ptr;
 189         char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
 190         void    *defp;
 191 
 192 
 193         if ((defp = defopen_r(LOGINADMIN)) != NULL) {
 194                 if ((ptr = defread_r("IDLEWEEKS=", defp)) != NULL)
 195                         idledays = 7 * atoi(ptr);
 196                 defclose_r(defp);
 197         }
 198 
 199         /*
 200          * if (sp_lstchg == 0), the administrator has forced the
 201          * user to change his/her passwd
 202          */
 203         if (shpwd->sp_lstchg == 0)
 204                 return (PAM_NEW_AUTHTOK_REQD);
 205 
 206         /* If password aging is disabled (or min>max), all is well */
 207         if (shpwd->sp_max < 0 || shpwd->sp_max < shpwd->sp_min)
 208                 return (PAM_SUCCESS);
 209 
 210         /* Password aging is enabled. See if the password has aged */
 211         if (now < (time_t)(shpwd->sp_lstchg + shpwd->sp_max))
 212                 return (PAM_SUCCESS);
 213 
 214         /* Password has aged. Has it aged more than idledays ? */
 215         if (idledays < 0)                    /* IDLEWEEKS not configured */
 216                 return (PAM_NEW_AUTHTOK_REQD);
 217 
 218         /* idledays is configured */
 219         if (idledays > 0 && (now < (time_t)(shpwd->sp_lstchg + idledays)))
 220                 return (PAM_NEW_AUTHTOK_REQD);
 221 
 222         /* password has aged more that allowed for by IDLEWEEKS */
 223         if (!(flags & PAM_SILENT)) {
 224                 (void) strlcpy(messages[0], dgettext(TEXT_DOMAIN,
 225                     "Your password has been expired for too long."),
 226                     sizeof (messages[0]));
 227                 (void) strlcpy(messages[1], dgettext(TEXT_DOMAIN,
 228                     "Please contact the system administrator."),
 229                     sizeof (messages[0]));
 230                 (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 2, messages,
 231                     NULL);
 232         }
 233         return (PAM_AUTHTOK_EXPIRED);
 234 }
 235 
 236 /*
 237  * warn_user_passwd_will_expire - warn the user when the password will
 238  *                                        expire.
 239  */
 240 
 241 static void
 242 warn_user_passwd_will_expire(
 243         pam_handle_t *pamh,
 244         struct  spwd shpwd)
 245 {
 246         time_t  now     = DAY_NOW;
 247         char    messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
 248         time_t  days;
 249 
 250 
 251         if ((shpwd.sp_warn > 0) && (shpwd.sp_max > 0) &&
 252             (now + shpwd.sp_warn) >= (time_t)(shpwd.sp_lstchg + shpwd.sp_max)) {
 253                 days = (time_t)(shpwd.sp_lstchg + shpwd.sp_max) - now;
 254                 if (days <= 0)
 255                         (void) snprintf(messages[0],
 256                             sizeof (messages[0]),
 257                             dgettext(TEXT_DOMAIN,
 258                             "Your password will expire within 24 hours."));
 259                 else if (days == 1)
 260                         (void) snprintf(messages[0],
 261                             sizeof (messages[0]),
 262                             dgettext(TEXT_DOMAIN,
 263                             "Your password will expire in 1 day."));
 264                 else
 265                         (void) snprintf(messages[0],
 266                             sizeof (messages[0]),
 267                             dgettext(TEXT_DOMAIN,
 268                             "Your password will expire in %d days."),
 269                             (int)days);
 270 
 271                 (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages,
 272                     NULL);
 273         }
 274 }
 275 
 276 /*
 277  * pam_sm_acct_mgmt     -       main account managment routine.
 278  *                        Returns: module error or specific error on failure
 279  */
 280 
 281 int
 282 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
 283 {
 284         uid_t                   pw_uid;
 285         char                    *repository_name = NULL;
 286         char                    *user;
 287         attrlist                attr_pw[3];
 288         attrlist                attr_spw[7];
 289         pwu_repository_t        *pwu_rep = PWU_DEFAULT_REP;
 290         pwu_repository_t        *auth_rep = NULL;
 291         int                     error = PAM_ACCT_EXPIRED;
 292         int                     result;
 293         int                     i;
 294         int                     debug = 0;
 295         int                     server_policy = 0;
 296         unix_authtok_data       *status;
 297         struct  spwd            shpwd = {NULL, NULL,
 298                                         -1, -1, -1, -1, -1, -1, 0};
 299 
 300         for (i = 0; i < argc; i++) {
 301                 if (strcasecmp(argv[i], "debug") == 0)
 302                         debug = 1;
 303                 else if (strcasecmp(argv[i], "server_policy") == 0)
 304                         server_policy = 1;
 305                 else if (strcasecmp(argv[i], "nowarn") == 0) {
 306                         flags = flags | PAM_SILENT;
 307                 } else {
 308                         __pam_log(LOG_AUTH | LOG_ERR,
 309                             "ACCOUNT:pam_sm_acct_mgmt: illegal option %s",
 310                             argv[i]);
 311                 }
 312         }
 313 
 314         if (debug)
 315                 __pam_log(LOG_AUTH | LOG_DEBUG,
 316                     "pam_unix_account: entering pam_sm_acct_mgmt()");
 317 
 318         if ((error = pam_get_item(pamh, PAM_USER, (void **)&user))
 319             != PAM_SUCCESS)
 320                 goto out;
 321 
 322         if (user == NULL) {
 323                 error = PAM_USER_UNKNOWN;
 324                 goto out;
 325         } else
 326                 shpwd.sp_namp = user;
 327 
 328         if ((error = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep))
 329             != PAM_SUCCESS)
 330                 goto out;
 331 
 332         if (auth_rep == NULL) {
 333                 pwu_rep = PWU_DEFAULT_REP;
 334         } else {
 335                 if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) {
 336                         error = PAM_BUF_ERR;
 337                         goto out;
 338                 }
 339                 pwu_rep->type = auth_rep->type;
 340                 pwu_rep->scope = auth_rep->scope;
 341                 pwu_rep->scope_len = auth_rep->scope_len;
 342         }
 343 
 344         /*
 345          * First get the password information
 346          */
 347         attr_pw[0].type =  ATTR_REP_NAME;       attr_pw[0].next = &attr_pw[1];
 348         attr_pw[1].type =  ATTR_UID;            attr_pw[1].next = &attr_pw[2];
 349         attr_pw[2].type =  ATTR_PASSWD;         attr_pw[2].next = NULL;
 350         result = __get_authtoken_attr(user, pwu_rep, attr_pw);
 351 
 352         if (result == PWU_NOT_FOUND) {
 353                 error = PAM_USER_UNKNOWN;
 354                 goto out;
 355         } else if (result == PWU_DENIED) {
 356                 error = PAM_PERM_DENIED;
 357                 goto out;
 358         } else if (result == PWU_NOMEM) {
 359                 error = PAM_BUF_ERR;
 360                 goto out;
 361         } else if (result != PWU_SUCCESS) {
 362                 error = PAM_SERVICE_ERR;
 363                 goto out;
 364         } else {
 365                 repository_name = attr_pw[0].data.val_s;
 366                 pw_uid = attr_pw[1].data.val_i;
 367                 shpwd.sp_pwdp = attr_pw[2].data.val_s;
 368         }
 369 
 370         /*
 371          * if repository is not files|nis, and user wants server_policy,
 372          * we don't care about aging and hence return PAM_IGNORE
 373          */
 374         if (server_policy &&
 375             strcmp(repository_name, "files") != 0 &&
 376             strcmp(repository_name, "nis") != 0) {
 377                 error = PAM_IGNORE;
 378                 goto out;
 379         }
 380 
 381         /*
 382          * Now get the aging information
 383          */
 384         attr_spw[0].type =  ATTR_LSTCHG;        attr_spw[0].next = &attr_spw[1];
 385         attr_spw[1].type =  ATTR_MIN;           attr_spw[1].next = &attr_spw[2];
 386         attr_spw[2].type =  ATTR_MAX;           attr_spw[2].next = &attr_spw[3];
 387         attr_spw[3].type =  ATTR_WARN;          attr_spw[3].next = &attr_spw[4];
 388         attr_spw[4].type =  ATTR_INACT;         attr_spw[4].next = &attr_spw[5];
 389         attr_spw[5].type =  ATTR_EXPIRE;        attr_spw[5].next = &attr_spw[6];
 390         attr_spw[6].type =  ATTR_FLAG;          attr_spw[6].next = NULL;
 391 
 392         result = __get_authtoken_attr(user, pwu_rep, attr_spw);
 393         if (result == PWU_SUCCESS) {
 394                 shpwd.sp_lstchg = attr_spw[0].data.val_i;
 395                 shpwd.sp_min = attr_spw[1].data.val_i;
 396                 shpwd.sp_max = attr_spw[2].data.val_i;
 397                 shpwd.sp_warn = attr_spw[3].data.val_i;
 398                 shpwd.sp_inact = attr_spw[4].data.val_i;
 399                 shpwd.sp_expire = attr_spw[5].data.val_i;
 400                 shpwd.sp_flag = attr_spw[6].data.val_i;
 401         }
 402 
 403         if (debug) {
 404                 char *pw = "Unix PW";
 405 
 406                 if (shpwd.sp_pwdp == NULL)
 407                         pw = "NULL";
 408                 else if (strncmp(shpwd.sp_pwdp, LOCKSTRING,
 409                     sizeof (LOCKSTRING) - 1) == 0)
 410                         pw = LOCKSTRING;
 411                 else if (strcmp(shpwd.sp_pwdp, NOPWDRTR) == 0)
 412                         pw = NOPWDRTR;
 413 
 414                 if (result ==  PWU_DENIED) {
 415                         __pam_log(LOG_AUTH | LOG_DEBUG,
 416                             "pam_unix_account: %s: permission denied "
 417                             "to access password aging information. "
 418                             "Using defaults.", user);
 419                 }
 420 
 421                 __pam_log(LOG_AUTH | LOG_DEBUG,
 422                     "%s Policy:Unix, pw=%s, lstchg=%d, min=%d, max=%d, "
 423                     "warn=%d, inact=%d, expire=%d",
 424                     user, pw, shpwd.sp_lstchg, shpwd.sp_min, shpwd.sp_max,
 425                     shpwd.sp_warn, shpwd.sp_inact, shpwd.sp_expire);
 426         }
 427 
 428         if (pwu_rep != PWU_DEFAULT_REP) {
 429                 free(pwu_rep);
 430                 pwu_rep = PWU_DEFAULT_REP;
 431         }
 432 
 433         if (result == PWU_NOT_FOUND) {
 434                 error = PAM_USER_UNKNOWN;
 435                 goto out;
 436         } else if (result == PWU_NOMEM) {
 437                 error = PAM_BUF_ERR;
 438                 goto out;
 439         } else if (result != PWU_SUCCESS && result != PWU_DENIED) {
 440                 error = PAM_SERVICE_ERR;
 441                 goto out;
 442         }
 443 
 444         /*
 445          * Check for locked account
 446          */
 447         if (shpwd.sp_pwdp != NULL &&
 448             strncmp(shpwd.sp_pwdp, LOCKSTRING, sizeof (LOCKSTRING) - 1) == 0) {
 449                 char *service;
 450                 char *rhost = NULL;
 451 
 452                 (void) pam_get_item(pamh, PAM_SERVICE, (void **)&service);
 453                 (void) pam_get_item(pamh, PAM_RHOST, (void **)&rhost);
 454                 __pam_log(LOG_AUTH | LOG_NOTICE,
 455                     "pam_unix_account: %s attempting to validate locked "
 456                     "account %s from %s",
 457                     service, user,
 458                     (rhost != NULL && *rhost != '\0') ? rhost : "local host");
 459                 error = PAM_PERM_DENIED;
 460                 goto out;
 461         }
 462 
 463         /*
 464          * Check for NULL password and, if so, see if such is allowed
 465          */
 466         if (shpwd.sp_pwdp[0] == '\0' &&
 467             (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0) {
 468                 char *service;
 469                 char *rhost = NULL;
 470 
 471                 (void) pam_get_item(pamh, PAM_SERVICE, (void **)&service);
 472                 (void) pam_get_item(pamh, PAM_RHOST, (void **)&rhost);
 473 
 474                 __pam_log(LOG_AUTH | LOG_NOTICE,
 475                     "pam_unix_account: %s: empty password not allowed for "
 476                     "account %s from %s", service, user,
 477                     (rhost != NULL && *rhost != '\0') ? rhost : "local host");
 478                 error = PAM_PERM_DENIED;
 479                 goto out;
 480         }
 481 
 482         /*
 483          * Check for account expiration
 484          */
 485         if (shpwd.sp_expire > 0 &&
 486             (time_t)shpwd.sp_expire < DAY_NOW) {
 487                 error = PAM_ACCT_EXPIRED;
 488                 goto out;
 489         }
 490 
 491         /*
 492          * Check for excessive login account inactivity
 493          */
 494         if (check_for_login_inactivity(pw_uid, &shpwd)) {
 495                 error = PAM_PERM_DENIED;
 496                 goto out;
 497         }
 498 
 499         /*
 500          * Check to see if the user needs to change their password
 501          */
 502         if (error = new_password_check(&shpwd, flags)) {
 503                 goto out;
 504         }
 505 
 506         /*
 507          * Check to make sure password aging information is okay
 508          */
 509         if ((error = perform_passwd_aging_check(pamh, &shpwd, flags))
 510             != PAM_SUCCESS) {
 511                 goto out;
 512         }
 513 
 514         /*
 515          * Finally, warn the user if their password is about to expire.
 516          */
 517         if (!(flags & PAM_SILENT)) {
 518                 warn_user_passwd_will_expire(pamh, shpwd);
 519         }
 520 
 521         /*
 522          * All done, return Success
 523          */
 524         error = PAM_SUCCESS;
 525 
 526 out:
 527 
 528         {
 529                 int pam_res;
 530                 unix_authtok_data *authtok_data;
 531 
 532                 if (debug) {
 533                         __pam_log(LOG_AUTH | LOG_DEBUG,
 534                             "pam_unix_account: %s: %s",
 535                             (user == NULL)?"NULL":user,
 536                             pam_strerror(pamh, error));
 537                 }
 538 
 539                 if (repository_name)
 540                         free(repository_name);
 541                 if (pwu_rep != PWU_DEFAULT_REP)
 542                         free(pwu_rep);
 543                 if (shpwd.sp_pwdp) {
 544                         (void) memset(shpwd.sp_pwdp, 0, strlen(shpwd.sp_pwdp));
 545                         free(shpwd.sp_pwdp);
 546                 }
 547 
 548                 /* store the password aging status in the pam handle */
 549                 pam_res = pam_get_data(pamh, UNIX_AUTHTOK_DATA,
 550                     (const void **)&authtok_data);
 551 
 552                 if ((status = (unix_authtok_data *)calloc(1,
 553                     sizeof (unix_authtok_data))) == NULL) {
 554                         return (PAM_BUF_ERR);
 555                 }
 556 
 557                 if (pam_res == PAM_SUCCESS)
 558                         (void) memcpy(status, authtok_data,
 559                             sizeof (unix_authtok_data));
 560 
 561                 status->age_status = error;
 562                 if (pam_set_data(pamh, UNIX_AUTHTOK_DATA, status, unix_cleanup)
 563                     != PAM_SUCCESS) {
 564                         free(status);
 565                         return (PAM_SERVICE_ERR);
 566                 }
 567         }
 568 
 569         return (error);
 570 }