#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/param.h>
#include <errno.h>
#include <fcntl.h>
#include <libutil.h>
#include <login_cap.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
static int lc_object_count = 0;
static size_t internal_stringsz = 0;
static char * internal_string = NULL;
static size_t internal_arraysz = 0;
static const char ** internal_array = NULL;
static char path_login_conf[] = _PATH_LOGIN_CONF;
static char *
allocstr(const char *str)
{
char *p;
size_t sz = strlen(str) + 1;
if (sz <= internal_stringsz)
p = strcpy(internal_string, str);
else if ((p = realloc(internal_string, sz)) != NULL) {
internal_stringsz = sz;
internal_string = strcpy(p, str);
}
return p;
}
static const char **
allocarray(size_t sz)
{
static const char **p;
if (sz <= internal_arraysz)
p = internal_array;
else if ((p = reallocarray(internal_array, sz, sizeof(char*))) != NULL) {
internal_arraysz = sz;
internal_array = p;
}
return p;
}
static size_t
strcspn_quote(const char *str, const char *exclude, int *is_quoted)
{
size_t indx = 0;
char quote = 0;
if (str == NULL)
return 0;
if (is_quoted)
*is_quoted = 0;
for (indx = 0; str[indx] != 0; indx++) {
if (quote && str[indx] == quote) {
if (is_quoted)
*is_quoted = 1;
quote = 0;
continue;
}
if (quote == 0 &&
(str[indx] == '\'' || str[indx] == '"')) {
quote = str[indx];
continue;
}
if (quote == 0 &&
strchr(exclude, str[indx]) != NULL)
return indx;
}
return indx;
}
static void
remove_quotes(char *str)
{
static const char *quote_chars = "'\"";
char qc = 0;
int found = 0;
do {
char *loc = NULL;
found = 0;
if (qc == 0) {
size_t indx;
indx = strcspn(str, quote_chars);
if (str[indx] == '\0')
return;
loc = str + indx;
qc = str[indx];
} else {
loc = strchr(str, qc);
if (loc == NULL)
return;
qc = 0;
}
if (loc) {
size_t len = strlen(loc + 1) + 1;
memmove(loc, loc + 1, len);
found = 1;
}
} while (found != 0);
}
static const char **
arrayize(const char *str, const char *chars, int *size)
{
int i;
char *ptr;
const char *cptr;
const char **res = NULL;
for (i = 0, cptr = str; *cptr; i++) {
int count = strcspn_quote(cptr, chars, NULL);
cptr += count;
if (*cptr)
++cptr;
}
if ((ptr = allocstr(str)) != NULL) {
if ((res = allocarray(++i)) == NULL)
free((void *)(uintptr_t)(const void *)str);
else {
i = 0;
while (*ptr) {
int quoted = 0;
int count = strcspn_quote(ptr, chars, "ed);
char *base = ptr;
res[i++] = ptr;
ptr += count;
if (*ptr)
*ptr++ = '\0';
if (quoted) {
remove_quotes(base);
}
}
res[i] = NULL;
}
}
if (size)
*size = i;
return res;
}
void
login_close(login_cap_t * lc)
{
if (lc) {
free(lc->lc_style);
free(lc->lc_class);
free(lc->lc_cap);
free(lc);
if (--lc_object_count == 0) {
free(internal_string);
free(internal_array);
internal_array = NULL;
internal_arraysz = 0;
internal_string = NULL;
internal_stringsz = 0;
cgetclose();
}
}
}
login_cap_t *
login_getclassbyname(char const *name, const struct passwd *pwd)
{
login_cap_t *lc;
if ((lc = calloc(1, sizeof(login_cap_t))) != NULL) {
int r, me, i = 0;
uid_t euid = 0;
gid_t egid = 0;
const char *msg = NULL;
const char *dir;
char userpath[MAXPATHLEN];
static char *login_dbarray[] = { NULL, NULL, NULL };
me = (name != NULL && strcmp(name, LOGIN_MECLASS) == 0);
dir = (!me || pwd == NULL) ? NULL : pwd->pw_dir;
if (dir) {
euid = geteuid();
egid = getegid();
(void)setegid(pwd->pw_gid);
(void)seteuid(pwd->pw_uid);
}
if (dir && snprintf(userpath, MAXPATHLEN, "%s/%s", dir,
_FILE_LOGIN_CONF) < MAXPATHLEN) {
if (_secure_path(userpath, pwd->pw_uid, pwd->pw_gid) != -1)
login_dbarray[i++] = userpath;
}
if (_secure_path(path_login_conf, 0, 0) != -1)
login_dbarray[i++] = path_login_conf;
login_dbarray[i] = NULL;
if (name == NULL || *name == '\0')
name = LOGIN_DEFCLASS;
switch (cgetent(&lc->lc_cap, login_dbarray, name)) {
case -1:
if (me)
break;
if (i == 0)
r = -1;
else if ((r = open(login_dbarray[0], O_RDONLY | O_CLOEXEC)) >= 0)
close(r);
if (r >= 0 || strcmp(name, LOGIN_DEFCLASS) != 0)
syslog(LOG_ERR, "login_getclass: unknown class '%s'", name);
name = LOGIN_DEFCLASS;
msg = "%s: no default/fallback class '%s'";
if (cgetent(&lc->lc_cap, login_dbarray, name) != 0 && r >= 0)
break;
case 0:
if ((lc->lc_class = strdup(name)) != NULL) {
if (dir) {
(void)seteuid(euid);
(void)setegid(egid);
}
++lc_object_count;
return lc;
}
msg = "%s: strdup: %m";
break;
case -2:
msg = "%s: retrieving class information: %m";
break;
case -3:
msg = "%s: 'tc=' reference loop '%s'";
break;
case 1:
msg = "couldn't resolve 'tc=' reference in '%s'";
break;
default:
msg = "%s: unexpected cgetent() error '%s': %m";
break;
}
if (dir) {
(void)seteuid(euid);
(void)setegid(egid);
}
if (msg != NULL)
syslog(LOG_ERR, msg, "login_getclass", name);
free(lc);
}
return NULL;
}
login_cap_t *
login_getclass(const char *cls)
{
return login_getclassbyname(cls, NULL);
}
login_cap_t *
login_getpwclass(const struct passwd *pwd)
{
const char *cls = NULL;
if (pwd != NULL) {
cls = pwd->pw_class;
if (cls == NULL || *cls == '\0')
cls = (pwd->pw_uid == 0) ? LOGIN_DEFROOTCLASS : LOGIN_DEFCLASS;
}
return login_getclassbyname(cls, pwd);
}
login_cap_t *
login_getuserclass(const struct passwd *pwd)
{
return login_getclassbyname(LOGIN_MECLASS, pwd);
}
const char *
login_getcapstr(login_cap_t *lc, const char *cap, const char *def, const char *error)
{
char *res;
int ret;
if (lc == NULL || cap == NULL || lc->lc_cap == NULL || *cap == '\0')
return def;
if ((ret = cgetstr(lc->lc_cap, cap, &res)) == -1)
return def;
return (ret >= 0) ? res : error;
}
const char **
login_getcaplist(login_cap_t *lc, const char *cap, const char *chars)
{
const char *lstring;
if (chars == NULL)
chars = ", \t";
if ((lstring = login_getcapstr(lc, cap, NULL, NULL)) != NULL)
return arrayize(lstring, chars, NULL);
return NULL;
}
const char *
login_getpath(login_cap_t *lc, const char *cap, const char *error)
{
const char *str;
char *ptr;
int count;
str = login_getcapstr(lc, cap, NULL, NULL);
if (str == NULL)
return error;
ptr = __DECONST(char *, str);
while (*ptr) {
count = strcspn(ptr, ", \t");
ptr += count;
if (*ptr)
*ptr++ = ':';
}
return str;
}
static int
isinfinite(const char *s)
{
static const char *infs[] = {
"infinity",
"inf",
"unlimited",
"unlimit",
"-1",
NULL
};
const char **i = &infs[0];
while (*i != NULL) {
if (strcasecmp(s, *i) == 0)
return 1;
++i;
}
return 0;
}
static u_quad_t
rmultiply(u_quad_t n1, u_quad_t n2)
{
u_quad_t m, r;
int b1, b2;
static int bpw = 0;
if (n1 == 0 || n2 == 0)
return 0;
if (n1 == 1)
return n2;
if (n2 == 1)
return n1;
if (!bpw) {
bpw = sizeof(u_quad_t) * 8;
while (((u_quad_t)1 << (bpw-1)) == 0)
--bpw;
}
for (b1 = bpw; (((u_quad_t)1 << (b1-1)) & n1) == 0; --b1)
;
for (b2 = bpw; (((u_quad_t)1 << (b2-1)) & n2) == 0; --b2)
;
if (b1 + b2 - 2 > bpw) {
errno = ERANGE;
return (UQUAD_MAX);
}
m = (n1 >> 1) * (n2 >> 1);
if (m >= ((u_quad_t)1 << (bpw-2))) {
errno = ERANGE;
return (UQUAD_MAX);
}
m *= 4;
r = (n1 & n2 & 1)
+ (n2 & 1) * (n1 & ~(u_quad_t)1)
+ (n1 & 1) * (n2 & ~(u_quad_t)1);
if ((u_quad_t)(m + r) < m) {
errno = ERANGE;
return (UQUAD_MAX);
}
m += r;
return (m);
}
rlim_t
login_getcaptime(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error)
{
char *res, *ep, *oval;
int r;
rlim_t tot;
errno = 0;
if (lc == NULL || lc->lc_cap == NULL)
return def;
if ((r = cgetstr(lc->lc_cap, cap, &res)) == -1)
return def;
else if (r < 0)
return error;
if (isinfinite(res))
return RLIM_INFINITY;
errno = 0;
tot = 0;
oval = res;
while (*res) {
rlim_t tim = strtoq(res, &ep, 0);
rlim_t mult = 1;
if (ep == NULL || ep == res || errno != 0) {
invalid:
syslog(LOG_WARNING, "login_getcaptime: class '%s' bad value %s=%s",
lc->lc_class, cap, oval);
errno = ERANGE;
return error;
}
switch (*ep++) {
case 0:
ep--;
break;
case 's': case 'S':
break;
case 'm': case 'M':
mult = 60;
break;
case 'h': case 'H':
mult = 60L * 60L;
break;
case 'd': case 'D':
mult = 60L * 60L * 24L;
break;
case 'w': case 'W':
mult = 60L * 60L * 24L * 7L;
break;
case 'y': case 'Y':
mult = 60L * 60L * 24L * 365L;
break;
default:
goto invalid;
}
res = ep;
tot += rmultiply(tim, mult);
if (errno)
goto invalid;
}
return tot;
}
rlim_t
login_getcapnum(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error)
{
char *ep, *res;
int r;
rlim_t val;
if (lc == NULL || lc->lc_cap == NULL)
return def;
r = cgetstr(lc->lc_cap, cap, &res);
if (r == -1) {
long lval;
if ((r = cgetnum(lc->lc_cap, cap, &lval)) == -1)
return def;
else if (r < 0)
return error;
else
return (rlim_t)lval;
} else if (r < 0)
return error;
if (isinfinite(res))
return RLIM_INFINITY;
errno = 0;
val = strtoq(res, &ep, 0);
if (ep == NULL || ep == res || errno != 0) {
syslog(LOG_WARNING, "login_getcapnum: class '%s' bad value %s=%s",
lc->lc_class, cap, res);
errno = ERANGE;
return error;
}
return val;
}
int
login_getcapenum(login_cap_t *lc, const char *cap, const char * const *values)
{
int ret, i;
char *cand;
const char * const *val;
if (lc == NULL || lc->lc_cap == NULL || cap == NULL || *cap == '\0')
return (-4);
ret = cgetstr(lc->lc_cap, cap, &cand);
if (ret == -1)
return (-2);
else if (ret < 0)
return (-3);
ret = -1;
for (i = 0, val = values; *val != NULL; val++)
if (strcmp(cand, *val) == 0) {
ret = i;
break;
}
free(cand);
return (ret);
}
rlim_t
login_getcapsize(login_cap_t *lc, const char *cap, rlim_t def, rlim_t error)
{
char *ep, *res, *oval;
int r;
rlim_t tot;
if (lc == NULL || lc->lc_cap == NULL)
return def;
if ((r = cgetstr(lc->lc_cap, cap, &res)) == -1)
return def;
else if (r < 0)
return error;
if (isinfinite(res))
return RLIM_INFINITY;
errno = 0;
tot = 0;
oval = res;
while (*res) {
rlim_t siz = strtoq(res, &ep, 0);
rlim_t mult = 1;
if (ep == NULL || ep == res || errno != 0) {
invalid:
syslog(LOG_WARNING, "login_getcapsize: class '%s' bad value %s=%s",
lc->lc_class, cap, oval);
errno = ERANGE;
return error;
}
switch (*ep++) {
case 0:
ep--;
break;
case 'b': case 'B':
mult = 512;
break;
case 'k': case 'K':
mult = 1024;
break;
case 'm': case 'M':
mult = 1024 * 1024;
break;
case 'g': case 'G':
mult = 1024 * 1024 * 1024;
break;
case 't': case 'T':
mult = 1024LL * 1024LL * 1024LL * 1024LL;
break;
default:
goto invalid;
}
res = ep;
tot += rmultiply(siz, mult);
if (errno)
goto invalid;
}
return tot;
}
int
login_getcapbool(login_cap_t *lc, const char *cap, int def)
{
if (lc == NULL || lc->lc_cap == NULL)
return def;
return (cgetcap(lc->lc_cap, cap, ':') != NULL);
}
const char *
login_getstyle(login_cap_t *lc, const char *style, const char *auth)
{
int i;
const char **authtypes = NULL;
char *auths= NULL;
char realauth[64];
static const char *defauthtypes[] = { LOGIN_DEFSTYLE, NULL };
if (auth != NULL && *auth != '\0') {
if (snprintf(realauth, sizeof realauth, "auth-%s", auth) < (int)sizeof(realauth))
authtypes = login_getcaplist(lc, realauth, NULL);
}
if (authtypes == NULL)
authtypes = login_getcaplist(lc, "auth", NULL);
if (authtypes == NULL)
authtypes = defauthtypes;
i = 0;
if (style != NULL && *style != '\0') {
while (authtypes[i] != NULL && strcmp(style, authtypes[i]) != 0)
i++;
}
lc->lc_style = NULL;
if (authtypes[i] != NULL && (auths = strdup(authtypes[i])) != NULL)
lc->lc_style = auths;
if (lc->lc_style != NULL)
lc->lc_style = strdup(lc->lc_style);
return lc->lc_style;
}