ubase/passwd.c
Mario Rugiero 140efda3a2 passwd: fix crashes for unencrypted passwords starting with 'x'.
When deciding where the previous hash should come from, is is
assumed that 'x' started strings all mean to look in shadow.
This is probably harmless in practice, since modern Linux still
use only hashes instead of raw passwords.
However, this is more robust, and more importantly, it is more
consistent with the previous check, which explicitly tests for
the string to be "x".
2019-01-30 11:58:55 +00:00

289 lines
5.6 KiB
C

/* See LICENSE file for copyright and license details. */
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pwd.h>
#include <shadow.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "config.h"
#include "passwd.h"
#include "text.h"
#include "util.h"
static FILE *
spw_get_file(const char *user)
{
FILE *fp = NULL;
char file[PATH_MAX];
int r;
r = snprintf(file, sizeof(file), "/etc/tcb/%s/shadow", user);
if (r < 0 || (size_t)r >= sizeof(file))
eprintf("snprintf:");
fp = fopen(file, "r+");
if (!fp)
fp = fopen("/etc/shadow", "r+");
return fp;
}
static int
spw_write_file(FILE *fp, const struct spwd *spw, char *pwhash)
{
struct spwd *spwent;
int r = -1, w = 0;
FILE *tfp = NULL;
/* write to temporary file. */
tfp = tmpfile();
if (!tfp) {
weprintf("tmpfile:");
goto cleanup;
}
while ((spwent = fgetspent(fp))) {
/* update entry on name match */
if (strcmp(spwent->sp_namp, spw->sp_namp) == 0) {
spwent->sp_pwdp = pwhash;
w++;
}
errno = 0;
if (putspent(spwent, tfp) == -1) {
weprintf("putspent:");
goto cleanup;
}
}
if (!w) {
weprintf("shadow: no matching entry to write to\n");
goto cleanup;
}
fflush(tfp);
if (fseek(fp, 0, SEEK_SET) == -1 || fseek(tfp, 0, SEEK_SET) == -1) {
weprintf("fseek:");
goto cleanup;
}
/* write temporary file to (tcb) shadow file */
concat(tfp, "tmpfile", fp, "shadow");
ftruncate(fileno(fp), ftell(tfp));
r = 0; /* success */
cleanup:
if (tfp)
fclose(tfp);
return r;
}
static int
pw_write_file(FILE *fp, const struct passwd *pw, char *pwhash) {
struct passwd *pwent;
int r = -1, w = 0;
FILE *tfp = NULL;
/* write to temporary file. */
tfp = tmpfile();
if (!tfp) {
weprintf("tmpfile:");
goto cleanup;
}
while ((pwent = fgetpwent(fp))) {
/* update entry on name match */
if (strcmp(pwent->pw_name, pw->pw_name) == 0) {
pwent->pw_passwd = pwhash;
w++;
}
errno = 0;
if (putpwent(pwent, tfp) == -1) {
weprintf("putpwent:");
goto cleanup;
}
}
if (!w) {
weprintf("passwd: no matching entry to write to\n");
goto cleanup;
}
fflush(tfp);
if (fseek(fp, 0, SEEK_SET) == -1 || fseek(tfp, 0, SEEK_SET) == -1) {
weprintf("fseek:");
goto cleanup;
}
/* write to passwd file. */
concat(tfp, "tmpfile", fp, "passwd");
ftruncate(fileno(fp), ftell(tfp));
r = 0; /* success */
cleanup:
if (tfp)
fclose(tfp);
return r;
}
/* generates a random base64-encoded salt string of length 16 */
static void
gensalt(char *s)
{
static const char b64[] = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
uint8_t buf[12];
uint32_t n;
int i;
if (syscall(SYS_getrandom, buf, sizeof(buf), 0) < 0)
eprintf("getrandom:");
for (i = 0; i < 12; i += 3) {
n = buf[i] << 16 | buf[i+1] << 8 | buf[i+2];
*s++ = b64[n%64]; n /= 64;
*s++ = b64[n%64]; n /= 64;
*s++ = b64[n%64]; n /= 64;
*s++ = b64[n];
}
*s++ = '\0';
}
static void
usage(void)
{
eprintf("usage: %s [username]\n", argv0);
}
int
main(int argc, char *argv[])
{
char *cryptpass1 = NULL, *cryptpass2 = NULL, *cryptpass3 = NULL;
char *inpass, *p, *prevhash = NULL, salt[sizeof(PW_CIPHER) + 16] = PW_CIPHER;
struct passwd *pw;
struct spwd *spw = NULL;
FILE *fp = NULL;
int r = -1, status = 1;
ARGBEGIN {
default:
usage();
} ARGEND;
pw_init();
umask(077);
errno = 0;
if (argc == 0)
pw = getpwuid(getuid());
else
pw = getpwnam(argv[0]);
if (!pw) {
if (errno)
eprintf("getpwnam: %s:", argv[0]);
else
eprintf("who are you?\n");
}
/* is using shadow entry ? */
if (pw->pw_passwd[0] == 'x' && pw->pw_passwd[1] == '\0') {
errno = 0;
spw = getspnam(pw->pw_name);
if (!spw) {
if (errno)
eprintf("getspnam: %s:", pw->pw_name);
else
eprintf("who are you?\n");
}
}
/* Flush pending input */
ioctl(0, TCFLSH, (void *)0);
if (getuid() == 0) {
goto newpass;
} else {
if (pw->pw_passwd[0] == '!' ||
pw->pw_passwd[0] == '*')
eprintf("denied\n");
if (pw->pw_passwd[0] == '\0') {
goto newpass;
}
if (pw->pw_passwd[0] == 'x' &&
pw->pw_passwd[1] == '\0')
prevhash = spw->sp_pwdp;
else
prevhash = pw->pw_passwd;
}
printf("Changing password for %s\n", pw->pw_name);
inpass = getpass("Old password: ");
if (!inpass)
eprintf("getpass:");
if (inpass[0] == '\0')
eprintf("no password supplied\n");
p = crypt(inpass, prevhash);
if (!p)
eprintf("crypt:");
cryptpass1 = estrdup(p);
if (strcmp(cryptpass1, prevhash) != 0)
eprintf("incorrect password\n");
newpass:
inpass = getpass("Enter new password: ");
if (!inpass)
eprintf("getpass:");
if (inpass[0] == '\0')
eprintf("no password supplied\n");
if(prevhash) {
p = crypt(inpass, prevhash);
if (!p)
eprintf("crypt:");
if (cryptpass1 && strcmp(cryptpass1, p) == 0)
eprintf("password left unchanged\n");
}
gensalt(salt + strlen(salt));
p = crypt(inpass, salt);
if (!p)
eprintf("crypt:");
cryptpass2 = estrdup(p);
/* Flush pending input */
ioctl(0, TCFLSH, (void *)0);
inpass = getpass("Retype new password: ");
if (!inpass)
eprintf("getpass:");
if (inpass[0] == '\0')
eprintf("no password supplied\n");
p = crypt(inpass, salt);
if (!p)
eprintf("crypt:");
cryptpass3 = estrdup(p);
if (strcmp(cryptpass2, cryptpass3) != 0)
eprintf("passwords don't match\n");
fp = spw_get_file(pw->pw_name);
if (fp) {
r = spw_write_file(fp, spw, cryptpass3);
} else {
fp = fopen("/etc/passwd", "r+");
if (fp)
r = pw_write_file(fp, pw, cryptpass3);
else
weprintf("fopen:");
}
if (!r)
status = 0;
if (fp)
fclose(fp);
free(cryptpass3);
free(cryptpass2);
free(cryptpass1);
return status;
}