Users and permissions: accounts, login, enforced file permissions, setuid, su, passwd, doas
The kernel now checks owner/group/other on open, create, unlink, rename, exec and path search; honours set-user-id and set-group-id; restricts kill, mount, reboot and mknod; keeps supplementary groups; and implements chown. devtmpfs nodes carry owners and modes that chmod/chown keep. Terminals run login, the image has root and schos, X starts as the ordinary user. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EVRhxi9zpdW2uDUN4FVcao
This commit is contained in:
parent
b3f72cd95e
commit
020aa6113c
34 changed files with 1342 additions and 120 deletions
178
cmd/passwd/main.c
Normal file
178
cmd/passwd/main.c
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
//
|
||||
// passwd [USER]
|
||||
//
|
||||
// Change a password. Setuid root, like su and for the same reason a program of
|
||||
// its own: it has to write /etc/shadow, which no one else can.
|
||||
//
|
||||
// Anyone may change their own password after proving they know the old one;
|
||||
// root may change anyone's without it. The new one is stored as a SHA-512
|
||||
// crypt hash with a fresh random salt, and the file is replaced by rename, so
|
||||
// a crash leaves the old shadow or the new one and never half of each.
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <crypt.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <grp.h>
|
||||
#include <pwd.h>
|
||||
#include <shadow.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <termios.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static int
|
||||
read_password(const char *prompt, char *buf, size_t n)
|
||||
{
|
||||
int fd = open("/dev/tty", O_RDWR | O_NOCTTY);
|
||||
struct termios t, old;
|
||||
size_t i = 0;
|
||||
char c;
|
||||
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
if (tcgetattr(fd, &old) == 0) {
|
||||
t = old;
|
||||
t.c_lflag &= ~(ECHO | ECHONL);
|
||||
tcsetattr(fd, TCSAFLUSH, &t);
|
||||
}
|
||||
if (write(fd, prompt, strlen(prompt)) < 0) {}
|
||||
while (read(fd, &c, 1) == 1 && c != '\n' && c != '\r')
|
||||
if (i + 1 < n)
|
||||
buf[i++] = c;
|
||||
buf[i] = 0;
|
||||
tcsetattr(fd, TCSAFLUSH, &old);
|
||||
if (write(fd, "\n", 1) < 0) {}
|
||||
close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
make_salt(char *out)
|
||||
{
|
||||
static const char a[] =
|
||||
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
unsigned char r[16];
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
|
||||
if (fd < 0 || read(fd, r, sizeof r) != (ssize_t)sizeof r) {
|
||||
fprintf(stderr, "passwd: no random numbers\n");
|
||||
exit(1);
|
||||
}
|
||||
close(fd);
|
||||
strcpy(out, "$6$");
|
||||
for (int i = 0; i < 16; i++)
|
||||
out[3 + i] = a[r[i] & 63];
|
||||
out[19] = '$';
|
||||
out[20] = 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
uid_t uid = getuid();
|
||||
struct passwd *me = getpwuid(uid), *pw;
|
||||
// getpwuid and getpwnam share one static buffer: keep the name we were
|
||||
// given, not a pointer into it.
|
||||
char *user = argc > 1 ? argv[1] : (me ? strdup(me->pw_name) : 0);
|
||||
char oldp[256], p1[256], p2[256], salt[24], *hash;
|
||||
FILE *in, *out;
|
||||
char line[1024], tmp[] = "/etc/shadow.new";
|
||||
long days = (long)(time(0) / 86400);
|
||||
int found = 0;
|
||||
|
||||
if (!user || !(pw = getpwnam(user))) {
|
||||
fprintf(stderr, "passwd: unknown user\n");
|
||||
return 1;
|
||||
}
|
||||
{
|
||||
static struct passwd copy;
|
||||
copy = *pw;
|
||||
copy.pw_name = strdup(pw->pw_name);
|
||||
pw = ©
|
||||
}
|
||||
if (geteuid() != 0) {
|
||||
fprintf(stderr, "passwd: must be setuid root\n");
|
||||
return 1;
|
||||
}
|
||||
if (uid != 0 && pw->pw_uid != uid) {
|
||||
fprintf(stderr, "passwd: you may only change your own password\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (uid != 0) {
|
||||
struct spwd *sp = getspnam(pw->pw_name);
|
||||
const char *cur = sp ? sp->sp_pwdp : "";
|
||||
if (cur[0]) {
|
||||
if (read_password("Current password: ", oldp, sizeof oldp) < 0)
|
||||
return 1;
|
||||
char *e = crypt(oldp, cur);
|
||||
memset(oldp, 0, sizeof oldp);
|
||||
if (!e || strcmp(e, cur) != 0) {
|
||||
sleep(2);
|
||||
fprintf(stderr, "passwd: incorrect password\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (read_password("New password: ", p1, sizeof p1) < 0 ||
|
||||
read_password("Retype new password: ", p2, sizeof p2) < 0)
|
||||
return 1;
|
||||
if (strcmp(p1, p2) != 0) {
|
||||
fprintf(stderr, "passwd: the passwords do not match\n");
|
||||
return 1;
|
||||
}
|
||||
make_salt(salt);
|
||||
// An empty password stays empty: crypt of "" is a hash, and the user asked
|
||||
// for no password at all.
|
||||
hash = p1[0] ? crypt(p1, salt) : "";
|
||||
if (!hash) {
|
||||
fprintf(stderr, "passwd: cannot hash\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!(in = fopen("/etc/shadow", "r"))) {
|
||||
fprintf(stderr, "passwd: /etc/shadow: %s\n", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
umask(0077);
|
||||
if (!(out = fopen(tmp, "w"))) {
|
||||
fprintf(stderr, "passwd: %s: %s\n", tmp, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
while (fgets(line, sizeof line, in)) {
|
||||
size_t n = strlen(pw->pw_name);
|
||||
if (strncmp(line, pw->pw_name, n) == 0 && line[n] == ':') {
|
||||
// name:hash:lastchg:min:max:warn:inactive:expire:reserved
|
||||
char *f = strchr(line + n + 1, ':');
|
||||
fprintf(out, "%s:%s:%ld%s", pw->pw_name, hash, days,
|
||||
f && (f = strchr(f + 1, ':')) ? f : ":0:::::\n");
|
||||
found = 1;
|
||||
} else {
|
||||
fputs(line, out);
|
||||
}
|
||||
}
|
||||
fclose(in);
|
||||
if (!found)
|
||||
fprintf(out, "%s:%s:%ld:0:::::\n", pw->pw_name, hash, days);
|
||||
memset(p1, 0, sizeof p1);
|
||||
memset(p2, 0, sizeof p2);
|
||||
|
||||
struct group *g = getgrnam("shadow");
|
||||
fchmod(fileno(out), 0640);
|
||||
if (g)
|
||||
fchown(fileno(out), 0, g->gr_gid);
|
||||
if (fclose(out) != 0 || rename(tmp, "/etc/shadow") < 0) {
|
||||
fprintf(stderr, "passwd: could not write /etc/shadow: %s\n", strerror(errno));
|
||||
unlink(tmp);
|
||||
return 1;
|
||||
}
|
||||
printf("password updated\n");
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in a new issue