Redo lib/password.c to remove shadow.h.

Implement internal common plumbing to modify colon separated files.
Doesn't handle "pam" but the design of pam is incompatible with static linking.
This commit is contained in:
Rob Landley 2023-08-26 12:58:47 -05:00
parent f614f4b930
commit 6c30b35342
10 changed files with 185 additions and 193 deletions

View File

@ -356,7 +356,9 @@ char *escape_url(char *str, char *and);
char *unescape_url(char *str, int do_cut);
// password.c
int get_salt(char *salt, char * algo);
int get_salt(char *salt, char *algo, int rand);
int read_password(char *buff, int buflen, char *mesg);
int update_password(char *filename, char *username, char *entry, int pos);
// commas.c
void comma_args(struct arg_list *al, void *data, char *err,
@ -423,6 +425,3 @@ pid_t __attribute__((returns_twice)) xvforkwrap(pid_t pid);
#define minof(a, b) ({typeof(a) aa = (a); typeof(b) bb = (b); aa<bb ? aa : bb;})
#define maxof(a, b) ({typeof(a) aa = (a); typeof(b) bb = (b); aa>bb ? aa : bb;})
// Functions in need of further review/cleanup
#include "lib/pending.h"

View File

@ -1,45 +1,48 @@
/* password.c - password read/update helper functions.
*
* Copyright 2012 Ashwini Kumar <ak.ashwini@gmail.com>
*
* TODO: cleanup
*/
#include "toys.h"
// generate $id$salt$hash given a password and algorithm. Generates salt if NULL
//char *toy_crypt(char *pass, char *salt, char *algo)
// generate ID prefix and random salt for given encryption algorithm.
int get_salt(char *salt, char *algo)
int get_salt(char *salt, char *algo, int rand)
{
struct {
char *type, id, len;
} al[] = {{"des", 0, 2}, {"md5", 1, 8}, {"sha256", 5, 16}, {"sha512", 6, 16}};
int i;
char *s;
int i, len;
for (i = 0; i < ARRAY_LEN(al); i++) {
if (!strcmp(algo, al[i].type)) {
int len = al[i].len;
char *s = salt;
if (al[i].id) s += sprintf(s, "$%c$", '0'+al[i].id);
// Read appropriate number of random bytes for salt
xgetrandom(libbuf, ((len*6)+7)/8);
// Grab 6 bit chunks and convert to characters in ./0-9a-zA-Z
for (i = 0; i<len; i++) {
int bitpos = i*6, bits = bitpos/8;
bits = ((libbuf[i]+(libbuf[i+1]<<8)) >> (bitpos&7)) & 0x3f;
bits += 46;
if (bits > 57) bits += 7;
if (bits > 90) bits += 6;
s[i] = bits;
}
salt[len] = 0;
return s-salt;
for (s = al[i].type, len = 0; algo[len]; len++) {
while (ispunct(algo[len])) len++;
if (tolower(algo[len]) != tolower(*s++)) break;
}
if (algo[len]) continue;
len = al[i].len;
s = salt + (al[i].id ? sprintf(salt, "$%c$", '0'+al[i].id) : 0);
// Read appropriate number of random bytes for salt
if (rand) xgetrandom(libbuf, ((len*6)+7)/8);
// Grab 6 bit chunks and convert to characters in ./0-9a-zA-Z
for (i = 0; i<len; i++) {
int bitpos = i*6, bits = bitpos/8;
bits = ((libbuf[i]+(libbuf[i+1]<<8)) >> (bitpos&7)) & 0x3f;
bits += 46;
if (bits > 57) bits += 7;
if (bits > 90) bits += 6;
s[i] = bits;
}
s[len] = 0;
return s-salt;
}
return -1;
@ -79,79 +82,91 @@ int read_password(char *buf, int buflen, char *mesg)
return ret;
}
/* update colon-separated text files ala /etc/{passwd,shadow,group,gshadow}
* username = string match for first entry in line
* entry = new entry (NULL deletes matching line from file)
* pos = which entry to replace with "entry" (0 is first)
*/
// filename+ = new copy being written, filename- = backup of old version
// update colon-separated text files ala /etc/{passwd,shadow,group,gshadow}
// returns 1 for success, 0 for failure
// username = string match for first entry in line
// entry = new entry (NULL deletes matching line, contains : adds/replaces line)
// pos = if no : in "entry", which field to replace (0 is first)
// filename+ = new copy being written, filename- = backup of old version
int update_password(char *filename, char *username, char *entry, int pos)
{
char *filenamesfx = xmprintf("%s-", filename), *line = 0, *start, *end;
FILE *ofp, *nfp;
int ret = 0, found = 0, len = strlen(username)*!strchr(username, ':'), ii;
char *ff = xmprintf("%s-", filename), *line = 0, *start, *end, *out, *oo;
FILE *ofp;
int len = strlen(username)*!strchr(username,':'), rc = 0, ret = 0, found = 0,
nfd;
struct flock lock = {.l_type = F_WRLCK};
struct stat st;
long long ll = 0;
// Open old filename ("r" won't let us lock), get blocking lock
if (!(ofp = fopen(filename, "w+")) || 0>fcntl(fileno(ofp), F_SETLK, &lock)) {
perror_msg("%s", filename);
// Open old filename ("r" won't let us lock) and get blocking lock
if (!(ofp = fopen(filename, "w+")) || 0>fcntl(fileno(ofp), F_SETLK, &lock)\
|| fstat(fileno(ofp), &st))
{
perror_msg_raw(filename);
goto free_storage;
}
// Delete old backup, link new backup. (Failure here isn't fatal.)
unlink(filenamesfx);
if (0>link(filename, filenamesfx)) perror_msg("%s", filenamesfx);
unlink(ff);
if (0>link(filename, ff)) perror_msg_raw(ff);
// Open new file to copy entries to
filenamesfx[strlen(filenamesfx)-1] = '+';
if (!(nfp = fopen(filenamesfx, "w+"))) {
perror_msg("%s", filenamesfx);
ff[strlen(ff)-1] = '+';
if (-1 == (nfd = xcreate(ff, O_CREAT|O_EXCL|O_WRONLY, st.st_mode))) {
perror_msg_raw(ff);
goto free_storage;
}
// Loop through lines
while (getline(&line, (void *)&ll, ofp)) {
for (; getline(&line, (void *)&ll, ofp)!=-1; memset(line, 0, strlen(line))) {
// find matching line
start = end = line;
if (strncmp(chomp(line), username, len) || line[len]!=':') {
oo = 0;
start = end = chomp(line);
if (strncmp(line, username, len) || !(line[len] && line[len]!=':'))
out = line;
else {
found++;
if (!entry) continue;
// Find start and end of span to replace
for (ii = pos;;) {
while (*end != ':') {
if (!*end) break;
end++;
// Delete or replace whole line?
if (!entry) out = 0;
else if (strchr(entry, ':')) out = entry;
// Replace entry at pos
else {
for (;; pos--, start = ++end) {
while (*end && *end != ':') end++;
if (!pos || !*end) break;
}
if (ii) {
start = ++end;
ii--;
} else break;
if (pos>=0) out = line;
else oo = out = xmprintf("%*s%s%s\n", (int)(start-line),line,entry,end);
}
if (ii) start = end = line;
}
// Write with replacement (if any)
fprintf(nfp, "%*s%s%s\n", (int)(start-line), line,
(start==line) ? "" : entry, end);
memset(line, 0, strlen(line));
if (out) {
rc = dprintf(nfd, "%s\n", out);
free(out);
if (rc<0) {
perror_msg_raw(ff);
goto free_storage;
}
free(oo);
}
}
free(line);
fflush(nfp);
fsync(fileno(nfp));
fclose(nfp); // automatically unlocks
if (!found && entry && strchr(entry, ':')) dprintf(nfd, "%s\n", entry);
fsync(nfd);
close(nfd); // automatically unlocks
if (!found || rename(filenamesfx, filename)) {
if (found) perror_msg("%s -> %s", filenamesfx, filename);
else if (entry) fprintf(nfp, "%s\n", entry);
unlink(filenamesfx);
} else ret = 1;
if (!found || rename(ff, filename)) {
if (found) perror_msg("%s -> %s", ff, filename);
else if (entry) error_msg("No %s in %s", username, filename);
} else ret = 1, *ff = 0;
free_storage:
if (ofp) fclose(ofp);
free(filenamesfx);
if (ff && *ff) unlink(ff);
free(ff);
return ret;
}

View File

@ -1,6 +0,0 @@
// pending.h - header for pending.c
// password.c
#define MAX_SALT_LEN 20 //3 for id, 16 for key, 1 for '\0'
int read_password(char *buff, int buflen, char *mesg);
int update_password(char *filename, char *username, char *encrypted, int pos);

View File

@ -1,4 +1,4 @@
/* passwd.c - Program to update user password.
/* passwd.c - update user password.
*
* Copyright 2012 Ashwini Kumar <ak.ashwini@gmail.com>
* Modified 2012 Jason Kyungwan Han <asura321@gmail.com>
@ -13,9 +13,9 @@ config PASSWD
help
usage: passwd [-a ALGO] [-dlu] [USER]
Update user's authentication tokens. Defaults to current user.
Update user's login password. Defaults to current user.
-a ALGO Encryption method (des, md5, sha256, sha512) default: des
-a ALGO Encryption method (des, md5, sha256, sha512) default: md5
-d Set password to ''
-l Lock (disable) account
-u Unlock (enable) account
@ -53,17 +53,17 @@ static void weak_check(char *new, char *old, char *user)
void passwd_main(void)
{
uid_t myuid;
uid_t myuid = getuid();
struct passwd *pw = 0;
struct spwd *sp;
char *pass, *name, *encrypted = 0, salt[32];
// If we're root or not -lud, load specified user. Exit if not allowed.
if (!(myuid = getuid()) || !(toys.optflags&(FLAG_l|FLAG_u|FLAG_d))) {
if (!myuid || !(toys.optflags&(FLAG_l|FLAG_u|FLAG_d))) {
if (*toys.optargs) pw = xgetpwnam(*toys.optargs);
else pw = xgetpwuid(myuid);
}
if (!pw || (myuid && (myuid != pw->pw_uid))) error_exit("Not root");
if (!pw || (myuid && myuid != pw->pw_uid)) error_exit("Not root");
// Get password from /etc/passwd or /etc/shadow
// TODO: why still support non-shadow passwords...?
@ -83,7 +83,7 @@ void passwd_main(void)
*(encrypted = toybuf) = 0;
} else {
if (!TT.a) TT.a = "des";
if (get_salt(salt, TT.a)<0) error_exit("bad -a '%s'", TT.a);
if (get_salt(salt, TT.a, 1)<0) error_exit("bad -a '%s'", TT.a);
printf("Changing password for %s\n", name);
if (myuid) {

View File

@ -14,11 +14,10 @@ config MKPASSWD
help
usage: mkpasswd [-P FD] [-m TYPE] [-S SALT] [PASSWORD] [SALT]
Crypt PASSWORD using crypt(3)
Encrypt PASSWORD using crypt(3), with either random or provided SALT.
-P FD Read password from file descriptor FD
-m TYPE Encryption method (des, md5, sha256, or sha512; default is des)
-S SALT
*/
#define FOR_mkpasswd
@ -31,24 +30,26 @@ GLOBALS(
void mkpasswd_main(void)
{
char salt[MAX_SALT_LEN] = {0,};
char salt[32] = {0,};
int i;
if (!TT.m) TT.m = "des";
if (toys.optc == 2) {
if (TT.S) error_exit("duplicate salt");
TT.S = toys.optargs[1];
}
if (-1 == (i = get_salt(salt, TT.m))) error_exit("bad -m");
if (-1 == (i = get_salt(salt, TT.m ? : "des", !TT.S))) error_exit("bad -m");
if (TT.S) {
char *s = TT.S;
char *mirv = strrchr(salt, '$'), *s = TT.S;
// In C locale, isalnum() means [A-Za-Z0-0]
if (mirv) mirv++;
else mirv = salt;
// In C locale, isalnum() means [a-zA-Z0-9]
while (isalnum(*s) || *s == '.' || *s == '/') s++;
if (*s) error_exit("salt not in [./A-Za-z0-9]");
snprintf(salt+i, sizeof(salt)-i, "%s", TT.S);
if (*s || s-TT.S!=strlen(mirv))
error_exit("bad SALT (need [a-zA-Z0-9] len %d)", (int)strlen(mirv));
strcpy(mirv, TT.S);
}
// Because read_password() doesn't have an fd argument
@ -73,5 +74,5 @@ void mkpasswd_main(void)
}
// encrypt & print the password
xprintf("%s\n",crypt(*toys.optargs ? *toys.optargs : toybuf, salt));
xprintf("%s\n", crypt(*toys.optargs ? *toys.optargs : toybuf, salt));
}

View File

@ -4,17 +4,18 @@
*
* See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/chsh.html
USE_CHSH(NEWTOY(chsh, "s:", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
USE_CHSH(NEWTOY(chsh, ">1R:s:a", TOYFLAG_USR|TOYFLAG_BIN|TOYFLAG_STAYROOT))
config CHSH
bool "chsh"
default n
help
usage: chsh [-s SHELL] [USER]
usage: chsh [-s SHELL] [-R CHROOT_DIR] [USER]
Change user's login shell.
-s Use SHELL instead of prompting
-R Act on CHROOT_DIR instead of host
Non-root users can only change their own shell to one listed in /etc/shells.
*/
@ -23,7 +24,7 @@ config CHSH
#include "toys.h"
GLOBALS(
char *s;
char *s, *R;
)
void chsh_main()
@ -36,18 +37,14 @@ void chsh_main()
// Get uid user information, may be discarded later
if ((user = *toys.optargs)) {
passwd_info = xgetpwnam(user);
if (geteuid() && strcmp(passwd_info->pw_name, user))
error_exit("Permission denied\n");
} else {
passwd_info = xgetpwuid(getuid());
user = passwd_info->pw_name;
}
if (strcmp((passwd_info = xgetpwnam(user))->pw_name, user))
if (geteuid()) errno = EPERM, error_exit(0);
} else user = (passwd_info = xgetpwuid(getuid()))->pw_name;
// Get a password, encrypt it, wipe it, and check it
if (mlock(toybuf, sizeof(toybuf))) perror_exit("mlock");
if (!(shadow_info = getspnam(passwd_info->pw_name))) perror_exit("getspnam");
if (read_password(toybuf, sizeof(toybuf), "Password: ")) perror_exit("woaj"); //xexit();
if (read_password(toybuf, sizeof(toybuf), "Password: ")) *toybuf = 0;
if (!(encrypted = crypt(toybuf, shadow_info->sp_pwdp))) perror_exit("crypt");
memset(toybuf, 0, sizeof(toybuf));
munlock(toybuf, sizeof(toybuf)); // prevents memset from "optimizing" away.
@ -57,21 +54,16 @@ void chsh_main()
file = xfopen("/etc/shells", "r");
if (toys.optflags) shell = TT.s;
else {
xprintf("Changing the login shell for %s\n"
"Enter the new value, or press ENTER for default\n"
" Login shell [%s]: ", user, passwd_info->pw_shell);
xprintf("Login shell for %s [%s]:", user, passwd_info->pw_shell);
if (!(shell = xgetline(stdin))) xexit();
if (!*shell) xexit();
}
// Verify supplied shell in /etc/shells, or get default shell
if (strlen(shell))
while ((line = xgetline(file)) && strcmp(shell, line)) free(line);
if (*shell) while ((line = xgetline(file)) && strcmp(shell, line)) free(line);
else do line = xgetline(file); while (line && *line != '/');
if (!line) error_exit("Shell not found in '/etc/shells'");
// Update /etc/passwd
passwd_info->pw_shell = line;
if (-1 == update_password("/etc/passwd", user, NULL)) perror_exit("Failed to remove passwd entry");
file = xfopen("/etc/passwd", "a");
if (putpwent(passwd_info, file)) perror_exit("putwent");
if (!update_password("/etc/passwd", user, line,6)) perror_exit("/etc/passwd");
}

View File

@ -5,7 +5,7 @@
*
* See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/groupadd.html
USE_GROUPADD(NEWTOY(groupadd, "<1>2g#<0S", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
USE_GROUPADD(NEWTOY(groupadd, "<1>2R:g#<0>2147483647S", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
USE_GROUPADD(OLDTOY(addgroup, groupadd, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
config GROUPADD
@ -14,20 +14,19 @@ config GROUPADD
help
usage: groupadd [-S] [-g GID] [USER] GROUP
Add a group or add a user to a group
Add a user to a group, or create a new group.
-g GID Group id
-S Create a system group
-g GID Group id
-R Operate within chroot
-S Create a system group
*/
#define FOR_groupadd
#include "toys.h"
#define GROUP_PATH "/etc/group"
#define SECURE_GROUP_PATH "/etc/gshadow"
GLOBALS(
long gid;
long g;
char *R;
)
/* Add a new group to the system, if GID is given then that is validated
@ -35,67 +34,58 @@ GLOBALS(
* SYSTEM IDs are considered in the range 100 ... 999
* update_group(), updates the entries in /etc/group, /etc/gshadow files
*/
static void new_group()
{
char *entry = NULL;
if (FLAG(g)) {
if (TT.gid > INT_MAX) error_exit("gid should be less than '%d' ", INT_MAX);
if (getgrgid(TT.gid)) error_exit("group '%ld' is in use", TT.gid);
} else {
if (FLAG(S)) TT.gid = CFG_TOYBOX_UID_SYS;
else TT.gid = CFG_TOYBOX_UID_USR;
//find unused gid
while (getgrgid(TT.gid)) TT.gid++;
}
entry = xmprintf("%s:%s:%ld:", *toys.optargs, "x", TT.gid);
update_password(GROUP_PATH, *toys.optargs, entry);
free(entry);
entry = xmprintf("%s:%s::", *toys.optargs, "!");
update_password(SECURE_GROUP_PATH, *toys.optargs, entry);
free(entry);
}
void groupadd_main(void)
{
struct group *grp = NULL;
char *entry = NULL;
struct group *grp = 0;
char *entry = 0, *s, *gfile = "/etc/group", *gsfile = "/etc/gshadow";
int i, len;
if (toys.optflags && toys.optc == 2)
help_exit("options, user and group can't be together");
if (toys.optc == 2) { //add user to group
//toys.optargs[0]- user, toys.optargs[1] - group
xgetpwnam(*toys.optargs);
if (!(grp = getgrnam(toys.optargs[1])))
error_exit("group '%s' does not exist", toys.optargs[1]);
if (!grp->gr_mem) entry = xmprintf("%s", *toys.optargs);
else {
int i;
for (i = 0; grp->gr_mem[i]; i++)
if (!strcmp(grp->gr_mem[i], *toys.optargs)) return;
entry = xstrdup("");
for (i=0; grp->gr_mem[i]; i++) {
entry = xrealloc(entry, strlen(entry) + strlen(grp->gr_mem[i]) + 2);
strcat(entry, grp->gr_mem[i]);
strcat(entry, ",");
}
entry = xrealloc(entry, strlen(entry) + strlen(*toys.optargs) + 1);
strcat(entry, *toys.optargs);
}
update_password(GROUP_PATH, grp->gr_name, entry);
update_password(SECURE_GROUP_PATH, grp->gr_name, entry);
free(entry);
} else { //new group to be created
char *s = *toys.optargs;
/* investigate the group to be created */
if (getgrnam(s)) error_exit("'%s' in use", s);
if (s[strcspn(s, ":/\n")] || strlen(s) > LOGIN_NAME_MAX)
error_exit("bad name");
new_group();
if (TT.R) {
gfile = xmprintf("%s%s", TT.R, gfile);
gsfile = xmprintf("%s%s", TT.R, gsfile);
}
// Add user to group?
if (toys.optc == 2) {
if (FLAG(g)|FLAG(S)) help_exit("No -gS with USER+GROUP");
if (!(grp = getgrnam(s = toys.optargs[1]))) error_exit("no group '%s'", s);
len = strlen(s)+1;
xgetpwnam(s = *toys.optargs);
// Is this user already in this group?
for (i = 0; grp->gr_mem[i]; i++) {
if (!strcmp(grp->gr_mem[i], s)) return;
len += strlen(grp->gr_mem[i])+1;
}
s = entry = xmalloc(len);
for (i = 0;; i++) {
if (i) *s++ = ',';
if (!grp->gr_mem[i]) {
strcpy(s, toys.optargs[1]);
break;
}
s = stpcpy(s, grp->gr_mem[i]);
}
update_password(gfile, grp->gr_name, entry, 3);
update_password(gsfile, grp->gr_name, entry, 3);
free(entry);
return;
}
// create new group
if (getgrnam(s = *toys.optargs)) error_exit("'%s' in use", s);
if (s[strcspn(s, ":/\n")] || strlen(s)>256) error_exit("bad '%s'", s);
// Find next unused GID or confirm selected GID isn't in use
if (!FLAG(g)) {
TT.g = FLAG(S) ? CFG_TOYBOX_UID_SYS : CFG_TOYBOX_UID_USR;
while (getgrgid(TT.g)) TT.g++;
} else if (getgrgid(TT.g)) error_exit("group '%ld' in use", TT.g);
sprintf(toybuf, "%s:x:%ld:", s, TT.g);
update_password(gfile, s, toybuf, 0);
sprintf(toybuf, "%s:!::", s);
update_password(gsfile, s, toybuf, 0);
}

View File

@ -5,7 +5,7 @@
*
* See http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/groupdel.html
USE_GROUPDEL(NEWTOY(groupdel, "<1>2", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
USE_GROUPDEL(NEWTOY(groupdel, "<1>2?", TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
USE_GROUPDEL(OLDTOY(delgroup, groupdel, TOYFLAG_NEEDROOT|TOYFLAG_SBIN))
config GROUPDEL
@ -55,7 +55,7 @@ void groupdel_main(void)
if (CFG_TOYBOX_FREE) endpwent();
}
update_password("/etc/group", grp->gr_name, entry);
update_password("/etc/gshadow", grp->gr_name, entry);
update_password("/etc/group", grp->gr_name, entry, 3);
update_password("/etc/gshadow", grp->gr_name, entry, 3);
if (CFG_TOYBOX_FREE) free(entry);
}

View File

@ -39,6 +39,7 @@ GLOBALS(
long gid;
)
// TODO user exists error
void useradd_main(void)
{
char *s = *toys.optargs, *entry;
@ -119,7 +120,7 @@ void useradd_main(void)
entry = xmprintf("%s:%s:%ld:%ld:%s:%s:%s", pwd.pw_name, pwd.pw_passwd,
(long)pwd.pw_uid, (long)pwd.pw_gid, pwd.pw_gecos, pwd.pw_dir,
pwd.pw_shell);
if (update_password("/etc/passwd", pwd.pw_name, entry)) error_exit("updating passwd file failed");
update_password("/etc/passwd", pwd.pw_name, entry, 0);
free(entry);
if (toys.optflags & FLAG_S)
@ -127,7 +128,7 @@ void useradd_main(void)
(unsigned)(time(NULL))/(24*60*60)); //passwd is not set initially
else entry = xmprintf("%s:!!:%u:0:99999:7:::", pwd.pw_name,
(unsigned)(time(0))/(24*60*60)); //passwd is not set initially
update_password("/etc/shadow", pwd.pw_name, entry);
update_password("/etc/shadow", pwd.pw_name, entry, 0);
free(entry);
// create home dir & copy skel dir to home

View File

@ -92,8 +92,8 @@ void userdel_main(void)
{
struct passwd *pwd = xgetpwnam(*toys.optargs);
update_password("/etc/passwd", pwd->pw_name, NULL);
update_password("/etc/shadow", pwd->pw_name, NULL);
update_password("/etc/passwd", pwd->pw_name, 0, 0);
update_password("/etc/shadow", pwd->pw_name, 0, 0);
// delete the group named USER, and remove user from group.
// could update_password() be used for this?