/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define LASTLOG "/var/adm/lastlog" #define LOGINADMIN "/etc/default/login" #define UNIX_AUTH_DATA "SUNW-UNIX-AUTH-DATA" #define UNIX_AUTHTOK_DATA "SUNW-UNIX-AUTHTOK-DATA" #define NO_PW "*NP*" /* * Function Declarations */ extern int defopen(char *); extern char *defread(char *); extern void setusershell(); extern int _nfssys(int, void *); typedef struct _unix_authtok_data_ { int age_status; }unix_authtok_data; /*ARGSUSED*/ static void unix_cleanup( pam_handle_t *pamh, void *data, int pam_status) { free((unix_authtok_data *)data); } /* * check_for_login_inactivity - Check for login inactivity * */ static int check_for_login_inactivity( uid_t pw_uid, struct spwd *shpwd) { int fdl; struct lastlog ll; int retval; offset_t offset; offset = (offset_t)pw_uid * (offset_t)sizeof (struct lastlog); if ((fdl = open(LASTLOG, O_RDWR|O_CREAT, 0444)) >= 0) { /* * Read the last login (ll) time */ if (llseek(fdl, offset, SEEK_SET) != offset) { syslog(LOG_ERR, "pam_unix_acct: pam_sm_acct_mgmt: " "can't obtain last login info on uid %d " "(uid too large)", pw_uid); return (0); } retval = read(fdl, (char *)&ll, sizeof (ll)); /* Check for login inactivity */ if ((shpwd->sp_inact > 0) && (retval == sizeof (ll)) && ll.ll_time) { /* * account inactive too long. * and no update password set * and no last pwd change date in shadow file * and last pwd change more than inactive time * then account inactive too long and no access. */ if (((time_t)((ll.ll_time / DAY) + shpwd->sp_inact) < DAY_NOW) && (shpwd->sp_lstchg != 0) && (shpwd->sp_lstchg != -1) && ((shpwd->sp_lstchg + shpwd->sp_inact) < DAY_NOW)) { /* * Account inactive for too long */ (void) close(fdl); return (1); } } (void) close(fdl); } return (0); } /* * new_password_check() * * check to see if the user needs to change their password */ static int new_password_check(pw_uid, shpwd, flags) uid_t pw_uid; struct spwd *shpwd; int flags; { time_t now = DAY_NOW; /* * We want to make sure that we change the password only if * passwords are required for the system, the user does not * have a password, AND the user's NULL password can be changed * according to its password aging information */ if ((flags & PAM_DISALLOW_NULL_AUTHTOK) != 0) { if (shpwd->sp_pwdp[0] == '\0') { if ((pw_uid != 0) && ((shpwd->sp_max == -1) || ((time_t)shpwd->sp_lstchg > now) || ((now >= (time_t)(shpwd->sp_lstchg + shpwd->sp_min)) && (shpwd->sp_max >= shpwd->sp_min)))) { return (PAM_NEW_AUTHTOK_REQD); } } } return (PAM_SUCCESS); } /* * perform_passwd_aging_check * - Check for password exipration. */ static int perform_passwd_aging_check( pam_handle_t *pamh, struct spwd *shpwd, int flags) { time_t now = DAY_NOW; int idledays = -1; char *ptr; char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; if (defopen(LOGINADMIN) == 0) { if ((ptr = defread("IDLEWEEKS=")) != NULL) idledays = 7 * atoi(ptr); (void) defopen(NULL); } /* * if (sp_lstchg == 0), the administrator has forced the * user to change his/her passwd */ if (shpwd->sp_lstchg == 0) return (PAM_NEW_AUTHTOK_REQD); /* If password aging is disabled (or min>max), all is well */ if (shpwd->sp_max < 0 || shpwd->sp_max < shpwd->sp_min) return (PAM_SUCCESS); /* Password aging is enabled. See if the password has aged */ if (now < (time_t)(shpwd->sp_lstchg + shpwd->sp_max)) return (PAM_SUCCESS); /* Password has aged. Has it aged more than idledays ? */ if (idledays < 0) /* IDLEWEEKS not configured */ return (PAM_NEW_AUTHTOK_REQD); /* idledays is configured */ if (idledays > 0 && (now < (time_t)(shpwd->sp_lstchg + idledays))) return (PAM_NEW_AUTHTOK_REQD); /* password has aged more that allowed for by IDLEWEEKS */ if (!(flags & PAM_SILENT)) { (void) strlcpy(messages[0], dgettext(TEXT_DOMAIN, "Your password has been expired for too long."), sizeof (messages[0])); (void) strlcpy(messages[1], dgettext(TEXT_DOMAIN, "Please contact the system administrator."), sizeof (messages[0])); (void) __pam_display_msg(pamh, PAM_ERROR_MSG, 2, messages, NULL); } return (PAM_AUTHTOK_EXPIRED); } /* * warn_user_passwd_will_expire - warn the user when the password will * expire. */ static void warn_user_passwd_will_expire( pam_handle_t *pamh, struct spwd shpwd) { time_t now = DAY_NOW; char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE]; time_t days; if ((shpwd.sp_warn > 0) && (shpwd.sp_max > 0) && (now + shpwd.sp_warn) >= (time_t)(shpwd.sp_lstchg + shpwd.sp_max)) { days = (time_t)(shpwd.sp_lstchg + shpwd.sp_max) - now; if (days <= 0) (void) snprintf(messages[0], sizeof (messages[0]), dgettext(TEXT_DOMAIN, "Your password will expire within 24 hours.")); else if (days == 1) (void) snprintf(messages[0], sizeof (messages[0]), dgettext(TEXT_DOMAIN, "Your password will expire in 1 day.")); else (void) snprintf(messages[0], sizeof (messages[0]), dgettext(TEXT_DOMAIN, "Your password will expire in %d days."), (int)days); (void) __pam_display_msg(pamh, PAM_TEXT_INFO, 1, messages, NULL); } } /* * pam_sm_acct_mgmt - main account managment routine. * Returns: module error or specific error on failure */ int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { uid_t pw_uid; char *repository_name = NULL; char *user; attrlist attr_pw[3]; attrlist attr_spw[7]; pwu_repository_t *pwu_rep = PWU_DEFAULT_REP; pwu_repository_t *auth_rep = NULL; int error = PAM_ACCT_EXPIRED; int result; int i; int debug = 0; int server_policy = 0; unix_authtok_data *status; struct spwd shpwd = {NULL, NULL, -1, -1, -1, -1, -1, -1, 0}; for (i = 0; i < argc; i++) { if (strcasecmp(argv[i], "debug") == 0) debug = 1; else if (strcasecmp(argv[i], "server_policy") == 0) server_policy = 1; else if (strcasecmp(argv[i], "nowarn") == 0) { flags = flags | PAM_SILENT; } else { syslog(LOG_ERR, "ACCOUNT:pam_sm_acct_mgmt: illegal option %s", argv[i]); } } if (debug) syslog(LOG_AUTH | LOG_DEBUG, "pam_unix_account: entering pam_sm_acct_mgmt()"); if ((error = pam_get_item(pamh, PAM_USER, (void **)&user)) != PAM_SUCCESS) goto out; if (user == NULL) { error = PAM_USER_UNKNOWN; goto out; } else shpwd.sp_namp = user; if ((error = pam_get_item(pamh, PAM_REPOSITORY, (void **)&auth_rep)) != PAM_SUCCESS) goto out; if (auth_rep == NULL) { pwu_rep = PWU_DEFAULT_REP; } else { if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL) { error = PAM_BUF_ERR; goto out; } pwu_rep->type = auth_rep->type; pwu_rep->scope = auth_rep->scope; pwu_rep->scope_len = auth_rep->scope_len; } /* * First get the password information */ attr_pw[0].type = ATTR_REP_NAME; attr_pw[0].next = &attr_pw[1]; attr_pw[1].type = ATTR_UID; attr_pw[1].next = &attr_pw[2]; attr_pw[2].type = ATTR_PASSWD; attr_pw[2].next = NULL; result = __get_authtoken_attr(user, pwu_rep, attr_pw); if (result == PWU_NOT_FOUND) { error = PAM_USER_UNKNOWN; goto out; } else if (result == PWU_DENIED) { error = PAM_PERM_DENIED; goto out; } else if (result == PWU_NOMEM) { error = PAM_BUF_ERR; goto out; } else if (result != PWU_SUCCESS) { error = PAM_SERVICE_ERR; goto out; } else { repository_name = attr_pw[0].data.val_s; pw_uid = attr_pw[1].data.val_i; shpwd.sp_pwdp = attr_pw[2].data.val_s; } /* * if repository is not files|nis|nisplus, and * user wants server_policy, we don't care * about aging and hence return PAM_IGNORE */ if (server_policy && strcmp(repository_name, "files") != 0 && strcmp(repository_name, "nis") != 0 && strcmp(repository_name, "nisplus") != 0) { error = PAM_IGNORE; goto out; } /* * Now get the aging information */ attr_spw[0].type = ATTR_LSTCHG; attr_spw[0].next = &attr_spw[1]; attr_spw[1].type = ATTR_MIN; attr_spw[1].next = &attr_spw[2]; attr_spw[2].type = ATTR_MAX; attr_spw[2].next = &attr_spw[3]; attr_spw[3].type = ATTR_WARN; attr_spw[3].next = &attr_spw[4]; attr_spw[4].type = ATTR_INACT; attr_spw[4].next = &attr_spw[5]; attr_spw[5].type = ATTR_EXPIRE; attr_spw[5].next = &attr_spw[6]; attr_spw[6].type = ATTR_FLAG; attr_spw[6].next = NULL; result = __get_authtoken_attr(user, pwu_rep, attr_spw); if (result == PWU_SUCCESS) { shpwd.sp_lstchg = attr_spw[0].data.val_i; shpwd.sp_min = attr_spw[1].data.val_i; shpwd.sp_max = attr_spw[2].data.val_i; shpwd.sp_warn = attr_spw[3].data.val_i; shpwd.sp_inact = attr_spw[4].data.val_i; shpwd.sp_expire = attr_spw[5].data.val_i; shpwd.sp_flag = attr_spw[6].data.val_i; } if (debug) { char *pw = "Unix PW"; if (shpwd.sp_pwdp == NULL) pw = "NULL"; else if (strncmp(shpwd.sp_pwdp, LOCKSTRING, sizeof (LOCKSTRING) - 1) == 0) pw = LOCKSTRING; else if (strcmp(shpwd.sp_pwdp, NO_PW) == 0) pw = NO_PW; if (result == PWU_DENIED) { syslog(LOG_AUTH | LOG_DEBUG, "pam_unix_account: %s: permission denied " "to access password aging information. " "Using defaults.", user); } syslog(LOG_AUTH | LOG_DEBUG, "%s Policy:Unix, pw=%s, lstchg=%d, min=%d, max=%d, " "warn=%d, inact=%d, expire=%d", user, pw, shpwd.sp_lstchg, shpwd.sp_min, shpwd.sp_max, shpwd.sp_warn, shpwd.sp_inact, shpwd.sp_expire); } if (pwu_rep != PWU_DEFAULT_REP) { free(pwu_rep); pwu_rep = PWU_DEFAULT_REP; } if (result == PWU_NOT_FOUND) { error = PAM_USER_UNKNOWN; goto out; } else if (result == PWU_NOMEM) { error = PAM_BUF_ERR; goto out; } else if (result != PWU_SUCCESS && result != PWU_DENIED) { error = PAM_SERVICE_ERR; goto out; } /* * Check for locked account */ if (shpwd.sp_pwdp != NULL && strncmp(shpwd.sp_pwdp, LOCKSTRING, sizeof (LOCKSTRING) - 1) == 0) { char *service; char *rhost = NULL; (void) pam_get_item(pamh, PAM_SERVICE, (void **)&service); (void) pam_get_item(pamh, PAM_RHOST, (void **)&rhost); __pam_log(LOG_AUTH | LOG_NOTICE, "pam_unix_account: %s attempting to validate locked " "account %s from %s", service, user, (rhost != NULL && *rhost != '\0') ? rhost : "local host"); error = PAM_PERM_DENIED; goto out; } /* * Check for account expiration */ if (shpwd.sp_expire > 0 && (time_t)shpwd.sp_expire < DAY_NOW) { error = PAM_ACCT_EXPIRED; goto out; } /* * Check for excessive login account inactivity */ if (check_for_login_inactivity(pw_uid, &shpwd)) { error = PAM_PERM_DENIED; goto out; } /* * Check to see if the user needs to change their password */ if (error = new_password_check(pw_uid, &shpwd, flags)) { goto out; } /* * Check to make sure password aging information is okay */ if ((error = perform_passwd_aging_check(pamh, &shpwd, flags)) != PAM_SUCCESS) { goto out; } /* * Finally, warn the user if their password is about to expire. */ if (!(flags & PAM_SILENT)) { warn_user_passwd_will_expire(pamh, shpwd); } /* * All done, return Success */ error = PAM_SUCCESS; out: { int pam_res; unix_authtok_data *authtok_data; if (debug) { syslog(LOG_AUTH | LOG_DEBUG, "pam_unix_account: %s: %s", (user == NULL)?"NULL":user, pam_strerror(pamh, error)); } if (repository_name) free(repository_name); if (pwu_rep != PWU_DEFAULT_REP) free(pwu_rep); if (shpwd.sp_pwdp) { (void) memset(shpwd.sp_pwdp, 0, strlen(shpwd.sp_pwdp)); free(shpwd.sp_pwdp); } /* store the password aging status in the pam handle */ pam_res = pam_get_data( pamh, UNIX_AUTHTOK_DATA, (const void **)&authtok_data); if ((status = (unix_authtok_data *)calloc (1, sizeof (unix_authtok_data))) == NULL) { return (PAM_BUF_ERR); } if (pam_res == PAM_SUCCESS) (void) memcpy(status, authtok_data, sizeof (unix_authtok_data)); status->age_status = error; if (pam_set_data(pamh, UNIX_AUTHTOK_DATA, status, unix_cleanup) != PAM_SUCCESS) { free(status); return (PAM_SERVICE_ERR); } } return (error); }