passwd.c revision b816ddf83939c2b433da956720fad32dfb172096
/*
* 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
* 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.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/* Copyright (c) 1987, 1988 Microsoft Corporation */
/* All Rights Reserved */
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* passwd is a program whose sole purpose is to manage
* the password file, map, or table. It allows system administrator
* to add, change and display password attributes.
* Non privileged user can change password or display
* password attributes which corresponds to their login name.
*/
#include <stdio.h>
#include <pwd.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <locale.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_impl.h>
#include <syslog.h>
#include <userdefs.h>
#include <passwdutil.h>
#include <nss_dbdefs.h>
#include <deflt.h>
#include <bsm/adt_event.h>
/*
* flags indicate password attributes to be modified
*/
/* password changes */
/*
* exit code
*/
#define SUCCESS 0 /* succeeded */
/*
* define error messages
*/
#define MSG_NP "Permission denied"
#define MSG_BS "Invalid combination of options"
#define MSG_NV "Invalid argument to option"
#define MSG_AD "Password aging is disabled"
#define MSG_RS "Cannot change from restricted shell %s\n"
#define MSG_NM "Out of memory."
#define MSG_UNACCEPT "%s is unacceptable as a new shell\n"
#define MSG_UNAVAIL "warning: %s is unavailable on this machine\n"
#define MSG_COLON "':' is not allowed.\n"
#define MSG_MAXLEN "Maximum number of characters allowed is %d."
#define MSG_CONTROL "Control characters are not allowed.\n"
#define MSG_SHELL_UNCHANGED "Login shell unchanged.\n"
#define MSG_GECOS_UNCHANGED "Finger information unchanged.\n"
#define MSG_DIR_UNCHANGED "Homedir information unchanged.\n"
#define MSG_NAME "\nName [%s]: "
#define MSG_HOMEDIR "\nHome Directory [%s]: "
#define MSG_OLDSHELL "Old shell: %s\n"
#define MSG_NEWSHELL "New shell: "
#define MSG_AGAIN "\nPlease try again\n"
#define MSG_INPUTHDR "Default values are printed inside of '[]'.\n" \
"To accept the default, type <return>.\n" \
"To have a blank entry, type the word 'none'.\n"
#define MSG_UNKNOWN "%s: User unknown: %s\n"
#define MSG_ACCOUNT_EXP "User account has expired: %s\n"
#define MSG_AUTHTOK_EXP "Your password has been expired for too long.\n" \
"Please contact the system administrator.\n"
#define MSG_NIS_HOMEDIR "-h does not apply to NIS"
#define MSG_CUR_PASS "Enter existing login password: "
#define MSG_CUR_PASS_UNAME "Enter %s's existing login password: "
#define MSG_SUCCESS "%s: password information changed for %s\n"
#define MSG_SORRY "%s: Sorry, wrong passwd\n"
#define MSG_INFO "%s: Changing password for %s\n"
/*
* return code from ckarg() routine
*/
#define FAIL -1
/*
* defind password file name
*/
#define MAX_INPUT_LEN 512
#define DEF_ATTEMPTS 3
/* Number of characters in that make up an encrypted password (for now) */
#define NUMCP 13
#ifdef DEBUG
#else
#define dprintf1(w, x)
#endif
extern int optind;
static int pam_retval = PAM_SUCCESS;
static char *prognamep;
static long maxdate; /* password aging information */
static int passwd_conv(int, struct pam_message **,
struct pam_response **, void *);
static char *usrname; /* user whose attribute we update */
static pam_repository_t auth_rep;
static pwu_repository_t repository;
/*
* Function Declarations
*/
extern nis_name nis_local_directory(void);
extern void setusershell(void);
extern char *getusershell(void);
extern void endusershell(void);
static void rusage(void);
static int ckuid(void);
static int get_namelist(pwu_repository_t, char ***, int *);
static int get_namelist_files(char ***, int *);
static int get_namelist_nisplus(char ***, int *);
static void display_attr(char *, attrlist *);
static void attrlist_reorder(attrlist **);
static char *getresponse(char *);
/*
* main():
* The main routine will call ckarg() to parse the command line
* arguments and call the appropriate functions to perform the
* tasks specified by the arguments. It allows system
* administrator to add, change and display password attributes.
* Non privileged user can change password or display
* password attributes which corresponds to their login name.
*/
int
{
int flag;
char **namelist;
int num_user;
int i;
char *input;
int tries = 1;
int updated_reps;
++prognamep;
else
repository.scope_len = 0;
/* initialization for variables, set locale and textdomain */
i = 0;
flag = 0;
#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */
#endif
(void) textdomain(TEXT_DOMAIN);
/*
* ckarg() parses the arguments. In case of an error,
* it sets the retval and returns FAIL (-1).
*/
if (argc < 1) {
else {
rusage();
}
} else if (flag == 0) {
/*
* If flag is zero, change passwd.
* Otherwise, it will display or
* modify password aging attributes
*/
usrname);
}
} else
!= PAM_SUCCESS) {
}
}
(void) passwd_exit(retval);
if (num_user == 0) {
}
i = 0;
&attributes);
(void) free_attr(attributes);
i++;
}
PWU_SUCCESS) {
(void) free_attr(attributes);
}
/* NOT REACHED */
}
switch (pam_authenticate(pamh, 0)) {
case PAM_SUCCESS:
break;
case PAM_USER_UNKNOWN:
usrname);
break;
case PAM_PERM_DENIED:
break;
case PAM_AUTH_ERR:
break;
default:
/* system error */
break;
}
if (flag == 0) { /* changing user password */
int chk_authtok = 0; /* check password strength */
dprintf1("call pam_chauthtok() repository name =%s\n",
/* Set up for Audit */
perror("adt_start_session");
}
perror("adt_alloc_event");
}
if (argc >= 1) {
/* save target user */
}
/* Don't check account expiration when invoked by root */
switch (pam_retval) {
case PAM_ACCT_EXPIRED:
break;
case PAM_AUTHTOK_EXPIRED:
break;
case PAM_NEW_AUTHTOK_REQD:
/* valid error when changing passwords */
break;
case PAM_SUCCESS:
/* Ok to change password */
break;
default:
}
}
tries = 1;
/* bypass password strength checks */
}
if (tries > 1)
if (pam_retval == PAM_TRY_AGAIN) {
(void) sleep(1);
}
tries++;
}
switch (pam_retval) {
case PAM_SUCCESS:
break;
break;
case PAM_AUTHTOK_LOCK_BUSY:
break;
case PAM_TRY_AGAIN:
break;
case PAM_AUTHTOK_ERR:
case PAM_AUTHTOK_RECOVERY_ERR:
default:
break;
}
(void) passwd_exit(retval);
/* NOT REACHED */
} else { /* changing attributes */
switch (flag) {
case EFLAG: /* changing user password attributes */
if (input)
else
break;
case GFLAG:
if (input)
else
break;
case HFLAG:
if (input)
else
break;
}
if (attributes != NULL) {
switch (retval) {
case PWU_SUCCESS:
if ((updated_reps & i) == 0)
continue;
}
break;
case PWU_AGING_DISABLED:
break;
default:
break;
}
} else {
}
(void) passwd_exit(retval);
}
/* NOTREACHED */
return (0);
}
/*
* Get a line of input from the user.
*
* If the line is empty, or the input equals 'oldval', NULL is returned.
* therwise, a malloced string containing the input (minus the trailing
* newline) is returned.
*/
char *
getresponse(char *oldval)
{
char resp[MAX_INPUT_LEN];
int resplen;
return (retval);
}
/*
* char *userinput(item)
*
* user conversation function. The old value of attribute "item" is
* displayed while the user is asked to provide a new value.
*
* returns a malloc()-ed string if the user actualy provided input
* or NULL if the user simply hit return or the input equals the old
* value (not changed).
*/
char *
{
char *oldval; /* shorthand for oldattr.data.val_s */
char *valid; /* points to valid shells */
char *response;
char *cp;
if (type == ATTR_SHELL) {
/* No current shell: set DEFSHL as default choice */
if (*oldval == '\0') {
}
/* User must currently have a valid shell */
setusershell();
valid = getusershell();
valid = getusershell();
endusershell();
return (NULL);
}
}
return (NULL);
/* Make sure new shell is listed */
setusershell();
valid = getusershell();
while (valid) {
char *cp;
/* Allow user to give shell without path */
if (*response == '/') {
} else {
else
cp++;
}
if (*response != '/') {
/* take shell name including path */
}
break;
}
valid = getusershell();
}
endusershell();
return (NULL);
}
return (response);
/* NOT REACHED */
}
/*
* if type == SHELL, we have returned by now. Only GECOS and
* HOMEDIR get to this point.
*/
/*
* PRE: oldval points to malloced string with Old Value
* INV: oldval remains unchanged
* POST:response points to valid string or NULL.
*/
for (;;) {
if (type == ATTR_GECOS)
else if (type == ATTR_HOMEDIR)
*response = '\0';
/* No-change or empty string are OK */
break;
/* Check for illegal characters */
} else {
/* don't allow control characters */
;
if (*cp != '\0') {
} else
break; /* response is a valid string */
}
/*
* We only get here if the input was invalid.
* In that case, we again ask the user for input.
*/
}
return (response);
}
/*
* ckarg():
* This function parses and verifies the
* arguments. It takes three parameters:
* argc => # of arguments
* argv => pointer to an argument
* attrlist => pointer to list of password attributes
*/
static int
{
extern char *optarg;
char *char_p;
int opt;
int flag;
flag = 0;
switch (opt) {
case 'r': /* Repository Specified */
/* repository: this option should be specified first */
"Repository is already defined or specified.\n"));
rusage();
return (FAIL);
}
}
} else {
gettext("invalid repository: %s\n"),
optarg);
rusage();
return (FAIL);
}
break;
case 'd': /* Delete Auth Token */
/* if no repository the default for -d is files */
/*
* Delete the password - only privileged processes
* can execute this for FILES
*/
"-d only applies to files repository\n"));
rusage(); /* exit */
return (FAIL);
}
return (FAIL);
}
rusage();
return (FAIL);
}
break;
case 'N': /* set account to be "no login" */
/* if no repository the default for -N is files */
"-N only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged processes can execute this
* for FILES
*/
if (IS_FILES(repository) &&
return (FAIL);
rusage(); /* exit */
return (FAIL);
}
break;
case 'l': /* lock the password */
/* if no repository the default for -l is files */
"-l only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged processes can execute this
* for FILES
*/
if (IS_FILES(repository) &&
return (FAIL);
rusage(); /* exit */
return (FAIL);
}
break;
case 'u': /* unlock the password */
/* if no repository the default for -u is files */
"-u only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged processes can execute this
* for FILES
*/
if (IS_FILES(repository) &&
return (FAIL);
rusage(); /* exit */
return (FAIL);
}
break;
case 'x': /* set the max date */
/* if no repository the default for -x is files */
"-x only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged process can execute this
* for FILES
*/
return (FAIL);
}
return (FAIL);
}
*char_p != '\0') {
return (FAIL);
}
break;
case 'n': /* set the min date */
/* if no repository the default for -n is files */
"-n only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged process can execute this
* for FILES
*/
if (IS_FILES(repository) &&
return (FAIL);
return (FAIL);
}
*char_p != '\0') {
return (FAIL);
}
break;
case 'w': /* set the warning field */
/* if no repository the default for -w is files */
"-w only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged process can execute this
* for FILES
*/
return (FAIL);
}
return (FAIL);
}
*char_p != '\0') {
return (FAIL);
}
break;
case 's': /* display password attributes */
/* if no repository the default for -s is files */
/* display password attributes */
"-s only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged process can execute this
* for FILES
*/
if (IS_FILES(repository) &&
return (FAIL);
return (FAIL);
}
break;
case 'a': /* display password attributes */
/* if no repository the default for -a is files */
"-a only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged process can execute this
* for FILES
*/
if (IS_FILES(repository) &&
return (FAIL);
return (FAIL);
}
break;
case 'f': /* expire password attributes */
/* if no repository the default for -f is files */
"-f only applies to files or nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
/*
* Only privileged process can execute this
* for FILES
*/
if (IS_FILES(repository) &&
return (FAIL);
return (FAIL);
}
break;
case 'D': /* domain name specified */
"-D only applies to nisplus repository\n"));
rusage(); /* exit */
return (FAIL);
}
return (FAIL);
}
/* It is cleaner not to set this flag */
/* flag |= OFLAG; */
/* get domain from optarg */
}
break;
case 'e': /* change login shell */
/* if no repository the default for -e is files */
/*
* Only privileged process can execute this
* for FILES
*/
return (FAIL);
}
return (FAIL);
}
break;
case 'g': /* change gecos information */
/* if no repository the default for -g is files */
/*
* Only privileged process can execute this
* for FILES
*/
return (FAIL);
}
return (FAIL);
}
break;
case 'h': /* change home dir */
/* if no repository the default for -h is files */
/*
* Only privileged process can execute this
* for FILES
*/
return (FAIL);
}
if (IS_NIS(repository)) {
return (FAIL);
}
return (FAIL);
}
break;
case '?':
rusage();
return (FAIL);
}
}
if (argc > 1) {
rusage();
return (FAIL);
}
/* Make sure (EXPIRE comes after (MAX comes after MIN)) */
/* If no options are specified or only the show option */
/* is specified, return because no option error checking */
/* is needed */
return (flag);
/* AFLAG must be used with SFLAG */
rusage();
return (FAIL);
}
/*
* user name is not specified (argc<1), it can't be
* aging info update.
*/
if (!(flag & NONAGEFLAG)) {
rusage();
return (FAIL);
}
}
/* user name(s) may not be specified when SAFLAG is used. */
rusage();
return (FAIL);
}
/*
* If aging is being turned off (maxdate == -1), mindate may not
* be specified.
*/
return (FAIL);
}
return (flag);
}
/*
*
* ckuid():
* This function returns SUCCESS if the caller is root, else
* it returns NOPERM.
*
*/
static int
ckuid(void)
{
if (uid != 0) {
}
return (SUCCESS);
}
/*
* get_attr()
*/
int
{
int res;
if (res == PWU_SUCCESS) {
return (PWU_SUCCESS);
}
if (res == PWU_NOT_FOUND)
username);
/*NOTREACHED*/
}
/*
* display_attr():
* This function prints out the password attributes of a usr
* onto standand output.
*/
void
{
char *status;
char *passwd;
long lstchg;
while (attributes) {
switch (attributes->type) {
case ATTR_PASSWD:
status = "NP ";
sizeof (LOCKSTRING)-1) == 0)
status = "LK ";
sizeof (NOLOGINSTRING)-1) == 0)
status = "NL ";
passwd[0] == '$')
status = "PS ";
else
status = "UN ";
break;
case ATTR_LSTCHG:
break;
case ATTR_MIN:
break;
case ATTR_MAX:
break;
case ATTR_WARN:
break;
}
}
if (status)
if (max != -1) {
if (lstchg == 0) {
} else {
}
}
}
void
{
while (attributes) {
}
}
/*
*
* get_namelist_files():
* This function gets a list of user names on the system from
*
*/
int
{
int max_user;
int nuser;
char **nl;
nuser = 0;
errno = 0;
return (NOPERM);
/*
* find out the actual number of entries in the PASSWD file
*/
max_user++;
/*
* reset the file stream pointer
*/
return (FMERR);
}
return (FMERR);
}
nuser++;
}
*namelist_p = nl;
return (SUCCESS);
}
/*
* get_namelist_nisplus
*
*/
/*
* Our private version of the switch frontend for getspent. We want to
* search just the nisplus sp file, so we want to bypass normal nsswitch.conf
* based processing. This implementation compatible with version 2 of the
* name service switch.
*/
#define NSS_NISPLUS_ONLY "nisplus"
extern int str2spwd(const char *, int, void *, char *, int);
static DEFINE_NSS_DB_ROOT(db_root);
static DEFINE_NSS_GETENT(context);
static void
{
p->name = NSS_DBNAM_SHADOW;
p->flags = NSS_USE_DEFAULT_CONFIG;
}
static void
_np_setspent(void)
{
}
static void
_np_endspent(void)
{
}
static struct spwd *
{
char *nam;
/* In getXXent_r(), protect the unsuspecting caller from +/- entries */
do {
/* No key to fill in */
&arg);
}
static nss_XbyY_buf_t *buffer;
static struct spwd *
_np_getspent(void)
{
nss_XbyY_buf_t *b;
}
int
{
int nuser = 0;
int alloced = 100;
char **nl;
struct spwd *p;
return (FMERR);
(void) _np_setspent();
while ((p = _np_getspent()) != NULL) {
_np_endspent();
return (FMERR);
}
alloced += 100;
_np_endspent();
return (FMERR);
}
}
}
(void) _np_endspent();
*namelist_p = nl;
return (SUCCESS);
}
int
{
if (IS_NISPLUS(repository))
else if (IS_FILES(repository))
rusage();
return (BADSYN);
}
/*
*
* passwd_exit():
* This function will call exit() with appropriate exit code
* according to the input "retcode" value.
* It also calls pam_end() to clean-up buffers before exit.
*
*/
void
passwd_exit(int retcode)
{
if (pamh)
switch (retcode) {
case SUCCESS:
break;
case NOPERM:
break;
case BADOPT:
break;
case FMERR:
break;
case FATAL:
break;
case FBUSY:
break;
case BADSYN:
break;
case BADAGE:
break;
case NOMEM:
break;
default:
break;
}
/* write password record */
if (adt_put_event(event,
pam_retval) != 0) {
(void) adt_end_session(ah);
perror("adt_put_event");
}
}
(void) adt_end_session(ah);
}
/*
*
* passwd_conv():
* This is the conv (conversation) function called from
* a PAM authentication module to print error messages
* or garner information from the user.
*
*/
/*ARGSUSED*/
static int
{
struct pam_message *m;
struct pam_response *r;
char *temp;
int k, i;
if (num_msg <= 0)
return (PAM_CONV_ERR);
sizeof (struct pam_response));
return (PAM_BUF_ERR);
k = num_msg;
m = *msg;
r = *response;
while (k--) {
switch (m->msg_style) {
case PAM_PROMPT_ECHO_OFF:
/* free responses */
r = *response;
for (i = 0; i < num_msg; i++, r++) {
if (r->resp)
}
return (PAM_BUF_ERR);
}
}
m++;
r++;
break;
case PAM_PROMPT_ECHO_ON:
}
sizeof (char));
/* free responses */
r = *response;
for (i = 0; i < num_msg; i++, r++) {
if (r->resp)
}
return (PAM_BUF_ERR);
}
}
m++;
r++;
break;
case PAM_ERROR_MSG:
}
m++;
r++;
break;
case PAM_TEXT_INFO:
}
m++;
r++;
break;
default:
break;
}
}
return (PAM_SUCCESS);
}
/*
* Utilities Functions
*/
/*
* int attrlist_add(attrlist **l, attrtype type, char *val)
* add an item, with type "type" and value "val", at the tail of list l.
* This functions exits the application on OutOfMem error.
*/
void
{
attrlist **w;
/* tail insert */
;
switch (type) {
case ATTR_MIN:
case ATTR_WARN:
case ATTR_MAX:
break;
default:
break;
}
}
/*
* attrlist_reorder(attrlist **l)
* Make sure that
* - if both MIN and MAX are set, MAX comes before MIN.
*/
static void
attrlist_reorder(attrlist **l)
{
attrlist **w;
return; /* order of list with <= one item is ok */
/*
* We simply walk the list, take off the EXPIRE and MAX items if
* they appear, and put them (first MAX, them EXPIRE) at the end
* of the list.
*/
w = l;
while (*w != NULL) {
if ((*w)->type == ATTR_EXPIRE_PASSWORD) {
exp = *w;
*w = (*w)->next;
max = *w;
*w = (*w)->next;
} else
w = &(*w)->next;
}
/* 'w' points to the address of the 'next' field of the last element */
if (max) {
*w = max;
}
if (exp) {
*w = exp;
}
*w = NULL;
}
void
rusage(void)
{
MSG("usage:\n");
MSG("\tpasswd [-r files | -r nis | -r nisplus | -r ldap] [name]\n");
MSG("\tpasswd [-r files] [-egh] [name]\n");
MSG("\tpasswd [-r files] -sa\n");
MSG("\tpasswd [-r files] -s [name]\n");
MSG("\tpasswd [-r files] [-d|-l|-N|-u] [-f] [-n min] [-w warn] "
"[-x max] name\n");
MSG("\tpasswd -r nis [-eg] [name]\n");
MSG("\tpasswd -r nisplus [-egh] [-D domainname] [name]\n");
MSG("\tpasswd -r nisplus -sa\n");
MSG("\tpasswd -r nisplus [-D domainname] -s [name]\n");
MSG("\tpasswd -r nisplus [-D domainname] [-l|-N|-u] [-f] [-n min] "
"[-w warn]\n");
MSG("\t\t[-x max] name\n");
MSG("\tpasswd -r ldap [-egh] [name]\n");
}