#include <sys/param.h>
#include <sys/endian.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <db.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pw_scan.h"
#define INSECURE 1
#define SECURE 2
#define PERM_INSECURE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
#define PERM_SECURE (S_IRUSR|S_IWUSR)
#define LEGACY_VERSION(x) _PW_VERSIONED(x, 3)
#define CURRENT_VERSION(x) _PW_VERSIONED(x, 4)
static HASHINFO openinfo = {
4096,
32,
256,
2048 * 1024,
NULL,
BIG_ENDIAN
};
static enum state { FILE_INSECURE, FILE_SECURE, FILE_ORIG } clean;
static struct passwd pwd;
static char *pname;
static char prefix[MAXPATHLEN];
static int is_comment;
static char line[LINE_MAX];
void cleanup(void);
void error(const char *);
void cp(char *, char *, mode_t mode);
void mv(char *, char *);
int scan(FILE *, struct passwd *);
static void usage(void);
int
main(int argc, char *argv[])
{
static char verskey[] = _PWD_VERSION_KEY;
char version = _PWD_CURRENT_VERSION;
DB *dp, *sdp, *pw_db;
DBT data, sdata, key;
FILE *fp, *oldfp;
sigset_t set;
int ch, cnt, ypcnt, makeold, tfd, yp_enabled = 0;
unsigned int len;
uint32_t store;
const char *t;
char *p;
char buf[MAX(MAXPATHLEN, LINE_MAX * 2)];
char sbuf[MAX(MAXPATHLEN, LINE_MAX * 2)];
char buf2[MAXPATHLEN];
char sbuf2[MAXPATHLEN];
char tbuf[1024];
char *username;
u_int method, methoduid;
int Cflag, dflag, iflag;
int nblock = 0;
iflag = dflag = Cflag = 0;
strcpy(prefix, _PATH_PWD);
makeold = 0;
username = NULL;
oldfp = NULL;
while ((ch = getopt(argc, argv, "Cd:iNps:u:v")) != -1)
switch(ch) {
case 'C':
Cflag = 1;
break;
case 'd':
dflag++;
strlcpy(prefix, optarg, sizeof(prefix));
break;
case 'i':
iflag++;
break;
case 'N':
nblock = LOCK_NB;
break;
case 'p':
makeold = 1;
break;
case 's':
openinfo.cachesize = atoi(optarg) * 1024 * 1024;
break;
case 'u':
username = optarg;
break;
case 'v':
break;
default:
usage();
}
argc -= optind;
argv += optind;
if (argc != 1 || (username && (*username == '+' || *username == '-')))
usage();
sigemptyset(&set);
sigaddset(&set, SIGTSTP);
sigaddset(&set, SIGHUP);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGQUIT);
sigaddset(&set, SIGTERM);
(void)sigprocmask(SIG_BLOCK, &set, (sigset_t *)NULL);
(void)umask(0);
pname = *argv;
for (;;) {
struct stat st;
if (!(fp = fopen(pname, "r")))
error(pname);
if (flock(fileno(fp), LOCK_EX|nblock) < 0 && !(dflag && iflag))
error("flock");
if (fstat(fileno(fp), &st) < 0)
error(pname);
if (st.st_nlink != 0)
break;
fclose(fp);
fp = NULL;
}
if (Cflag) {
while (scan(fp, &pwd))
if (!is_comment && strlen(pwd.pw_name) >= MAXLOGNAME) {
warnx("%s: username too long", pwd.pw_name);
exit(1);
}
exit(0);
}
(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
(void)snprintf(sbuf, sizeof(sbuf), "%s/%s.tmp", prefix, _SMP_DB);
if (username) {
int use_version;
(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
(void)snprintf(sbuf2, sizeof(sbuf2), "%s/%s", prefix, _SMP_DB);
clean = FILE_INSECURE;
cp(buf2, buf, PERM_INSECURE);
dp = dbopen(buf,
O_RDWR|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo);
if (dp == NULL)
error(buf);
clean = FILE_SECURE;
cp(sbuf2, sbuf, PERM_SECURE);
sdp = dbopen(sbuf,
O_RDWR|O_EXCL, PERM_SECURE, DB_HASH, &openinfo);
if (sdp == NULL)
error(sbuf);
pw_db = dbopen(_PATH_MP_DB, O_RDONLY, 0, DB_HASH, NULL);
if (!pw_db)
error(_MP_DB);
key.data = verskey;
key.size = sizeof(verskey)-1;
if ((pw_db->get)(pw_db, &key, &data, 0) == 0)
use_version = *(unsigned char *)data.data;
else
use_version = 3;
buf[0] = _PW_VERSIONED(_PW_KEYBYNAME, use_version);
len = strlen(username);
memmove(buf + 1, username, MIN(len, sizeof(buf) - 1));
key.data = (u_char *)buf;
key.size = len + 1;
if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
p = (char *)data.data;
while (*p++)
;
while (*p++)
;
buf[0] = _PW_VERSIONED(_PW_KEYBYUID, use_version);
memmove(buf + 1, p, sizeof(store));
key.data = (u_char *)buf;
key.size = sizeof(store) + 1;
if ((pw_db->get)(pw_db, &key, &data, 0) == 0) {
if (!strcmp(data.data, username))
methoduid = 0;
else
methoduid = R_NOOVERWRITE;
} else {
methoduid = R_NOOVERWRITE;
}
} else {
methoduid = R_NOOVERWRITE;
}
if ((pw_db->close)(pw_db))
error("close pw_db");
method = 0;
} else {
dp = dbopen(buf,
O_RDWR|O_CREAT|O_EXCL, PERM_INSECURE, DB_HASH, &openinfo);
if (dp == NULL)
error(buf);
clean = FILE_INSECURE;
sdp = dbopen(sbuf,
O_RDWR|O_CREAT|O_EXCL, PERM_SECURE, DB_HASH, &openinfo);
if (sdp == NULL)
error(sbuf);
clean = FILE_SECURE;
method = R_NOOVERWRITE;
methoduid = R_NOOVERWRITE;
}
if (makeold) {
(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
if ((tfd = open(buf,
O_WRONLY|O_CREAT|O_EXCL, PERM_INSECURE)) < 0)
error(buf);
if ((oldfp = fdopen(tfd, "w")) == NULL)
error(buf);
clean = FILE_ORIG;
}
if (username == NULL) {
key.data = verskey;
key.size = sizeof(verskey)-1;
data.data = &version;
data.size = 1;
if ((dp->put)(dp, &key, &data, 0) == -1)
error("put");
if ((sdp->put)(sdp, &key, &data, 0) == -1)
error("put");
}
ypcnt = 0;
data.data = (u_char *)buf;
sdata.data = (u_char *)sbuf;
key.data = (u_char *)tbuf;
for (cnt = 1; scan(fp, &pwd); ++cnt) {
if (!is_comment &&
(pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-')) {
yp_enabled = 1;
ypcnt++;
}
if (is_comment)
--cnt;
#define COMPACT(e) t = e; while ((*p++ = *t++));
#define SCALAR(e) store = htonl((uint32_t)(e)); \
memmove(p, &store, sizeof(store)); \
p += sizeof(store);
#define LSCALAR(e) store = HTOL((uint32_t)(e)); \
memmove(p, &store, sizeof(store)); \
p += sizeof(store);
#define HTOL(e) (openinfo.lorder == BYTE_ORDER ? \
(uint32_t)(e) : \
bswap32((uint32_t)(e)))
if (!is_comment &&
(!username || (strcmp(username, pwd.pw_name) == 0))) {
p = buf;
COMPACT(pwd.pw_name);
COMPACT("*");
SCALAR(pwd.pw_uid);
SCALAR(pwd.pw_gid);
SCALAR(pwd.pw_change);
COMPACT(pwd.pw_class);
COMPACT(pwd.pw_gecos);
COMPACT(pwd.pw_dir);
COMPACT(pwd.pw_shell);
SCALAR(pwd.pw_expire);
SCALAR(pwd.pw_fields);
data.size = p - buf;
p = sbuf;
COMPACT(pwd.pw_name);
COMPACT(pwd.pw_passwd);
SCALAR(pwd.pw_uid);
SCALAR(pwd.pw_gid);
SCALAR(pwd.pw_change);
COMPACT(pwd.pw_class);
COMPACT(pwd.pw_gecos);
COMPACT(pwd.pw_dir);
COMPACT(pwd.pw_shell);
SCALAR(pwd.pw_expire);
SCALAR(pwd.pw_fields);
sdata.size = p - sbuf;
tbuf[0] = CURRENT_VERSION(_PW_KEYBYNAME);
len = strlen(pwd.pw_name);
memmove(tbuf + 1, pwd.pw_name, len);
key.size = len + 1;
if ((dp->put)(dp, &key, &data, method) == -1)
error("put");
tbuf[0] = CURRENT_VERSION(_PW_KEYBYNUM);
store = htonl(cnt);
memmove(tbuf + 1, &store, sizeof(store));
key.size = sizeof(store) + 1;
if ((dp->put)(dp, &key, &data, method) == -1)
error("put");
tbuf[0] = CURRENT_VERSION(_PW_KEYBYUID);
store = htonl(pwd.pw_uid);
memmove(tbuf + 1, &store, sizeof(store));
key.size = sizeof(store) + 1;
if ((dp->put)(dp, &key, &data, methoduid) == -1)
error("put");
tbuf[0] = CURRENT_VERSION(_PW_KEYBYNAME);
len = strlen(pwd.pw_name);
memmove(tbuf + 1, pwd.pw_name, len);
key.size = len + 1;
if ((sdp->put)(sdp, &key, &sdata, method) == -1)
error("put");
tbuf[0] = CURRENT_VERSION(_PW_KEYBYNUM);
store = htonl(cnt);
memmove(tbuf + 1, &store, sizeof(store));
key.size = sizeof(store) + 1;
if ((sdp->put)(sdp, &key, &sdata, method) == -1)
error("put");
tbuf[0] = CURRENT_VERSION(_PW_KEYBYUID);
store = htonl(pwd.pw_uid);
memmove(tbuf + 1, &store, sizeof(store));
key.size = sizeof(store) + 1;
if ((sdp->put)(sdp, &key, &sdata, methoduid) == -1)
error("put");
if (pwd.pw_name[0] == '+' || pwd.pw_name[0] == '-') {
tbuf[0] = CURRENT_VERSION(_PW_KEYYPBYNUM);
store = htonl(ypcnt);
memmove(tbuf + 1, &store, sizeof(store));
key.size = sizeof(store) + 1;
if ((dp->put)(dp, &key, &data, method) == -1)
error("put");
if ((sdp->put)(sdp, &key, &sdata, method) == -1)
error("put");
}
}
if (makeold && !is_comment) {
char uidstr[20];
char gidstr[20];
snprintf(uidstr, sizeof(uidstr), "%u", pwd.pw_uid);
snprintf(gidstr, sizeof(gidstr), "%u", pwd.pw_gid);
if (fprintf(oldfp, "%s:*:%s:%s:%s:%s:%s\n",
pwd.pw_name, pwd.pw_fields & _PWF_UID ? uidstr : "",
pwd.pw_fields & _PWF_GID ? gidstr : "",
pwd.pw_gecos, pwd.pw_dir, pwd.pw_shell) < 0)
error("write old");
}
}
if (yp_enabled) {
buf[0] = yp_enabled + 2;
data.size = 1;
key.size = 1;
tbuf[0] = CURRENT_VERSION(_PW_KEYYPENABLED);
if ((dp->put)(dp, &key, &data, method) == -1)
error("put");
if ((sdp->put)(sdp, &key, &data, method) == -1)
error("put");
}
if ((dp->close)(dp) == -1)
error("close");
if ((sdp->close)(sdp) == -1)
error("close");
if (makeold) {
(void)fflush(oldfp);
if (fclose(oldfp) == EOF)
error("close old");
}
(void)fchmod(fileno(fp), S_IRUSR|S_IWUSR);
(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _MP_DB);
mv(buf, buf2);
(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _SMP_DB);
mv(buf, buf2);
if (makeold) {
(void)snprintf(buf2, sizeof(buf2), "%s/%s", prefix, _PASSWD);
(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
mv(buf, buf2);
}
(void)snprintf(buf, sizeof(buf), "%s/%s", prefix, _MASTERPASSWD);
mv(pname, buf);
if (fclose(fp) == EOF)
error("close fp");
exit(0);
}
int
scan(FILE *fp, struct passwd *pw)
{
static int lcnt;
size_t len;
char *p;
p = fgetln(fp, &len);
if (p == NULL)
return (0);
++lcnt;
if (len > 0 && p[len - 1] == '\n')
len--;
if (len >= sizeof(line) - 1) {
warnx("line #%d too long", lcnt);
goto fmt;
}
memcpy(line, p, len);
line[len] = '\0';
for (p = line; *p != '\0'; p++)
if (*p != ' ' && *p != '\t')
break;
if (*p == '#' || *p == '\0') {
is_comment = 1;
return(1);
} else
is_comment = 0;
if (!__pw_scan(line, pw, _PWSCAN_WARN|_PWSCAN_MASTER)) {
warnx("at line #%d", lcnt);
fmt: errno = EFTYPE;
error(pname);
}
return (1);
}
void
cp(char *from, char *to, mode_t mode)
{
static char buf[MAXBSIZE];
int from_fd, rcount, to_fd, wcount;
if ((from_fd = open(from, O_RDONLY, 0)) < 0)
error(from);
if ((to_fd = open(to, O_WRONLY|O_CREAT|O_EXCL, mode)) < 0)
error(to);
while ((rcount = read(from_fd, buf, MAXBSIZE)) > 0) {
wcount = write(to_fd, buf, rcount);
if (rcount != wcount || wcount == -1) {
int sverrno = errno;
(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
errno = sverrno;
error(buf);
}
}
if (rcount < 0) {
int sverrno = errno;
(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
errno = sverrno;
error(buf);
}
}
void
mv(char *from, char *to)
{
char buf[MAXPATHLEN];
char *to_dir;
int to_dir_fd = -1;
if (rename(from, to) != 0 ||
(to_dir = dirname(to)) == NULL ||
(to_dir_fd = open(to_dir, O_RDONLY|O_DIRECTORY)) == -1 ||
fsync(to_dir_fd) != 0) {
int sverrno = errno;
(void)snprintf(buf, sizeof(buf), "%s to %s", from, to);
errno = sverrno;
if (to_dir_fd != -1)
close(to_dir_fd);
error(buf);
}
if (to_dir_fd != -1)
close(to_dir_fd);
}
void
error(const char *name)
{
warn("%s", name);
cleanup();
exit(1);
}
void
cleanup(void)
{
char buf[MAXPATHLEN];
switch(clean) {
case FILE_ORIG:
(void)snprintf(buf, sizeof(buf), "%s.orig", pname);
(void)unlink(buf);
case FILE_SECURE:
(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _SMP_DB);
(void)unlink(buf);
case FILE_INSECURE:
(void)snprintf(buf, sizeof(buf), "%s/%s.tmp", prefix, _MP_DB);
(void)unlink(buf);
}
}
static void
usage(void)
{
(void)fprintf(stderr,
"usage: pwd_mkdb [-CiNp] [-d directory] [-s cachesize] [-u username] file\n");
exit(1);
}