#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <db.h>
#include <syslog.h>
#include <pwd.h>
#include <errno.h>
#include <unistd.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <netgroup.h>
#define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
struct pw_storage {
struct passwd pw;
uid_t uid;
char name[_PW_NAME_LEN + 1];
char pwbuf[_PW_BUF_LEN];
};
static DB *_pw_db;
static struct pw_storage *_pw_storage = MAP_FAILED;
static int _pw_keynum;
static int _pw_stayopen;
static int _pw_flags;
static int __hashpw(DBT *, char *buf, size_t buflen, struct passwd *, int *);
static int __initdb(int);
static struct passwd *_pwhashbyname(const char *name, char *buf,
size_t buflen, struct passwd *pw, int *);
static struct passwd *_pwhashbyuid(uid_t uid, char *buf,
size_t buflen, struct passwd *pw, int *);
static struct passwd *
__get_pw_buf(char **bufp, size_t *buflenp, uid_t uid, const char *name)
{
bool remap = true;
if (_pw_storage != MAP_FAILED) {
if (name != NULL) {
if (strcmp(_pw_storage->name, name) == 0) {
#ifdef PWDEBUG
struct syslog_data sdata = SYSLOG_DATA_INIT;
syslog_r(LOG_CRIT | LOG_CONS, &sdata,
"repeated passwd lookup of user \"%s\"",
name);
#endif
remap = false;
}
} else if (uid != (uid_t)-1) {
if (_pw_storage->uid == uid) {
#ifdef PWDEBUG
struct syslog_data sdata = SYSLOG_DATA_INIT;
syslog_r(LOG_CRIT | LOG_CONS, &sdata,
"repeated passwd lookup of uid %u",
uid);
#endif
remap = false;
}
}
if (remap)
munmap(_pw_storage, sizeof(*_pw_storage));
}
if (remap) {
_pw_storage = mmap(NULL, sizeof(*_pw_storage),
PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
if (_pw_storage == MAP_FAILED)
return NULL;
if (name != NULL)
strlcpy(_pw_storage->name, name, sizeof(_pw_storage->name));
_pw_storage->uid = uid;
}
*bufp = _pw_storage->pwbuf;
*buflenp = sizeof(_pw_storage->pwbuf);
return &_pw_storage->pw;
}
struct passwd *
getpwent(void)
{
char bf[1 + sizeof(_pw_keynum)];
struct passwd *pw, *ret = NULL;
char *pwbuf;
size_t buflen;
DBT key;
if (!_pw_db && !__initdb(0))
goto done;
if ((pw = __get_pw_buf(&pwbuf, &buflen, -1, NULL)) == NULL)
goto done;
++_pw_keynum;
bf[0] = _PW_KEYBYNUM;
bcopy((char *)&_pw_keynum, &bf[1], sizeof(_pw_keynum));
key.data = (u_char *)bf;
key.size = 1 + sizeof(_pw_keynum);
if (__hashpw(&key, pwbuf, buflen, pw, &_pw_flags)) {
ret = pw;
goto done;
}
done:
return (ret);
}
static struct passwd *
_pwhashbyname(const char *name, char *buf, size_t buflen, struct passwd *pw,
int *flagsp)
{
char bf[1 + _PW_NAME_LEN];
size_t len;
DBT key;
int r;
len = strlen(name);
if (len > _PW_NAME_LEN)
return (NULL);
bf[0] = _PW_KEYBYNAME;
bcopy(name, &bf[1], MINIMUM(len, _PW_NAME_LEN));
key.data = (u_char *)bf;
key.size = 1 + MINIMUM(len, _PW_NAME_LEN);
r = __hashpw(&key, buf, buflen, pw, flagsp);
if (r)
return (pw);
return (NULL);
}
static struct passwd *
_pwhashbyuid(uid_t uid, char *buf, size_t buflen, struct passwd *pw,
int *flagsp)
{
char bf[1 + sizeof(int)];
DBT key;
int r;
bf[0] = _PW_KEYBYUID;
bcopy(&uid, &bf[1], sizeof(uid));
key.data = (u_char *)bf;
key.size = 1 + sizeof(uid);
r = __hashpw(&key, buf, buflen, pw, flagsp);
if (r)
return (pw);
return (NULL);
}
static int
getpwnam_internal(const char *name, struct passwd *pw, char *buf, size_t buflen,
struct passwd **pwretp, bool shadow, bool reentrant)
{
struct passwd *pwret = NULL;
int flags = 0, *flagsp = &flags;
int my_errno = 0;
int saved_errno, tmp_errno;
saved_errno = errno;
errno = 0;
if (!_pw_db && !__initdb(shadow))
goto fail;
if (!reentrant) {
if ((pw = __get_pw_buf(&buf, &buflen, -1, name)) == NULL)
goto fail;
flagsp = &_pw_flags;
}
if (!pwret)
pwret = _pwhashbyname(name, buf, buflen, pw, flagsp);
if (!_pw_stayopen) {
tmp_errno = errno;
(void)(_pw_db->close)(_pw_db);
_pw_db = NULL;
errno = tmp_errno;
}
fail:
if (pwretp)
*pwretp = pwret;
if (pwret == NULL)
my_errno = errno;
errno = saved_errno;
return (my_errno);
}
int
getpwnam_r(const char *name, struct passwd *pw, char *buf, size_t buflen,
struct passwd **pwretp)
{
return getpwnam_internal(name, pw, buf, buflen, pwretp, false, true);
}
struct passwd *
getpwnam(const char *name)
{
struct passwd *pw = NULL;
int my_errno;
my_errno = getpwnam_internal(name, NULL, NULL, 0, &pw, false, false);
if (my_errno) {
pw = NULL;
errno = my_errno;
}
return (pw);
}
struct passwd *
getpwnam_shadow(const char *name)
{
struct passwd *pw = NULL;
int my_errno;
my_errno = getpwnam_internal(name, NULL, NULL, 0, &pw, true, false);
if (my_errno) {
pw = NULL;
errno = my_errno;
}
return (pw);
}
static int
getpwuid_internal(uid_t uid, struct passwd *pw, char *buf, size_t buflen,
struct passwd **pwretp, bool shadow, bool reentrant)
{
struct passwd *pwret = NULL;
int flags = 0, *flagsp = &flags;
int my_errno = 0;
int saved_errno, tmp_errno;
saved_errno = errno;
errno = 0;
if (!_pw_db && !__initdb(shadow))
goto fail;
if (!reentrant) {
if ((pw = __get_pw_buf(&buf, &buflen, uid, NULL)) == NULL)
goto fail;
flagsp = &_pw_flags;
}
if (!pwret)
pwret = _pwhashbyuid(uid, buf, buflen, pw, flagsp);
if (!_pw_stayopen) {
tmp_errno = errno;
(void)(_pw_db->close)(_pw_db);
_pw_db = NULL;
errno = tmp_errno;
}
fail:
if (pwretp)
*pwretp = pwret;
if (pwret == NULL)
my_errno = errno;
errno = saved_errno;
return (my_errno);
}
int
getpwuid_r(uid_t uid, struct passwd *pw, char *buf, size_t buflen,
struct passwd **pwretp)
{
return getpwuid_internal(uid, pw, buf, buflen, pwretp, false, true);
}
struct passwd *
getpwuid(uid_t uid)
{
struct passwd *pw = NULL;
int my_errno;
my_errno = getpwuid_internal(uid, NULL, NULL, 0, &pw, false, false);
if (my_errno) {
pw = NULL;
errno = my_errno;
}
return (pw);
}
struct passwd *
getpwuid_shadow(uid_t uid)
{
struct passwd *pw = NULL;
int my_errno;
my_errno = getpwuid_internal(uid, NULL, NULL, 0, &pw, true, false);
if (my_errno) {
pw = NULL;
errno = my_errno;
}
return (pw);
}
int
setpassent(int stayopen)
{
_pw_keynum = 0;
_pw_stayopen = stayopen;
return (1);
}
void
setpwent(void)
{
(void) setpassent(0);
}
void
endpwent(void)
{
int saved_errno;
saved_errno = errno;
_pw_keynum = 0;
if (_pw_db) {
(void)(_pw_db->close)(_pw_db);
_pw_db = NULL;
}
errno = saved_errno;
}
static int
__initdb(int shadow)
{
static int warned;
int saved_errno = errno;
if (shadow)
_pw_db = dbopen(_PATH_SMP_DB, O_RDONLY, 0, DB_HASH, NULL);
if (!_pw_db)
_pw_db = dbopen(_PATH_MP_DB, O_RDONLY, 0, DB_HASH, NULL);
if (_pw_db) {
errno = saved_errno;
return (1);
}
if (!warned) {
saved_errno = errno;
errno = saved_errno;
warned = 1;
}
return (0);
}
static int
__hashpw(DBT *key, char *buf, size_t buflen, struct passwd *pw,
int *flagsp)
{
char *p, *t;
DBT data;
if ((_pw_db->get)(_pw_db, key, &data, 0))
return (0);
p = (char *)data.data;
if (data.size > buflen) {
errno = ERANGE;
return (0);
}
t = buf;
#define EXPAND(e) e = t; while ((*t++ = *p++));
EXPAND(pw->pw_name);
EXPAND(pw->pw_passwd);
bcopy(p, (char *)&pw->pw_uid, sizeof(int));
p += sizeof(int);
bcopy(p, (char *)&pw->pw_gid, sizeof(int));
p += sizeof(int);
bcopy(p, (char *)&pw->pw_change, sizeof(time_t));
p += sizeof(time_t);
EXPAND(pw->pw_class);
EXPAND(pw->pw_gecos);
EXPAND(pw->pw_dir);
EXPAND(pw->pw_shell);
bcopy(p, (char *)&pw->pw_expire, sizeof(time_t));
p += sizeof(time_t);
if (data.size > (p - (char *)data.data)) {
bcopy(p, (char *)flagsp, sizeof(int));
p += sizeof(int);
} else
*flagsp = _PASSWORD_NOUID|_PASSWORD_NOGID;
return (1);
}