static char sccsid
[] = "@(#)passwd.c 4.16 (Berkeley) 85/08/09";
* Modify a field in the password file (either
* password, login shell, or gecos field).
* This program should be suid with an owner
* with write permission on /etc/passwd.
#include <sys/resource.h>
char temp
[] = "/etc/ptmp";
char passwd
[] = "/etc/passwd";
char *cp
, *uname
, *progname
;
int fd
, i
, u
, dochfn
, dochsh
, err
;
if ((progname
= rindex(argv
[0], '/')) == NULL
)
while (argc
> 0 && argv
[0][0] == '-') {
for (cp
= &argv
[0][1]; *cp
; cp
++) switch (*cp
) {
"passwd: Only one of -f and -s allowed.\n");
fprintf(stderr
, "passwd: -%c: unknown option.\n", *cp
);
if (!dochfn
&& !dochsh
) {
if (strcmp(progname
, "chfn") == 0)
else if (strcmp(progname
, "chsh") == 0)
if ((uname
= getlogin()) == NULL
) {
fprintf(stderr
, "Usage: %s [-f] [-s] [user]\n", progname
);
printf("Changing %s for %s.\n",
dochfn
? "finger information" :
dochsh
? "login shell" : "password",
fprintf(stderr
, "passwd: %s: unknown user.\n", uname
);
if (u
!= 0 && u
!= pwd
->pw_uid
) {
printf("Permission denied.\n");
cp
= getfingerinfo(pwd
, u
);
cp
= getloginshell(pwd
, u
, *argv
);
cp
= getnewpasswd(pwd
, u
);
signal(SIGQUIT
, SIG_IGN
);
signal(SIGTSTP
, SIG_IGN
);
fd
= open(temp
, O_WRONLY
|O_CREAT
|O_EXCL
, 0644);
fprintf(stderr
, "passwd: ");
fprintf(stderr
, "password file busy - try again.\n");
if ((tf
= fdopen(fd
, "w")) == NULL
) {
fprintf(stderr
, "passwd: fdopen failed?\n");
if ((dp
= dbm_open(passwd
, O_RDWR
, 0644)) == NULL
) {
fprintf(stderr
, "Warning: dbm_open failed: ");
} else if (flock(dp
->dbm_dirf
, LOCK_EX
) < 0) {
perror("Warning: lock failed");
* Copy passwd to temp, replacing matching lines
while ((pwd
= getpwent()) != NULL
) {
if (strcmp(pwd
->pw_name
, uname
) == 0) {
if (u
&& u
!= pwd
->pw_uid
) {
fprintf(stderr
, "passwd: permission denied.\n");
if (pwd
->pw_gecos
[0] == '*') /* ??? */
fprintf(tf
,"%s:%s:%d:%d:%s:%s:%s\n",
if (dp
!= NULL
&& dbm_error(dp
))
fprintf(stderr
, "Warning: dbm_store failed\n");
fprintf(stderr
, "Warning: %s write error, %s not updated\n",
if (rename(temp
, passwd
) < 0) {
perror("passwd: rename");
rlim
.rlim_cur
= rlim
.rlim_max
= RLIM_INFINITY
;
(void) setrlimit(lim
, &rlim
);
* Replace the password entry in the dbm data base with pwd.
#define COMPACT(e) tp = pwd->pw_/**/e; while (*cp++ = *tp++);
bcopy((char *)&pwd
->pw_uid
, cp
, sizeof (int));
bcopy((char *)&pwd
->pw_gid
, cp
, sizeof (int));
bcopy((char *)&pwd
->pw_quota
, cp
, sizeof (int));
content
.dsize
= cp
- buf
;
key
.dsize
= strlen(pwd
->pw_name
);
dbm_store(dp
, key
, content
, DBM_REPLACE
);
key
.dptr
= (char *)&pwd
->pw_uid
;
key
.dsize
= sizeof (int);
dbm_store(dp
, key
, content
, DBM_REPLACE
);
register struct passwd
*pwd
;
int i
, insist
= 0, ok
, flags
;
if (pwd
->pw_passwd
[0] && u
!= 0) {
strcpy(pwbuf
, getpass("Old password:"));
pw
= crypt(pwbuf
, pwd
->pw_passwd
);
if (strcmp(pw
, pwd
->pw_passwd
) != 0) {
strcpy(pwbuf
, getpass("New password:"));
printf("Password unchanged.\n");
* Insure password is of reasonable length and
* composition. If we really wanted to make things
* sticky, we could check the dictionary for common
* words, but then things would really be slow.
if (c
>= 'a' && c
<= 'z')
else if (c
>= 'A' && c
<= 'Z')
else if (c
>= '0' && c
<= '9')
if (flags
>= 7 && pwlen
>= 4)
if ((flags
== 2 || flags
== 4) && pwlen
>= 6)
if ((flags
== 3 || flags
== 5 || flags
== 6) && pwlen
>= 5)
printf("Please use %s.\n", flags
== 1 ?
"at least one non-numeric character" :
if (strcmp(pwbuf
, getpass("Retype new password:")) != 0) {
printf("Mismatch - password unchanged.\n");
saltc
[1] = (salt
>>6) & 077;
for (i
= 0; i
< 2; i
++) {
return (crypt(pwbuf
, saltc
));
#define DEFSHELL okshells[0]
{ "/bin/sh", "/bin/csh", "/bin/oldcsh", "/bin/newcsh", "/usr/new/csh", 0 };
getloginshell(pwd
, u
, arg
)
static char newshell
[256];
if (pwd
->pw_shell
== 0 || *pwd
->pw_shell
== '\0')
pwd
->pw_shell
= DEFSHELL
;
strncpy(newshell
, arg
, sizeof newshell
- 1);
newshell
[sizeof newshell
- 1] = 0;
printf("Old shell: %s\nNew shell: ", pwd
->pw_shell
);
fgets(newshell
, sizeof (newshell
) - 1, stdin
);
cp
= index(newshell
, '\n');
if (newshell
[0] == '\0' || strcmp(newshell
, pwd
->pw_shell
) == 0) {
printf("Login shell unchanged.\n");
* Allow user to give shell name w/o preceding pathname.
if (*cp
!= '/' && u
!= 0) {
for (cpp
= okshells
; *cpp
; cpp
++) {
if (strcmp(cp
+1, newshell
) == 0)
for (cpp
= okshells
; *cpp
; cpp
++)
if (strcmp(*cpp
, newshell
) == 0)
printf("%s is unacceptable as a new shell.\n",
if (access(newshell
, X_OK
) < 0) {
printf("%s is unavailable.\n", newshell
);
if (strcmp(newshell
, DEFSHELL
) == 0)
* Get name, room number, school phone, and home phone.
struct default_values
*defaults
, *get_defaults();
static char answer
[4*BUFSIZ
];
defaults
= get_defaults(pwd
->pw_gecos
);
printf("Default values are printed inside of of '[]'.\n");
printf("To accept the default, type <return>.\n");
printf("To have a blank entry, type the word 'none'.\n");
printf("\nName [%s]: ", defaults
->name
);
(void) fgets(in_str
, BUFSIZ
, stdin
);
if (special_case(in_str
, defaults
->name
))
} while (illegal_input(in_str
));
(void) strcpy(answer
, in_str
);
printf("Room number (Exs: 597E or 197C) [%s]: ",
(void) fgets(in_str
, BUFSIZ
, stdin
);
if (special_case(in_str
, defaults
->office_num
))
} while (illegal_input(in_str
) || illegal_building(in_str
));
(void) strcat(strcat(answer
, ","), in_str
);
* Get office phone number.
* Remove hyphens and 642, x2, or 2 prefixes if present.
printf("Office Phone (Ex: 1632) [%s]: ",
(void) fgets(in_str
, BUFSIZ
, stdin
);
if (special_case(in_str
, defaults
->office_phone
))
if (strlen(in_str
) == 8 && strcmpn(in_str
, "642", 3) == 0)
(void) strcpy(in_str
, in_str
+3);
if (strlen(in_str
) == 7 && strcmpn(in_str
, "x2", 2) == 0)
(void) strcpy(in_str
, in_str
+2);
if (strlen(in_str
) == 6 && in_str
[0] == '2')
(void) strcpy(in_str
, in_str
+1);
} while (illegal_input(in_str
) || not_all_digits(in_str
)
|| wrong_length(in_str
, 4));
(void) strcat(strcat(answer
, ","), in_str
);
* Remove hyphens if present.
printf("Home Phone (Ex: 9875432) [%s]: ", defaults
->home_phone
);
(void) fgets(in_str
, BUFSIZ
, stdin
);
if (special_case(in_str
, defaults
->home_phone
))
} while (illegal_input(in_str
) || not_all_digits(in_str
));
(void) strcat(strcat(answer
, ","), in_str
);
if (strcmp(answer
, pwd
->pw_gecos
) == 0) {
printf("Finger information unchanged.\n");
* Prints an error message if a ':' or a newline is found in the string.
* A message is also printed if the input string is too long.
* The password file uses :'s as seperators, and are not allowed in the "gcos"
* field. Newlines serve as delimiters between users in the password file,
* and so, those too, are checked for. (I don't think that it is possible to
* type them in, but better safe than sorry)
* Returns '1' if a colon or newline is found or the input line is too long.
int length
= strlen(input_str
);
if (index(input_str
, ':')) {
printf("':' is not allowed.\n");
if (input_str
[length
-1] != '\n') {
/* the newline and the '\0' eat up two characters */
printf("Maximum number of characters allowed is %d\n",
/* flush the rest of the input line */
while (getchar() != '\n')
* Delete newline by shortening string by 1.
input_str
[length
-1] = '\0';
* Don't allow control characters, etc in input string.
for (ptr
=input_str
; *ptr
!= '\0'; ptr
++) {
printf("Control characters are not allowed.\n");
* Removes '-'s from the input string.
while ((hyphen
= index(str
, '-')) != NULL
)
(void) strcpy(hyphen
, hyphen
+1);
* Checks to see if 'str' contains only digits (0-9). If not, then
* an error message is printed and '1' is returned.
for (ptr
= str
; *ptr
!= '\0'; ++ptr
)
printf("Phone numbers can only contain digits.\n");
* Returns 1 when the length of the input string is not zero or equal to n.
* Prints an error message in this case.
if (strlen(str
) != 0 && strlen(str
) != n
) {
printf("The phone number should be %d digits long.\n", n
);
* Deal with Berkeley buildings. Abbreviating Cory to C and Evans to E.
* Correction changes "str".
* Returns 1 if incorrect room format.
* Note: this function assumes that the newline has been removed from str.
int length
= strlen(str
);
* If the string is [Ee]vans or [Cc]ory or ends in
* [ \t0-9][Ee]vans or [ \t0-9M][Cc]ory, then contract the name
* into 'E' or 'C', as the case may be, and delete leading blanks.
if (length
>= 5 && strcmp(ptr
= str
+ length
- 4, "vans") == 0 &&
(*--ptr
== 'e' || *ptr
== 'E') &&
(--ptr
< str
|| isspace(*ptr
) || isdigit(*ptr
))) {
for (; ptr
> str
&& isspace(*ptr
); ptr
--)
if (length
>= 4 && strcmp(ptr
= str
+ length
- 3, "ory") == 0 &&
(*--ptr
== 'c' || *ptr
== 'C') &&
(--ptr
< str
|| *ptr
== 'M' || isspace(*ptr
) || isdigit(*ptr
))) {
for (; ptr
> str
&& isspace(*ptr
); ptr
--)
* get_defaults picks apart "str" and returns a structure points.
* "str" contains up to 4 fields separated by commas.
* Any field that is missing is set to blank.
struct default_values
*answer
;
answer
= (struct default_values
*)
malloc((unsigned)sizeof(struct default_values
));
if (answer
== (struct default_values
*) NULL
) {
"\nUnable to allocate storage in get_defaults!\n");
* Values if no corresponding string in "str".
answer
->office_phone
= "";
str
= index(answer
->name
, ',');
answer
->office_num
= str
+ 1;
str
= index(answer
->office_num
, ',');
answer
->office_phone
= str
+ 1;
str
= index(answer
->office_phone
, ',');
answer
->home_phone
= str
+ 1;
* special_case returns true when either the default is accepted
* (str = '\n'), or when 'none' is typed. 'none' is accepted in
* either upper or lower case (or any combination). 'str' is modified
special_case(str
,default_str
)
static char word
[] = "none\n";
* If the default is accepted, then change the old string do the
(void) strcpy(str
, default_str
);
* Check to see if str is 'none'. (It is questionable if case
* insensitivity is worth the hair).
for (ptr
= str
; *ptr
!= '\0'; ++ptr
) {
if (*wordptr
== '\0') /* then words are different sizes */
if (isupper(*ptr
) && (tolower(*ptr
) == *wordptr
))
* At this point we have a mismatch, so we return
* Make sure that words are the same length.
if (*(wordptr
+1) != '\0')
* Change 'str' to be the null string