1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00
cde/cde/lib/pam/pam_modules/unix/unix_update_authtok.c
2018-06-27 21:59:18 -06:00

1541 lines
40 KiB
C

/*
* CDE - Common Desktop Environment
*
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
*
* These libraries and programs are free software; you can
* redistribute them and/or modify them under the terms of the GNU
* Lesser General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* These libraries and programs are distributed in the hope that
* they will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with these libraries and programs; if not, write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
/* $XConsortium: unix_update_authtok.c /main/3 1996/05/09 04:35:27 drk $ */
/*
* Copyright (c) 1992-1995, by Sun Microsystems, Inc.
* All rights reserved.
*/
#ident "@(#)unix_update_authtok.c 1.29 96/02/02 SMI"
#include "unix_headers.h"
#ifdef PAM_SECURE_RPC
static bool_t verify_rpc_passwd(char *, char *);
#endif
#ifdef PAM_NISPLUS
static int get_nispluscred(pam_handle_t *, char *, char *, int,
struct passwd *, nis_result **);
#endif
static int verify_old_passwd(pam_handle_t *, int, int, char *,
struct spwd *, char *, char *, char *,
int, void *, uid_t, int *, int *, int, int);
static int turn_on_default_aging(pam_handle_t *, int, char *, int);
static int get_newpasswd(pam_handle_t *, char *, int, char *,
int, uid_t, int, int, int, int);
static int circ(char *, char *);
static int triviality_checks(pam_handle_t *, uid_t,
char *, char *, int, int, int);
/*
* __update_authtok():
* To change authentication token.
*
* This function calls ck_perm() to check the caller's
* permission. If the check succeeds, it will then call
* verify_old_passwd() to validate the old password and password
* aging information. If verify_old_passwd() succeeds, get_newpasswd()
* will then be called to get and check the user's new passwd.
* Last, update_authtok_<repository>() will be called to change the user's
* password to the new password.
*
* All temporary password buffers allocate PAM_MAX_RESP_SIZE bytes.
* This is because the longest value (and hence password) that can be
* returned by a PAM conv function is PAM_MAX_RESP_SIZE bytes.
*
* The "passwd" command bypasses PAM and calls this function directly
* if the -r (repository) or -D (domain) options are specified
* on the command line.
*/
int
__update_authtok(
pam_handle_t *pamh,
int flags,
int repository,
char *domain,
int argc,
const char **argv)
{
int retcode;
int i, j, c, done = 0;
time_t salt;
char saltc[2]; /* crypt() takes 2 char */
/* string as a salt */
char *pw = NULL; /* encrypted new password */
struct passwd *pwd = NULL;
struct spwd *shpwd = NULL;
char *usrname = NULL;
char *prognamep = NULL;
int re; /* single repository */
int newpass = 0; /* flag: user entered new passwd */
int found_user = 0; /* flag: user found in a repository */
int privileged = 0; /* privileged nisplus user */
uid_t uid; /* real uid of calling process */
char orpcpw[PAM_MAX_RESP_SIZE] = {'\0'}; /* old rpc passwd */
char opwbuf[PAM_MAX_RESP_SIZE] = {'\0'}; /* old passwd */
char pwbuf[PAM_MAX_RESP_SIZE] = {'\0'}; /* new passwd */
char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
int debug = 0;
int nowarn = 0;
int try_first_pass = 0;
int use_first_pass = 0;
#ifdef PAM_NISPLUS
nis_result *passwd_res;
nis_result *cred_res = NULL; /* cred entry nis_list result */
#else
void *passwd_res = NULL, *cred_res = NULL;
#endif
/*
* Holds the old encrypted secret key obtained from the server.
*/
#ifdef PAM_SECURE_RPC
char curcryptsecret[HEXKEYBYTES+KEYCHECKSUMSIZE+1];
#else
char curcryptsecret[48+16+1];
#endif /* PAM_SECURE_RPC */
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0)
debug = 1;
else if (strcmp(argv[i], "nowarn") == 0)
nowarn = 1;
else if (strcmp(argv[i], "try_first_pass") == 0)
try_first_pass = 1;
else if (strcmp(argv[i], "use_first_pass") == 0)
use_first_pass = 1;
else
syslog(LOG_ERR,
"UNIX chauthtok(): illegal module option %s",
argv[i]);
}
if (debug) {
syslog(LOG_DEBUG, "unix_sm_chauthtok(): update passwords");
syslog(LOG_DEBUG, "unix_sm_chauthtok: %s",
repository_to_string(repository));
syslog(LOG_DEBUG, "unix_sm_chauthtok: uid = %d, euid = %d",
getuid(), geteuid());
}
if ((retcode = pam_get_item(pamh, PAM_SERVICE, (void **)&prognamep))
!= PAM_SUCCESS ||
(retcode = pam_get_item(pamh, PAM_USER, (void **)&usrname))
!= PAM_SUCCESS) {
goto out;
}
/*
* get_ns() will consult nsswitch file and set the repository
* accordingly.
* If specified repository is not defined in switch file, a warning
* is printed, e.g. -r nis while "passwd: files, nis+" is defined
* in switch file.
*/
repository = get_ns(pamh, repository, debug, nowarn);
if (repository == -1) {
retcode = PAM_AUTHTOK_ERR;
goto out;
}
if (debug)
syslog(LOG_DEBUG,
"unix pam_sm_chauthtok(): repository: %s after get_ns()",
repository_to_string(repository));
/* if we still get PAM_REP_DEFAULT after calling get_ns(), error! */
if (repository == PAM_REP_DEFAULT) {
retcode = PAM_AUTHTOK_ERR;
goto out;
}
#ifdef PAM_NISPLUS
/* nispasswd or -r nisplus */
if ((repository & PAM_REP_NISPLUS) && ((domain == NULL) ||
(*domain == '\0')))
domain = nis_local_directory();
#endif
/*
* XXX:
* For this module to work, real UID must be user's UID,
* effective UID must be 0.
*
* If the effective UID is not 0, then we can not open
* the shadow file (in ck_perm()).
* If the real UID is not the user's UID, then we have no
* idea who called passwd. We need to know who invoked
* passwd because special priviliges are given to the root
* user (root can change anyone's password in FILES).
*
* If the service is login, rlogin, telnet, or dtlogin, then
* we query the password file for the uid of the user.
*
* Typically a service module should not use the service name
* in this fashion. The service name should only be used when
* printing out error or text information.
*/
if ((strcmp(prognamep, "passwd") == 0)) {
uid = getuid();
} else {
/*
* Service is login, rlogin, etc.
*/
struct passwd pd;
char pwd_buf[1024];
if (getpwnam_r(usrname, &pd, pwd_buf, sizeof (pwd_buf)) == NULL)
return (PAM_USER_UNKNOWN);
uid = pd.pw_uid;
}
/*
* We need to call ck_perm, verify_old_passwd, and get_newpasswd
* for each repository that the user may be in (nisplus, nis, files).
* The first time thru the loop handles NISPLUS.
* The second time thru the loop handles NIS.
* The third time thru handles FILES.
*
* We want to update remote repositories first. If they fail
* we won't update local FILES - thus leaving a consistent
* state across the system.
*/
for (j = 1; j <= 3; j++) {
switch (j) {
case 1:
/* first time thru loop, NISPLUS */
re = PAM_REP_NISPLUS;
break;
case 2:
/* 2nd time thru loop, NIS */
re = PAM_REP_NIS;
#ifdef PAM_NISPLUS
if (repository & PAM_REP_NISPLUS) {
/*
* Nisplus case sets euid to user.
* Set it back to root now.
*/
seteuid(0);
setreuid(uid, 0);
}
#endif /* PAM_NISPLUS */
break;
case 3:
/* 3rd time thru loop, FILES */
re = PAM_REP_FILES;
done = 1;
break;
}
if ((repository & re) == 0) {
/* the repository is not specified in nsswitch */
if (j == 3 && !found_user) {
if (!nowarn) {
sprintf(messages[0],
PAM_MSG(pamh, 60,
"%s%s: %s does not exist"),
prognamep, UNIX_MSG, usrname);
(void) __pam_display_msg(
pamh,
PAM_ERROR_MSG, 1,
messages, NULL);
}
retcode = PAM_USER_UNKNOWN;
goto out;
}
continue;
}
retcode = ck_perm(pamh, re,
(char *)domain, &pwd, &shpwd, &privileged,
(void **)&passwd_res, uid, debug, nowarn);
switch (retcode) {
case PAM_SUCCESS:
found_user = 1;
break;
case PAM_USER_UNKNOWN:
/*
* Although the repository was listed in
* nsswitch.conf, the user may not be in that
* repository -- check all repositories before
* quitting
*/
if (done) {
if (found_user == 1) {
retcode = PAM_SUCCESS;
goto out;
} else {
if (!nowarn) {
sprintf(messages[0],
PAM_MSG(pamh, 60,
"%s%s: %s does not exist"),
prognamep,
UNIX_MSG,
usrname);
(void) __pam_display_msg(
pamh,
PAM_ERROR_MSG, 1,
messages, NULL);
}
goto out;
}
}
/* user not in this repository -- check the others */
continue;
default:
/* all other errors */
goto out;
}
#ifdef PAM_NISPLUS
if (IS_NISPLUS(repository)) {
char *p; /* nispasswd */
/*
* We must use an authenticated handle to get the cred
* table information for the user we want to modify the
* cred info for. If we can't even read that info, we
* definitely wouldn't have modify permission. Well..
*/
retcode = get_nispluscred(pamh, domain,
prognamep, repository, pwd, &cred_res);
if (retcode != PAM_SUCCESS)
goto out;
if ((cred_res == NULL) ||
(cred_res->status != NIS_SUCCESS)) {
if (IS_OPWCMD(repository)) {
if (!nowarn) {
(void) sprintf(messages[0],
PAM_MSG(pamh, 61,
"%s%s: user must have NIS+ credential"),
prognamep, UNIX_MSG);
(void) __pam_display_msg(
pamh,
PAM_ERROR_MSG, 1,
messages, NULL);
}
retcode = PAM_PERM_DENIED;
goto out;
}
/* continue even if there is no cred */
} else {
if ((p = ENTRY_VAL(cred_res->objects.objects_val,
4)) == NULL) {
(void) strncpy(curcryptsecret, NULLSTRING,
sizeof (curcryptsecret));
} else {
(void) strncpy(curcryptsecret, p, ENTRY_LEN(
cred_res->objects.objects_val, 4));
}
}
}
#endif /* PAM_NISPLUS */
/* verify the old password */
retcode = verify_old_passwd(pamh,
re, repository, (char *)domain, shpwd,
opwbuf, orpcpw, curcryptsecret,
privileged, (void *)cred_res, uid,
&try_first_pass, &use_first_pass,
debug, nowarn);
if (retcode != PAM_SUCCESS)
goto out;
/*
* verify_old_passwd() may have modified try_first_pass
* and use_first_pass. This is to avoid get_newpasswd()
* from using the new password with use/try_first_pass
* when the old password failed.
*
* Once we get the new passwd for remote repository
* (nis or nis+), we don't need to prompt again for
* local repository (files).
* The default behavior is to keep passwds in sync.
* However, we check old passwds in both repositories.
*/
if (!newpass) {
retcode = get_newpasswd(pamh, pwbuf, re,
opwbuf, privileged, uid,
try_first_pass, use_first_pass, debug, nowarn);
if (retcode != PAM_SUCCESS)
goto out;
/* we have a new passwd from user */
newpass = 1;
}
/* Construct salt, then encrypt the new password */
(void) time((time_t *)&salt);
salt += (long)getpid();
saltc[0] = salt & 077;
saltc[1] = (salt >> 6) & 077;
for (i = 0; i < 2; i++) {
c = saltc[i] + '.';
if (c > '9') c += 7;
if (c > 'Z') c += 6;
saltc[i] = c;
}
pw = crypt(pwbuf, saltc);
/* update remote repositories first */
/* make sure user exists before we update the repository */
#ifdef PAM_NIS
if (IS_NIS(re) && (pwd != NULL)) {
retcode = update_authtok_nis(pamh,
"passwd", &pw, opwbuf, pwbuf,
pwd, privileged, nowarn);
if (retcode != PAM_SUCCESS) {
free_passwd_structs(pwd, shpwd);
goto out;
}
}
#endif /* PAM_NIS */
#ifdef PAM_NISPLUS
/*
* nis+ update will choose different protocol depending
* on the opwcmd flag.
*/
if (IS_NISPLUS(re) && (pwd != NULL)) {
/* nis+ needs clear versions of old/new passwds */
if (orpcpw[0] == '\0') {
if (strlen(opwbuf) + 1 <= PAM_MAX_RESP_SIZE) {
(void) strncpy(orpcpw, opwbuf,
strlen(opwbuf)+1);
} else {
retcode = PAM_BUF_ERR;
free_passwd_structs(pwd, shpwd);
goto out;
}
}
retcode = update_authtok_nisplus(pamh,
(char *)domain, "passwd", &pw,
opwbuf, orpcpw, pwbuf,
IS_OPWCMD(re) ? 1 : 0, pwd, curcryptsecret,
privileged, passwd_res, cred_res,
debug, nowarn);
if (retcode != PAM_SUCCESS) {
free_passwd_structs(pwd, shpwd);
goto out;
}
}
#endif /* PAM_NISPLUS */
if (IS_FILES(re) && (pwd != NULL)) {
retcode = update_authtok_file(pamh,
"passwd", &pw, pwd,
privileged, nowarn);
if (retcode != PAM_SUCCESS) {
free_passwd_structs(pwd, shpwd);
goto out;
}
}
free_passwd_structs(pwd, shpwd);
} /* for */
retcode = PAM_SUCCESS;
out:
memset(pwbuf, 0, sizeof (pwbuf));
memset(orpcpw, 0, PAM_MAX_RESP_SIZE);
memset(opwbuf, 0, PAM_MAX_RESP_SIZE);
#ifdef PAM_NISPLUS
if (cred_res)
nis_freeresult(cred_res);
#endif
/*
* If invoked from login, rlogin, etc,
* passwd module exits with:
* uid = 0 euid = 0
*
* If invoked from passwd command,
* passwd module exits with:
* uid = usrname UID euid = 0
*/
if (strcmp("passwd", prognamep) != 0) {
setuid(0);
}
/* audit_passwd_main10(retval); */
return (retcode);
}
/*
* verify_old_passwd():
* To verify user old password. It also checks the
* password aging information to verify that user is authorized
* to change password.
*/
static int
verify_old_passwd(pamh, repository, real_rep, domain,
shpwd, opwbuf, orpcpw, curcryptsecret, privileged, cred_res,
uid, try_first_pass, use_first_pass, debug, nowarn)
pam_handle_t *pamh;
int repository; /* repositories in nsswitch.conf */
int real_rep; /* current repository trying to update passwd */
char *domain; /* NIS+ domain */
struct spwd *shpwd;
char *opwbuf; /* old password */
char *orpcpw; /* old RPC password */
int privileged; /* NIS+ privileged user? */
char *curcryptsecret; /* old encrypted secret key */
void *cred_res; /* cred entry nis_list result */
uid_t uid;
int *try_first_pass;
int *use_first_pass;
int debug;
int nowarn;
{
int now;
int done = 0;
int retcode;
char *prognamep;
char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
char *pswd = NULL; /* user input clear passwd */
char *pw; /* encrypted passwd */
pid_t pid;
int w;
char prompt[PAM_MAX_MSG_SIZE];
/* Set if password already exists as pam handle item */
int passwd_already_set = 0;
char *saved_passwd = NULL;
if (debug) {
syslog(LOG_DEBUG,
"verify_old_passwd(): repository is %s",
repository_to_string(repository));
syslog(LOG_DEBUG,
" try_first_pass = %d, use_first_pass = %d",
*try_first_pass, *use_first_pass);
}
if ((retcode = pam_get_item(pamh, PAM_SERVICE, (void **)&prognamep))
!= PAM_SUCCESS)
return (retcode);
/*
* Make sure repository is in range, and there is a shadow passwd.
* If the repository is FILES, only prompt for old password
* if user is not the root user.
*/
if (shpwd->sp_pwdp[0] &&
((uid != 0 && IS_FILES(repository)) ||
IS_NIS(repository) ||
IS_NISPLUS(repository))) {
/*
* For nis+, prompt for passwd even if it's a privileged user.
* However to maintain the old nispasswd behavior, skip
* this part if it's a privileged user.
*/
if (IS_NISPLUS(repository) && IS_OPWCMD(real_rep) &&
privileged == 1) {
opwbuf[0] = '\0';
goto aging;
}
if (opwbuf && opwbuf[0] != '\0') {
/* user already input passwd */
pw = crypt(opwbuf, shpwd->sp_pwdp);
if (strcmp(shpwd->sp_pwdp, pw) == 0) {
/* same old passwd: no need to check again */
goto aging;
}
}
/*
* See if there's a password already saved in the
* pam handle. If no, then if we prompt for a password,
* it will be saved in the handle by __pam_get_authtok().
* We will want to clear this password from the handle
* if it is incorrect before we return.
*/
pam_get_item(pamh, PAM_OLDAUTHTOK, (void **)&saved_passwd);
if (saved_passwd != NULL)
passwd_already_set = 1;
/*
* The only time this loop will iterate more than once is
* if try_first_pass is set and the first password is not
* correct so we must prompt the user for a new one.
*/
while (!done) {
if (*try_first_pass || *use_first_pass) {
__pam_get_authtok(pamh, PAM_HANDLE,
PAM_OLDAUTHTOK, PASSWORD_LEN, 0, &pswd);
} else {
memset(prompt, 0, PAM_MAX_MSG_SIZE);
if (IS_NIS(repository))
strcpy(prompt, PAM_MSG(pamh, 62,
"Enter login(NIS) password: "));
else if (IS_NISPLUS(repository))
strcpy(prompt, PAM_MSG(pamh, 63,
"Enter login(NIS+) password: "));
else
strcpy(prompt, PAM_MSG(pamh, 64,
"Enter login password: "));
retcode = __pam_get_authtok(pamh, PAM_PROMPT,
PAM_OLDAUTHTOK, PASSWORD_LEN,
prompt, &pswd);
if (retcode != PAM_SUCCESS)
return (retcode);
}
if (pswd == NULL || pswd[0] == '\0') {
if (*try_first_pass) {
/*
* This means that the module has
* try_first_pass set, but there was no
* previous module that ever prompted
* for a password. Go back and prompt
* for a password.
*/
*try_first_pass = 0;
continue;
}
(void) sprintf(messages[0],
PAM_MSG(pamh, 65, "%s%s: Sorry."),
prognamep, UNIX_MSG);
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
if (pswd)
free(pswd);
*use_first_pass = 0;
return (PAM_AUTHTOK_ERR);
} else {
(void) strncpy(opwbuf, pswd, PAM_MAX_RESP_SIZE);
pw = crypt(opwbuf, shpwd->sp_pwdp);
memset(pswd, 0, strlen(pswd));
free(pswd);
}
/*
* To really want to maintain the behavior of yppasswd,
* add a check of PAM_OPWCMD here for PAM_REP_NIS.
* Allow the user to change passwd to the master
* consecutively. It doesn't check the passwd
* at the server that it's bound to. (which may not be
* in sync with the master all the time).
*/
/*
* Privileged user may update someone else's passwd.
* It doesn't make sense to compare admin's passwd to
* the regular user's passwd.
*/
if ((privileged == 0) &&
strcmp(pw, shpwd->sp_pwdp) != 0) {
if (*try_first_pass) {
/*
* We just attempted the first
* password and it failed. Go
* back and prompt for the old
* password.
*/
*try_first_pass = 0;
continue;
} else {
(void) sprintf(messages[0],
PAM_MSG(pamh, 66,
"%s%s: Sorry, wrong passwd"),
prognamep, UNIX_MSG);
(void) __pam_display_msg(pamh,
PAM_ERROR_MSG, 1, messages,
NULL);
*use_first_pass = 0;
/* clear bad passwd */
if (passwd_already_set == 0)
pam_set_item(pamh,
PAM_OLDAUTHTOK,
NULL);
return (PAM_PERM_DENIED);
}
}
/* At this point the old password has been verified. */
done = 1;
} /* while */
#ifdef PAM_NISPLUS
/* nispasswd or -r nisplus */
if ((privileged == 0) && IS_NISPLUS(repository) &&
((nis_result *)cred_res != NULL) &&
((nis_result *)cred_res)->status == NIS_SUCCESS) {
/*
* At this point only check if the password matches the
* one in passwd table. If it doesn't, don't even bother
* about seeing if it can decrypt the secretkey in cred table.
* If it does, attempt to decrypt the old secretkey with it.
* This only makes sense when users have credentials.
*/
if (verify_rpc_passwd(opwbuf, curcryptsecret)
== FALSE) {
/* failed to decrypt the secret key */
/* ask for Old secure RPC passwd */
char *tmpkeybuf = NULL;
(void) sprintf(messages[0], PAM_MSG(pamh, 67,
"This password differs from your secure RPC password."));
(void) __pam_display_msg(pamh, PAM_TEXT_INFO,
1, messages, NULL);
if ((retcode = __pam_get_authtok(pamh,
PAM_PROMPT, 0, PASSWORD_LEN,
PAM_MSG(pamh, 68, "Please enter your old Secure RPC password: "),
&tmpkeybuf)) != PAM_SUCCESS) {
return (retcode);
}
if (verify_rpc_passwd(tmpkeybuf, curcryptsecret)
== FALSE) {
(void) sprintf(messages[0],
PAM_MSG(pamh, 69,
"This password does not decrypt your secure RPC credentials. Try again:"));
(void) __pam_display_msg(
pamh, PAM_ERROR_MSG, 1,
messages, NULL);
memset(tmpkeybuf, 0, strlen(tmpkeybuf));
free(tmpkeybuf);
if ((retcode = __pam_get_authtok(pamh,
PAM_PROMPT, 0, PASSWORD_LEN,
PAM_MSG(pamh, 70, "Please enter your old Secure RPC password: "),
&tmpkeybuf)) != PAM_SUCCESS) {
return (retcode);
}
if (verify_rpc_passwd(tmpkeybuf,
curcryptsecret) == FALSE) {
(void) sprintf(messages[0],
PAM_MSG(pamh, 65,
"%s%s: Sorry."),
prognamep, UNIX_MSG);
(void) __pam_display_msg(
pamh,
PAM_ERROR_MSG, 1,
messages, NULL);
memset(tmpkeybuf, 0,
strlen(tmpkeybuf));
free(tmpkeybuf);
return (PAM_PERM_DENIED);
}
}
(void) strcpy(orpcpw, tmpkeybuf);
memset(tmpkeybuf, 0, strlen(tmpkeybuf));
free(tmpkeybuf);
}
/* continue to construct a new passwd */
}
#endif /* PAM_NISPLUS */
} else {
opwbuf[0] = '\0';
orpcpw[0] = '\0';
}
aging:
/* YP doesn't support aging. */
if (IS_NIS(repository))
return (PAM_SUCCESS);
/* password age checking applies */
if (shpwd->sp_max != -1 && shpwd->sp_lstchg != 0) {
/* If password aging is turned on and the password last */
/* change date is set */
now = DAY_NOW;
if (shpwd->sp_lstchg <= now) {
if (((uid != 0 && IS_FILES(repository)) ||
(privileged == 0 && IS_NISPLUS(repository))) &&
(now < shpwd->sp_lstchg + shpwd->sp_min)) {
if (!nowarn) {
(void) sprintf(messages[0],
PAM_MSG(pamh, 71,
"%s%s: Sorry: less than %ld days since the last change."),
prognamep, UNIX_MSG, shpwd->sp_min);
(void) __pam_display_msg(
pamh,
PAM_ERROR_MSG, 1,
messages, NULL);
}
return (PAM_PERM_DENIED);
}
if (shpwd->sp_min > shpwd->sp_max) {
if ((IS_FILES(repository) && uid != 0) ||
(IS_NISPLUS(repository) &&
(privileged == 0))) {
if (!nowarn) {
(void) sprintf(messages[0],
PAM_MSG(pamh, 72,
"%s%s: You may not change this password."),
prognamep, UNIX_MSG);
(void) __pam_display_msg(
pamh,
PAM_ERROR_MSG, 1,
messages, NULL);
}
return (PAM_PERM_DENIED);
}
}
}
} else {
if (shpwd->sp_lstchg == 0 &&
shpwd->sp_max > 0 || shpwd->sp_min > 0) {
/* If password aging is turned on */
return (PAM_SUCCESS);
} else {
/* aging not turned on */
/* so turn on passwd for user with default values */
/*
* Because we need to run the current process as
* regular user for nis+ request, we don't want
* to become root at this point. However, in order
* to turn on default aging for files, we have
* to have root privilege. Thus, we let the child
* have root privilege while the current process
* remains as regular user process. Refer to ck_perm()
* for related uid manipulation.
*/
switch (pid = fork()) {
case -1:
if (!nowarn) {
(void) sprintf(messages[0],
PAM_MSG(pamh, 73,
"%s%s: can't create process"),
prognamep, UNIX_MSG);
(void) __pam_display_msg(
pamh, PAM_ERROR_MSG, 1,
messages, NULL);
}
return (PAM_SYSTEM_ERR);
case 0: /* child */
(void) seteuid(0);
retcode = turn_on_default_aging(pamh,
repository, domain, debug);
exit(retcode);
default:
/* wait for child */
while ((w = (int)waitpid(pid, &retcode, 0))
!= pid && w != -1)
;
return ((w == -1) ? w : retcode);
}
}
}
return (PAM_SUCCESS);
}
/*
* turn_on_default_aging():
* Turn on the default password aging
*/
static int
turn_on_default_aging(pamh, repository, domain, debug)
pam_handle_t *pamh;
int repository;
char *domain;
int debug;
{
char value[PAM_MAX_ATTR_SIZE];
char *char_p;
char *set_attribute[PAM_MAX_NUM_ATTR];
int retcode;
int k;
int mindate; /* password aging information */
int maxdate; /* password aging information */
int warndate; /* password aging information */
/* can't set network policy locally */
if (IS_NISPLUS(repository))
return (PAM_SUCCESS);
/* We only process local files. Skip anything else. */
if (!IS_FILES(repository))
return (PAM_PERM_DENIED);
k = 0;
/*
* Open "/etc/default/passwd" file,
* if password administration file can't be opened
* use built in defaults.
*/
if ((defopen(PWADMIN)) != 0) { /* M005 start */
mindate = MINWEEKS * 7;
maxdate = MAXWEEKS * 7;
warndate = WARNWEEKS * 7;
} else {
/* get minimum date before password can be changed */
if ((char_p = defread("MINWEEKS=")) == NULL)
mindate = 7 * MINWEEKS;
else {
mindate = 7 * atoi(char_p);
if (mindate < 0)
mindate = 7 * MINWEEKS;
}
/* get warn date before password is expired */
if ((char_p = defread("WARNWEEKS=")) == NULL)
warndate = 7 * WARNWEEKS;
else {
warndate = 7 * atoi(char_p);
if (warndate < 0)
warndate = 7 * WARNWEEKS;
}
/* get max date that password is valid */
if ((char_p = defread("MAXWEEKS=")) == NULL)
maxdate = 7 * MAXWEEKS;
else if ((maxdate = atoi(char_p)) == -1) {
mindate = -1;
warndate = -1;
} else if (maxdate < -1)
maxdate = 7 * MAXWEEKS;
else
maxdate *= 7;
/* close defaults file */
defopen(NULL);
}
if (debug)
syslog(LOG_DEBUG,
"turn: maxdate == %d, mindate == %d\n", maxdate, mindate);
/* set up the attribute/value pairs, then call pam_set_authtokattr() */
/* to change the attribute values */
(void) sprintf(value, "%d", maxdate);
setup_attr(set_attribute, k++, "AUTHTOK_MAXAGE=", value);
(void) sprintf(value, "%d", mindate);
setup_attr(set_attribute, k++, "AUTHTOK_MINAGE=", value);
(void) sprintf(value, "%d", warndate);
setup_attr(set_attribute, k++, "AUTHTOK_WARNDATE=", value);
setup_attr(set_attribute, k, NULL, NULL);
retcode = __set_authtoken_attr(pamh, (const char **) &set_attribute[0],
repository, domain, 0, NULL);
free_setattr(set_attribute);
return (retcode);
}
/*
* get_newpasswd():
* Get user's new password. It also does the syntax check for
* the new password.
*/
static int
get_newpasswd(pamh, pwbuf, re, opwbuf, privileged, uid,
try_first_pass, use_first_pass, debug, nowarn)
pam_handle_t *pamh;
char pwbuf[PAM_MAX_RESP_SIZE]; /* new password */
int re; /* current repository trying to update passwd */
char *opwbuf; /* old password */
int privileged; /* NIS+ privileged user */
uid_t uid; /* UID of user that invoked passwd cmd */
int try_first_pass;
int use_first_pass;
int debug;
int nowarn;
{
int insist = 0; /* # times new passwd fails checks */
int count = 0; /* # times old/new passwds do not match */
int done = 0; /* continue to prompt until done */
char *usrname;
char *prognamep;
int retcode;
char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
char *pswd;
/* Set if password already exists as pam handle item */
int passwd_already_set = 0;
char *saved_passwd = NULL;
if (debug)
syslog(LOG_DEBUG,
"get_newpasswd(), try_first_pass = %d, use_first_pass = %d",
try_first_pass, use_first_pass);
if ((retcode = pam_get_item(pamh, PAM_SERVICE, (void **)&prognamep))
!= PAM_SUCCESS ||
(retcode = pam_get_item(pamh, PAM_USER, (void **)&usrname))
!= PAM_SUCCESS)
return (retcode);
pam_get_item(pamh, PAM_AUTHTOK, (void **)&saved_passwd);
if (saved_passwd != NULL)
passwd_already_set = 1;
while (insist < MAX_CHANCES && count < MAX_CHANCES && !done) {
/* clear a bad "new password" if it was saved */
if (passwd_already_set == 0)
pam_set_item(pamh, PAM_AUTHTOK, NULL);
/*
* If use_first_pass failed, then return.
* If try_first_pass failed, note it so that we
* do not try it again (when retyping in the new
* password).
*/
if (use_first_pass && (insist != 0 || count != 0))
return (PAM_AUTHTOK_ERR);
if (try_first_pass && (insist != 0 || count != 0))
try_first_pass = 0;
if ((try_first_pass || use_first_pass) &&
insist == 0 && count == 0) {
/*
* Try the new password entered to the first password
* module in the stack (try/use_first_pass option).
* If it already failed (insist > 0 || count > 0) then
* prompt if option is try_first_pass.
*/
if ((retcode = __pam_get_authtok(pamh, PAM_HANDLE,
PAM_AUTHTOK, PASSWORD_LEN, 0,
&pswd)) != PAM_SUCCESS) {
return (retcode);
}
} else {
if ((retcode = __pam_get_authtok(pamh, PAM_PROMPT,
PAM_AUTHTOK, PASSWORD_LEN,
PAM_MSG(pamh, 74, "New password: "),
&pswd)) != PAM_SUCCESS) {
return (retcode);
}
}
if (pswd == NULL) {
if (try_first_pass) {
/*
* This means that the module has
* try_first_pass set, but there was no
* previous module that ever prompted for
* a password. Go back and prompt for
* a password.
*/
count++;
continue;
}
(void) sprintf(messages[0],
PAM_MSG(pamh, 65, "%s%s: Sorry."),
prognamep, UNIX_MSG);
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
if (pswd)
free(pswd);
/* clear a bad "new password" if it was saved */
if (passwd_already_set == 0)
pam_set_item(pamh, PAM_AUTHTOK, NULL);
return (PAM_AUTHTOK_ERR);
} else {
memset(pwbuf, 0, PAM_MAX_RESP_SIZE);
(void) strncpy(pwbuf, pswd, PAM_MAX_RESP_SIZE);
memset(pswd, 0, PASSWORD_LEN);
free(pswd);
}
if ((retcode = triviality_checks(pamh, uid, opwbuf,
pwbuf, re, privileged, nowarn)) != PAM_SUCCESS) {
if (retcode == PAM_AUTHTOK_ERR) {
insist++;
continue;
} else {
/* clear bad "new password" if saved */
if (passwd_already_set == 0)
pam_set_item(pamh, PAM_AUTHTOK, NULL);
return (retcode);
}
}
/*
* New password passed triviality check. Reset
* insist to 0 in case user does not retype password
* correctly and we have to loop all over.
*/
insist = 0;
/* Now make sure user re-types in passwd correctly. */
if (try_first_pass || use_first_pass) {
/*
* Try the new password entered to the first password
* module in the stack (try/use_first_pass option).
*/
if ((retcode = __pam_get_authtok(pamh, PAM_HANDLE,
PAM_AUTHTOK, PASSWORD_LEN, NULL,
&pswd)) != PAM_SUCCESS) {
/* clear bad "new password" if saved */
if (passwd_already_set == 0)
pam_set_item(pamh,
PAM_AUTHTOK, NULL);
return (retcode);
}
} else {
if ((retcode = __pam_get_authtok(pamh,
PAM_PROMPT, 0, PASSWORD_LEN,
PAM_MSG(pamh, 75, "Re-enter new password: "),
&pswd)) != PAM_SUCCESS) {
/* clear bad "new password" if saved */
if (passwd_already_set == 0)
pam_set_item(pamh,
PAM_AUTHTOK, NULL);
return (retcode);
}
}
if ((strlen(pswd) != strlen(pwbuf)) ||
(strncmp(pswd, pwbuf, strlen(pwbuf)))) {
memset(pswd, 0, strlen(pswd));
free(pswd);
if (++count >= MAX_CHANCES) {
(void) sprintf(messages[0],
PAM_MSG(pamh, 76,
"%s%s: Too many tries; try again later"),
prognamep, UNIX_MSG);
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
/* clear bad "new password" if saved */
if (passwd_already_set == 0)
pam_set_item(pamh, PAM_AUTHTOK, NULL);
return (PAM_PERM_DENIED);
} else {
(void) sprintf(messages[0], PAM_MSG(pamh, 77,
"%s%s: They don't match; try again."),
prognamep, UNIX_MSG);
(void) __pam_display_msg(
pamh, PAM_ERROR_MSG, 1,
messages, NULL);
}
/* audit_passwd_main9(); */
continue;
}
/* password matched - exit loop and return PAM_SUCCESS */
done = 1;
} /* while loop */
/*
* If we exit the while loop with too
* many attempts return error.
*/
if (insist >= MAX_CHANCES) {
if (debug)
syslog(LOG_DEBUG,
"get_newpasswd: failed trivial check");
(void) sprintf(messages[0], PAM_MSG(pamh, 78,
"%s%s: Too many failures - try later."),
prognamep, UNIX_MSG);
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
/* clear bad "new password" if saved */
if (passwd_already_set == 0)
pam_set_item(pamh, PAM_AUTHTOK, NULL);
return (PAM_PERM_DENIED);
}
/* new password passed triviality check and re-type check */
return (PAM_SUCCESS);
}
static int
triviality_checks(pam_handle_t *pamh, uid_t uid,
char *opwbuf, char *pwbuf, int re,
int privileged, int nowarn)
{
int retcode = 0;
int tmpflag = 0;
int flags = 0;
char *p, *o;
int c;
int i, j, k;
int bare_minima = MINLENGTH;
char *char_p;
char *usrname, *prognamep;
char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
int pwlen = strlen(pwbuf);
if ((retcode = pam_get_item(pamh, PAM_USER, (void **)&usrname))
!= PAM_SUCCESS ||
(retcode = pam_get_item(pamh, PAM_SERVICE, (void **)&prognamep))
!= PAM_SUCCESS)
return (retcode);
/*
* Make sure new password is long enough if not privileged user
*/
/*
* Open "/etc/default/passwd" file,
* if password administration file can't be opened
* use built in defaults.
*/
if ((defopen(PWADMIN)) == 0) {
/* get minimum length of password */
if ((char_p = defread("PASSLENGTH=")) != NULL)
bare_minima = atoi(char_p);
/* close defaults file */
defopen(NULL);
}
if (bare_minima < MINLENGTH)
bare_minima = MINLENGTH;
else if (bare_minima > MAXLENGTH)
bare_minima = MAXLENGTH;
if (!((uid == 0 && IS_FILES(re)) ||
(privileged && IS_NISPLUS(re))) && (pwlen < bare_minima)) {
if (!nowarn) {
(void) sprintf(messages[0], PAM_MSG(pamh, 79,
"%s%s: Password too short - must be at least %d characters."),
prognamep, UNIX_MSG, bare_minima);
retcode = __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
if (retcode != PAM_SUCCESS) {
return (retcode);
}
}
return (PAM_AUTHTOK_ERR);
}
/* Check the circular shift of the logonid */
if (!((uid == 0 && IS_FILES(re)) ||
(privileged && IS_NISPLUS(re))) && circ(usrname, pwbuf)) {
if (!nowarn) {
(void) sprintf(messages[0], PAM_MSG(pamh, 80,
"%s%s: Password cannot be circular shift of logonid."),
prognamep, UNIX_MSG);
retcode = __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
if (retcode != PAM_SUCCESS) {
return (retcode);
}
}
return (PAM_AUTHTOK_ERR);
}
/*
* Ensure passwords contain at least two alpha characters
* and one numeric or special character
*/
flags = 0;
tmpflag = 0;
p = pwbuf;
if (!((uid == 0 && IS_FILES(re)) ||
(privileged && IS_NISPLUS(re)))) {
c = *p++;
while (c != '\0') {
if (isalpha(c) && tmpflag)
flags |= 1;
else if (isalpha(c) && !tmpflag) {
flags |= 2;
tmpflag = 1;
} else if (isdigit(c))
flags |= 4;
else
flags |= 8;
c = *p++;
}
/*
* 7 = lca, lca, num
* 7 = lca, uca, num
* 7 = uca, uca, num
* 11 = lca, lca, spec
* 11 = lca, uca, spec
* 11 = uca, uca, spec
* 15 = spec, num, alpha, alpha
*/
if (flags != 7 && flags != 11 && flags != 15) {
if (!nowarn) {
(void) sprintf(messages[0], PAM_MSG(pamh, 81,
"%s%s: The first %d characters of the password"),
prognamep, UNIX_MSG, bare_minima);
(void) sprintf(messages[1], PAM_MSG(pamh, 82,
"must contain at least two alphabetic characters and at least"));
(void) sprintf(messages[2], PAM_MSG(pamh, 83,
"one numeric or special character."));
retcode = __pam_display_msg(pamh,
PAM_ERROR_MSG, 3, messages, NULL);
if (retcode != PAM_SUCCESS) {
return (retcode);
}
}
return (PAM_AUTHTOK_ERR);
}
}
if (!((uid == 0 && IS_FILES(re)) ||
(privileged && IS_NISPLUS(re)))) {
p = pwbuf;
o = opwbuf;
if (pwlen >= strlen(opwbuf)) {
i = pwlen;
k = pwlen - strlen(opwbuf);
} else {
i = strlen(opwbuf);
k = strlen(opwbuf) - pwlen;
}
for (j = 1; j <= i; j++)
if (*p++ != *o++)
k++;
if (k < 3) {
if (!nowarn) {
(void) sprintf(messages[0], PAM_MSG(pamh, 84,
"%s%s: Passwords must differ by at least 3 positions"),
prognamep, UNIX_MSG);
retcode = __pam_display_msg(pamh,
PAM_ERROR_MSG, 1, messages, NULL);
if (retcode != PAM_SUCCESS) {
return (retcode);
}
}
return (PAM_AUTHTOK_ERR);
}
}
return (PAM_SUCCESS);
}
/*
* circ():
* This function return 1 if string "t" is a circular shift of
* string "s", else it returns 0.
*/
static int
circ(s, t)
char *s, *t;
{
char c, *p, *o, *r, buff[25], ubuff[25], pubuff[25];
int i, j, k, l, m;
m = 2;
i = strlen(s);
o = &ubuff[0];
for (p = s; c = *p++; *o++ = c)
if (islower(c))
c = toupper(c);
*o = '\0';
o = &pubuff[0];
for (p = t; c = *p++; *o++ = c)
if (islower(c))
c = toupper(c);
*o = '\0';
p = &ubuff[0];
while (m--) {
for (k = 0; k <= i; k++) {
c = *p++;
o = p;
l = i;
r = &buff[0];
while (--l)
*r++ = *o++;
*r++ = c;
*r = '\0';
p = &buff[0];
if (strcmp(p, pubuff) == 0)
return (1);
}
p = p + i;
r = &ubuff[0];
j = i;
while (j--)
*--p = *r++;
}
return (0);
}
#ifdef PAM_SECURE_RPC
/*
* verify that the given passwd decrypts the secret key
*/
static bool_t
verify_rpc_passwd(oldpass, curcryptsecret)
char *oldpass;
char *curcryptsecret;
{
char oldsecret[HEXKEYBYTES + KEYCHECKSUMSIZE + 1];
(void) memcpy(oldsecret, curcryptsecret,
HEXKEYBYTES + KEYCHECKSUMSIZE + 1);
if (xdecrypt(oldsecret, oldpass) &&
(memcmp(oldsecret, &(oldsecret[HEXKEYBYTES]),
KEYCHECKSUMSIZE) == 0))
return (TRUE); /* successfully decrypted and */
/* the decrypted key is correct */
return (FALSE);
}
#endif /* PAM_SECURE_RPC */
#ifdef PAM_NISPLUS
/*
* Sets the global variable cred_res
*
* First, find the LOCAL credentials
* use the user's uid from the passwd entry pointed to by
* nisplus_pwd as the key to search on.
* Next, obtain the user's home domain
* this is gotten from the cname of the LOCAL credentials
* Finally, get the user's DES credentials
* use the cname of the LOCAL credentials for the search key
* and perform the search in the user's home domain
*
* Note: PAM_SUCCESS does not mean credentials were found,
* only that this routine did what it could without
* internal errors.
* Check cred_res for actual results.
*/
static int
get_nispluscred(pamh, nisdomain, prognamep, repository, nisplus_pwd, cred_res)
pam_handle_t *pamh;
char *nisdomain;
char *prognamep;
int repository;
struct passwd *nisplus_pwd;
nis_result **cred_res;
{
char messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
char buf[NIS_MAXNAMELEN+1];
struct nis_result *local_res = NULL; /* cred local nis_list result */
nis_name cred_domain;
char *local_cname;
if (nisplus_pwd == NULL) {
/* No NIS+ passwd entry, can't resolve credentials. */
return (PAM_SUCCESS);
}
/*
* The credentials may not be in the local domain.
* Find the the LOCAL entry first to get the correct
* cname to search for DES credentials.
*/
/*
* strlen("[auth_name=nnnnnnnnnnnnnnnn,auth_type=LOCAL],.") + null
* + "." = 50 (allow for long UID in future release)
*/
if ((50 + strlen(nisdomain) + PKTABLELEN) >
(size_t) NIS_MAXNAMELEN) {
syslog(LOG_ERR, "%s: Name too long", prognamep);
*cred_res = NULL;
return (PAM_AUTHTOK_ERR);
}
(void) sprintf(buf, "[auth_name=%d,auth_type=LOCAL],%s.%s",
nisplus_pwd->pw_uid, PKTABLE, nisdomain);
if (buf[strlen(buf) - 1] != '.')
(void) strcat(buf, ".");
local_res = nis_list(buf,
USE_DGRAM+FOLLOW_LINKS+FOLLOW_PATH+MASTER_ONLY,
NULL, NULL);
if ((local_res == NULL) || (local_res->status != NIS_SUCCESS)) {
nis_freeresult(local_res);
if (IS_OPWCMD(repository)) {
(void) sprintf(messages[0], PAM_MSG(pamh, 85,
"nispasswd: user must have LOCAL credential"));
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
*cred_res = NULL;
return (PAM_CRED_INSUFFICIENT);
}
*cred_res = NULL;
return (PAM_SUCCESS);
}
local_cname = ENTRY_VAL(NIS_RES_OBJECT(local_res), 0);
if (local_cname == NULL) {
nis_freeresult(local_res);
if (IS_OPWCMD(repository)) {
(void) sprintf(messages[0], PAM_MSG(pamh, 85,
"%s: invalid LOCAL credential"), prognamep);
(void) __pam_display_msg(pamh, PAM_ERROR_MSG,
1, messages, NULL);
*cred_res = NULL;
return (PAM_CRED_ERR);
}
*cred_res = NULL;
return (PAM_SUCCESS);
}
cred_domain = nis_domain_of(local_cname);
/*
* strlen("[cname=,auth_type=DES],.") + null = 25
*/
if ((25 + strlen(local_cname) + strlen(cred_domain)
+ PKTABLELEN) > (size_t) NIS_MAXNAMELEN) {
syslog(LOG_ERR, "%s: Name too long", prognamep);
nis_freeresult(local_res);
*cred_res = NULL;
return (PAM_AUTHTOK_ERR);
}
(void) sprintf(buf, "[cname=%s,auth_type=DES],%s.%s",
local_cname, PKTABLE, cred_domain);
nis_freeresult(local_res);
*cred_res = nis_list(buf,
USE_DGRAM+FOLLOW_LINKS+FOLLOW_PATH+MASTER_ONLY,
NULL, NULL);
return (PAM_SUCCESS);
}
#endif /* PAM_NISPLUS */