blob: f707c05217b9943b2d0014676f1d403a00c1fa9c [file] [log] [blame]
/*
* passwd.c - change password on an account
*
* Initially written for Linux by Peter Orbaek <poe@daimi.aau.dk>
* Currently maintained at ftp://ftp.daimi.aau.dk/pub/linux/poe/
Hacked by Alvaro Martinez Echevarria, alvaro@enano.etsit.upm.es,
to allow peaceful coexistence with yp. Nov 94.
Hacked to allow root to set passwd from command line.
by Arpad Magossanyi (mag@tas.vein.hu)
Hacked by Peter Breitenlohner, peb@mppmu.mpg.de,
moved Alvaro's changes to setpwnam.c (so they get used
by chsh and chfn as well). Oct 5, 96.
*/
/*
* Sun Oct 15 13:18:34 1995 Martin Schulze <joey@finlandia.infodrom.north.de>
*
* I have completely rewritten the whole argument handlig (what?)
* to support two things. First I wanted "passwd $user $pw" to
* work and second I wanted simplicity checks to be done for
* root, too. Only root can turn this of using the -f
* switch. Okay, I started with this to support -V version
* information, but one thing comes to the next. *sigh*
* In a later step perhaps we'll be able to support shadow
* passwords. (?)
*
* I have also included a DEBUG mode (-DDEBUG) to test the
* argument handling _without_ any write attempt to
* /etc/passwd.
*
* If you're paranoid about security on your system, you may want
* to add -DLOGALL to CFLAGS. This will turn on additional syslog
* logging of every password change. (user changes are logged as
* auth.notice, but changing root's password is logged as
* auth.warning. (Of course, the password itself is not logged.)
*/
/*
* Usage: passwd [username [password]]
* Only root may use the one and two argument forms.
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <termios.h>
#include <getopt.h>
#include <malloc.h>
#include <fcntl.h>
#include <pwd.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <sys/resource.h>
#if defined (__GNU_LIBRARY__) && __GNU_LIBRARY__ > 1
#include <crypt.h>
#endif
#if 0
# include "../version.h"
#else
char version[] = "admutil 1.18, 15-Oct-95";
#endif
#ifndef _PATH_CHFN
# define _PATH_CHFN "/usr/bin/chfn"
# define _PATH_CHSH "/usr/bin/chsh"
#endif
#define LOGALL
#ifdef LOGALL
#include <syslog.h>
#endif /* LOGALL */
extern int is_local(char *); /* islocal.c */
extern int setpwnam(struct passwd *); /* setpwnam.c */
#define ascii_to_bin(c) ((c)>='a'?(c-59):(c)>='A'?((c)-53):(c)-'.')
#define bin_to_ascii(c) ((c)>=38?((c)-38+'a'):(c)>=12?((c)-12+'A'):(c)+'.')
static void
pexit(char *str, ...)
{
va_list vlst;
va_start(vlst, str);
vfprintf(stderr, str, vlst);
fprintf(stderr, ": ");
perror("");
va_end(vlst);
exit(1);
}
/*
* Do various checks for stupid passwords here...
*
* This would probably be the best place for checking against
* dictionaries. :-)
*/
int check_passwd_string(char *passwd, char *string)
{
int r;
char *p, *q;
r = 0;
/* test for string at the beginning of passwd */
for (p = passwd, q = string; *q && *p; q++, p++) {
if(tolower(*p) != tolower(*q)) {
r++;
break;
}
}
/* test for reverse string at the beginning of passwd */
for (p = passwd, q = string + strlen(string)-1;
*p && q >= string; p++, q--) {
if(tolower(*p) != tolower(*q)) {
r++;
break;
}
}
/* test for string at the end of passwd */
for (p = passwd + strlen(passwd)-1, q = string + strlen(string)-1;
q >= string && p >= passwd; q--, p--) {
if(tolower(*p) != tolower(*q)) {
r++;
break;
}
}
/* test for reverse string at the beginning of passwd */
for (p = passwd + strlen(passwd)-1, q = string;
p >= passwd && *q; p--, q++) {
if(tolower(*p) != tolower(*q)) {
r++;
break;
}
}
if (r != 4) {
return 0;
}
return 1;
}
int check_passwd(char *passwd, char *oldpasswd, char *user, char *gecos)
{
int ucase, lcase, digit, other;
char *c, *g, *p;
if ( (strlen(passwd) < 6) ) {
printf("The password must have at least 6 characters, try again.\n");
return 0;
}
other = digit = ucase = lcase = 0;
for (p = passwd; *p; p++) {
ucase = ucase || isupper(*p);
lcase = lcase || islower(*p);
digit = digit || isdigit(*p);
other = other || !isalnum(*p);
}
if ( (other + digit + ucase + lcase) < 2) {
printf("The password must contain characters out of two of the following\n");
printf("classes: upper and lower case letters, digits and non alphanumeric\n");
printf("characters. See passwd(1) for more information.\n");
return 0;
}
if ( oldpasswd[0] && !strncmp(oldpasswd, crypt(passwd, oldpasswd), 13) ) {
printf("You cannot reuse the old password.\n");
return 0;
}
if ( !check_passwd_string(passwd, user) ) {
printf("Please don't use something like your username as password!\n");
return 0;
}
/* check against realname */
if ( (c = index(gecos, ',')) ) {
if ( c-gecos && (g = (char *)malloc (c-gecos+1)) ) {
strncpy (g, gecos, c-gecos);
g[c-gecos] = 0;
while ( (c=rindex(g, ' ')) ) {
if ( !check_passwd_string(passwd, c+1) ) {
printf("Please don't use something like your realname as password!\n");
free (g);
return 0;
}
*c = '\0';
} /* while */
if ( !check_passwd_string(passwd, g) ) {
printf("Please don't use something like your realname as password!\n");
free (g);
return 0;
}
free (g);
} /* if malloc */
}
/*
* if ( !check_password_dict(passwd) ) ...
*/
return 1; /* fine */
}
void usage()
{
printf ("Usage: passwd [username [password]]\n");
printf("Only root may use the one and two argument forms.\n");
}
int
main(argc, argv)
int argc;
char *argv[];
{
struct passwd *pe;
uid_t gotuid = getuid();
char *pwdstr = NULL, *cryptstr, *oldstr;
char pwdstr1[10];
char *user;
time_t tm;
char salt[2];
int force_passwd = 0;
int silent = 0;
char c;
int opt_index;
int fullname = 0, shell = 0;
static const struct option long_options[] =
{
{"fullname", no_argument, 0, 'f'},
{"shell", no_argument, 0, 's'},
{"force", no_argument, 0, 'o'},
{"quiet", no_argument, 0, 'q'},
{"silent", no_argument, 0, 'q'},
{"version", no_argument, 0, 'v'},
{0, 0, 0, 0}
};
optind = 0;
while ( (c = getopt_long(argc, argv, "foqsvV", long_options, &opt_index)) != -1 ) {
switch (c) {
case 'f':
fullname = 1;
break;
case 's':
shell = 1;
break;
case 'o':
force_passwd = 1;
break;
case 'q':
silent = 1;
break;
case 'V':
case 'v':
printf("%s\n", version);
exit(0);
default:
fprintf(stderr, "Usage: passwd [-foqsvV] [user [password]]\n");
exit(1);
} /* switch (c) */
} /* while */
if (fullname || shell) {
char *args[100];
int i, j;
setuid(getuid()); /* drop special privs. */
if (fullname)
args[0] = _PATH_CHFN;
else
args[0] = _PATH_CHSH;
for (i = optind, j = 1; (i < argc) && (j < 99); i++, j++)
args[j] = argv[i];
args[j] = NULL;
execv(args[0], args);
fprintf(stderr, "Can't exec %s: %s\n", args[0], strerror(errno));
exit(1);
}
switch (argc - optind) {
case 0:
if ( !(user = getlogin()) ) {
if ( !(pe = getpwuid( getuid() )) ) {
pexit("Cannot find login name");
} else
user = pe->pw_name;
}
break;
case 1:
if(gotuid) {
printf("Only root can change the password for others.\n");
exit (1);
} else
user = argv[optind];
break;
case 2:
if(gotuid) {
printf("Only root can change the password for others.\n");
exit(1);
} else {
user = argv[optind];
pwdstr = argv[optind+1];
}
break;
default:
printf("Too many arguments.\n");
exit (1);
} /* switch */
if(!(pe = getpwnam(user))) {
pexit("Can't find username anywhere. Is `%s' really a user?", user);
}
if (!(is_local(user))) {
puts("Sorry, I can only change local passwords. Use yppasswd instead.");
exit(1);
}
/* if somebody got into changing utmp... */
if(gotuid && gotuid != pe->pw_uid) {
puts("UID and username does not match, imposter!");
exit(1);
}
if ( !silent )
printf( "Changing password for %s\n", user );
if ( (gotuid && pe->pw_passwd && pe->pw_passwd[0])
|| (!gotuid && !strcmp(user,"root")) ) {
oldstr = getpass("Enter old password: ");
if(strncmp(pe->pw_passwd, crypt(oldstr, pe->pw_passwd), 13)) {
puts("Illegal password, imposter.");
exit(1);
}
}
if ( pwdstr ) { /* already set on command line */
if ( !force_passwd && !check_passwd(pwdstr, pe->pw_passwd, user, pe->pw_gecos) )
exit (1);
} else {
/* password not set on command line by root, ask for it ... */
redo_it:
pwdstr = getpass("Enter new password: ");
if (pwdstr[0] == '\0') {
puts("Password not changed.");
exit(1);
}
if ( (gotuid || (!gotuid && !force_passwd))
&& !check_passwd(pwdstr, pe->pw_passwd, user, pe->pw_gecos) )
goto redo_it;
strncpy(pwdstr1, pwdstr, 9);
pwdstr1[9] = 0;
pwdstr = getpass("Re-type new password: ");
if(strncmp(pwdstr, pwdstr1, 8)) {
puts("You misspelled it. Password not changed.");
exit(1);
}
} /* pwdstr i.e. password set on command line */
time(&tm); tm ^= getpid();
salt[0] = bin_to_ascii(tm & 0x3f);
salt[1] = bin_to_ascii((tm >> 6) & 0x3f);
cryptstr = crypt(pwdstr, salt);
if (pwdstr[0] == 0) cryptstr = "";
#ifdef LOGALL
openlog("passwd", 0, LOG_AUTH);
if (gotuid)
syslog(LOG_NOTICE,"password changed, user %s",user);
else {
if ( !strcmp(user, "root") )
syslog(LOG_WARNING,"ROOT PASSWORD CHANGED");
else
syslog(LOG_NOTICE,"password changed by root, user %s",user);
}
closelog();
#endif /* LOGALL */
pe->pw_passwd = cryptstr;
#ifdef DEBUG
printf ("calling setpwnam to set password.\n");
#else
if (setpwnam( pe ) < 0) {
perror( "setpwnam" );
printf( "Password *NOT* changed. Try again later.\n" );
exit( 1 );
}
#endif
if ( !silent )
printf("Password changed.\n");
exit(0);
}