// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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; }