#include <sys/time.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <login_cap.h>
#define MAXSPOOLSIZE (8*1024)
struct rmfiles {
struct rmfiles *next;
char *file;
};
struct authopts {
struct authopts *next;
char *opt;
};
struct authdata {
struct authdata *next;
void *ptr;
size_t len;
};
struct auth_session_t {
char *name;
char *style;
char *class;
char *service;
char *challenge;
int flags;
struct passwd *pwd;
struct timeval now;
int state;
struct rmfiles *rmlist;
struct authopts *optlist;
struct authdata *data;
char spool[MAXSPOOLSIZE];
int index;
int fd;
va_list ap0;
va_list ap;
};
#define AF_INTERACTIVE 0x0001
#include <bsd_auth.h>
static void _add_rmlist(auth_session_t *, char *);
static void _auth_spool(auth_session_t *, int);
static void _recv_fd(auth_session_t *, int);
static char *_auth_next_arg(auth_session_t *);
static char * const auth_environ[] = {
"PATH=" _PATH_DEFPATH,
"SHELL=" _PATH_BSHELL,
NULL,
};
static char defservice[] = LOGIN_DEFSERVICE;
static va_list nilap;
void auth_setstate(auth_session_t *as, int s){ as->state = s; }
void auth_set_va_list(auth_session_t *as, va_list ap) { va_copy(as->ap, ap); }
int auth_getstate(auth_session_t *as) { return (as->state); }
struct passwd *auth_getpwd(auth_session_t *as) { return (as->pwd); }
DEF_WEAK(auth_setstate);
DEF_WEAK(auth_set_va_list);
DEF_WEAK(auth_getstate);
DEF_WEAK(auth_getpwd);
auth_session_t *
auth_open(void)
{
auth_session_t *as;
if ((as = calloc(1, sizeof(auth_session_t))) != NULL) {
as->service = defservice;
as->fd = -1;
}
return (as);
}
DEF_WEAK(auth_open);
void
auth_clean(auth_session_t *as)
{
struct rmfiles *rm;
struct authdata *data;
as->state = 0;
auth_clrenv(as);
while ((rm = as->rmlist) != NULL) {
as->rmlist = rm->next;
unlink(rm->file);
free(rm);
}
while ((data = as->data) != NULL) {
if (as->data->len)
explicit_bzero(as->data->ptr, as->data->len);
as->data = data->next;
free(data);
}
auth_setitem(as, AUTHV_ALL, NULL);
if (as->pwd != NULL) {
explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
free(as->pwd);
as->pwd = NULL;
}
if (as->fd != -1) {
close(as->fd);
as->fd = -1;
}
}
DEF_WEAK(auth_clean);
int
auth_close(auth_session_t *as)
{
struct rmfiles *rm;
struct authopts *opt;
struct authdata *data;
int s;
s = as->state & AUTH_ALLOW;
if (s == 0)
as->index = 0;
auth_setenv(as);
while ((rm = as->rmlist) != NULL) {
as->rmlist = rm->next;
if (s == 0)
unlink(rm->file);
free(rm);
}
while ((opt = as->optlist) != NULL) {
as->optlist = opt->next;
free(opt);
}
while ((data = as->data) != NULL) {
if (as->data->len)
explicit_bzero(as->data->ptr, as->data->len);
as->data = data->next;
free(data);
}
if (as->pwd != NULL) {
explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
free(as->pwd);
as->pwd = NULL;
}
if (as->service && as->service != defservice)
free(as->service);
free(as->challenge);
free(as->class);
free(as->style);
free(as->name);
free(as);
return (s);
}
DEF_WEAK(auth_close);
char *
auth_challenge(auth_session_t *as)
{
char path[PATH_MAX];
int len;
if (as == NULL || as->style == NULL || as->name == NULL ||
!_auth_validuser(as->name))
return (NULL);
len = snprintf(path, sizeof(path), _PATH_AUTHPROG "%s", as->style);
if (len < 0 || len >= sizeof(path))
return (NULL);
as->state = 0;
free(as->challenge);
as->challenge = NULL;
auth_call(as, path, as->style, "-s", "challenge", "--", as->name,
as->class, (char *)NULL);
if (as->state & AUTH_CHALLENGE)
as->challenge = auth_getvalue(as, "challenge");
as->state = 0;
as->index = 0;
return (as->challenge);
}
DEF_WEAK(auth_challenge);
void
auth_setenv(auth_session_t *as)
{
char *line, *name;
for (line = as->spool; line < as->spool + as->index;) {
if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
line[0] = 'd'; line[1] = 'i'; line[2] = 'd';
line += sizeof(BI_SETENV) - 1;
for (name = line;
isblank((unsigned char)*name); ++name)
;
for (line = name;
*line && !isblank((unsigned char)*line);
++line)
;
if (*line)
*line++ = '\0';
for (; isblank((unsigned char)*line); ++line)
;
if (*line != '\0' && setenv(name, line, 1))
warn("setenv(%s, %s)", name, line);
}
} else
if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
line[2] = 'd'; line[3] = 'i'; line[4] = 'd';
line += sizeof(BI_UNSETENV) - 1;
for (name = line;
isblank((unsigned char)*name); ++name)
;
for (line = name;
*line && !isblank((unsigned char)*line);
++line)
;
if (*line)
*line++ = '\0';
unsetenv(name);
}
}
while (*line++)
;
}
}
DEF_WEAK(auth_setenv);
void
auth_clrenv(auth_session_t *as)
{
char *line;
for (line = as->spool; line < as->spool + as->index;) {
if (!strncasecmp(line, BI_SETENV, sizeof(BI_SETENV)-1)) {
if (isblank((unsigned char)line[sizeof(BI_SETENV) - 1])) {
line[0] = 'i'; line[1] = 'g'; line[2] = 'n';
}
} else
if (!strncasecmp(line, BI_UNSETENV, sizeof(BI_UNSETENV)-1)) {
if (isblank((unsigned char)line[sizeof(BI_UNSETENV) - 1])) {
line[2] = 'i'; line[3] = 'g'; line[4] = 'n';
}
}
while (*line++)
;
}
}
DEF_WEAK(auth_clrenv);
char *
auth_getitem(auth_session_t *as, auth_item_t item)
{
if (as != NULL) {
switch (item) {
case AUTHV_CHALLENGE:
return (as->challenge);
case AUTHV_CLASS:
return (as->class);
case AUTHV_NAME:
return (as->name);
case AUTHV_SERVICE:
return (as->service ? as->service : defservice);
case AUTHV_STYLE:
return (as->style);
case AUTHV_INTERACTIVE:
return ((as->flags & AF_INTERACTIVE) ? "True" : NULL);
default:
break;
}
}
return (NULL);
}
DEF_WEAK(auth_getitem);
int
auth_setitem(auth_session_t *as, auth_item_t item, char *value)
{
if (as == NULL) {
errno = EINVAL;
return (-1);
}
switch (item) {
case AUTHV_ALL:
if (value != NULL) {
errno = EINVAL;
return (-1);
}
auth_setitem(as, AUTHV_CHALLENGE, NULL);
auth_setitem(as, AUTHV_CLASS, NULL);
auth_setitem(as, AUTHV_NAME, NULL);
auth_setitem(as, AUTHV_SERVICE, NULL);
auth_setitem(as, AUTHV_STYLE, NULL);
auth_setitem(as, AUTHV_INTERACTIVE, NULL);
return (0);
case AUTHV_CHALLENGE:
if (value == as->challenge)
return (0);
if (value != NULL && (value = strdup(value)) == NULL)
return (-1);
free(as->challenge);
as->challenge = value;
return (0);
case AUTHV_CLASS:
if (value == as->class)
return (0);
if (value != NULL && (value = strdup(value)) == NULL)
return (-1);
free(as->class);
as->class = value;
return (0);
case AUTHV_NAME:
if (value == as->name)
return (0);
if (value != NULL && !_auth_validuser(value)) {
errno = EINVAL;
return (-1);
}
if (value != NULL && (value = strdup(value)) == NULL)
return (-1);
free(as->name);
as->name = value;
return (0);
case AUTHV_SERVICE:
if (value == as->service)
return (0);
if (value == NULL || strcmp(value, defservice) == 0)
value = defservice;
else if ((value = strdup(value)) == NULL)
return (-1);
if (as->service && as->service != defservice)
free(as->service);
as->service = value;
return (0);
case AUTHV_STYLE:
if (value == as->style)
return (0);
if (value == NULL || strchr(value, '/') != NULL ||
(value = strdup(value)) == NULL)
return (-1);
free(as->style);
as->style = value;
return (0);
case AUTHV_INTERACTIVE:
if (value == NULL)
as->flags &= ~AF_INTERACTIVE;
else
as->flags |= ~AF_INTERACTIVE;
return (0);
default:
errno = EINVAL;
return (-1);
}
}
DEF_WEAK(auth_setitem);
int
auth_setoption(auth_session_t *as, char *n, char *v)
{
struct authopts *opt;
size_t len = strlen(n) + strlen(v) + 2;
int ret;
if ((opt = malloc(sizeof(*opt) + len)) == NULL)
return (-1);
opt->opt = (char *)(opt + 1);
ret = snprintf(opt->opt, len, "%s=%s", n, v);
if (ret < 0 || ret >= len) {
free(opt);
errno = ENAMETOOLONG;
return (-1);
}
opt->next = as->optlist;
as->optlist = opt;
return(0);
}
DEF_WEAK(auth_setoption);
void
auth_clroptions(auth_session_t *as)
{
struct authopts *opt;
while ((opt = as->optlist) != NULL) {
as->optlist = opt->next;
free(opt);
}
}
DEF_WEAK(auth_clroptions);
void
auth_clroption(auth_session_t *as, char *option)
{
struct authopts *opt, *oopt;
size_t len;
len = strlen(option);
if ((opt = as->optlist) == NULL)
return;
if (strncmp(opt->opt, option, len) == 0 &&
(opt->opt[len] == '=' || opt->opt[len] == '\0')) {
as->optlist = opt->next;
free(opt);
return;
}
while ((oopt = opt->next) != NULL) {
if (strncmp(oopt->opt, option, len) == 0 &&
(oopt->opt[len] == '=' || oopt->opt[len] == '\0')) {
opt->next = oopt->next;
free(oopt);
return;
}
opt = oopt;
}
}
DEF_WEAK(auth_clroption);
int
auth_setdata(auth_session_t *as, void *ptr, size_t len)
{
struct authdata *data, *dp;
if (len <= 0)
return (0);
if ((data = malloc(sizeof(*data) + len)) == NULL)
return (-1);
data->next = NULL;
data->len = len;
data->ptr = data + 1;
memcpy(data->ptr, ptr, len);
if (as->data == NULL)
as->data = data;
else {
for (dp = as->data; dp->next != NULL; dp = dp->next)
;
dp->next = data;
}
return (0);
}
DEF_WEAK(auth_setdata);
int
auth_setpwd(auth_session_t *as, struct passwd *pwd)
{
struct passwd pwstore;
char *instance, pwbuf[_PW_BUF_LEN];
if (pwd == NULL && as->pwd == NULL && as->name == NULL)
return (-1);
if (pwd == NULL) {
if (as->name == NULL)
return (0);
getpwnam_r(as->name, &pwstore, pwbuf, sizeof(pwbuf), &pwd);
if (pwd == NULL) {
instance = strchr(as->name, '/');
if (instance == NULL)
return (as->pwd ? 0 : 1);
if (strcmp(instance, "/root") == 0) {
getpwnam_r(instance + 1, &pwstore, pwbuf,
sizeof(pwbuf), &pwd);
}
if (pwd == NULL)
return (as->pwd ? 0 : 1);
}
}
if ((pwd = pw_dup(pwd)) == NULL)
return (-1);
if (as->pwd) {
explicit_bzero(as->pwd->pw_passwd, strlen(as->pwd->pw_passwd));
free(as->pwd);
}
as->pwd = pwd;
return (0);
}
DEF_WEAK(auth_setpwd);
char *
auth_getvalue(auth_session_t *as, char *what)
{
char *line, *v, *value;
int n, len;
len = strlen(what);
for (line = as->spool; line < as->spool + as->index;) {
if (strncasecmp(line, BI_VALUE, sizeof(BI_VALUE)-1) != 0)
goto next;
line += sizeof(BI_VALUE) - 1;
if (!isblank((unsigned char)*line))
goto next;
while (isblank((unsigned char)*++line))
;
if (strncmp(line, what, len) != 0 ||
!isblank((unsigned char)line[len]))
goto next;
line += len;
while (isblank((unsigned char)*++line))
;
value = strdup(line);
if (value == NULL)
return (NULL);
for (line = v = value; *line; ++line) {
if (*line == '\\') {
switch (*++line) {
case 'r':
*v++ = '\r';
break;
case 'n':
*v++ = '\n';
break;
case 't':
*v++ = '\t';
break;
case '0': case '1': case '2':
case '3': case '4': case '5':
case '6': case '7':
n = *line - '0';
if (isdigit((unsigned char)line[1])) {
++line;
n <<= 3;
n |= *line-'0';
}
if (isdigit((unsigned char)line[1])) {
++line;
n <<= 3;
n |= *line-'0';
}
break;
default:
*v++ = *line;
break;
}
} else
*v++ = *line;
}
*v = '\0';
return (value);
next:
while (*line++)
;
}
return (NULL);
}
DEF_WEAK(auth_getvalue);
quad_t
auth_check_expire(auth_session_t *as)
{
if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
as->state &= ~AUTH_ALLOW;
as->state |= AUTH_EXPIRED;
return (-1);
}
if (as->pwd == NULL)
return (0);
if (as->pwd && (quad_t)as->pwd->pw_expire != 0) {
if (as->now.tv_sec == 0)
WRAP(gettimeofday)(&as->now, NULL);
if ((quad_t)as->now.tv_sec >= (quad_t)as->pwd->pw_expire) {
as->state &= ~AUTH_ALLOW;
as->state |= AUTH_EXPIRED;
}
if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_expire)
return (-1);
return ((quad_t)as->pwd->pw_expire - (quad_t)as->now.tv_sec);
}
return (0);
}
DEF_WEAK(auth_check_expire);
quad_t
auth_check_change(auth_session_t *as)
{
if (as->pwd == NULL && auth_setpwd(as, NULL) < 0) {
as->state &= ~AUTH_ALLOW;
as->state |= AUTH_PWEXPIRED;
return (-1);
}
if (as->pwd == NULL)
return (0);
if (as->pwd && (quad_t)as->pwd->pw_change) {
if (as->now.tv_sec == 0)
WRAP(gettimeofday)(&as->now, NULL);
if (as->now.tv_sec >= (quad_t)as->pwd->pw_change) {
as->state &= ~AUTH_ALLOW;
as->state |= AUTH_PWEXPIRED;
}
if ((quad_t)as->now.tv_sec == (quad_t)as->pwd->pw_change)
return (-1);
return ((quad_t)as->pwd->pw_change - (quad_t)as->now.tv_sec);
}
return (0);
}
DEF_WEAK(auth_check_change);
int
auth_call(auth_session_t *as, char *path, ...)
{
char *line;
struct authdata *data;
struct authopts *opt;
pid_t pid;
int status;
int okay;
int pfd[2];
int argc;
char *argv[64];
#define Nargc (sizeof(argv)/sizeof(argv[0]))
va_start(as->ap0, path);
argc = 0;
if ((argv[argc] = _auth_next_arg(as)) != NULL)
++argc;
if (as->fd != -1) {
argv[argc++] = "-v";
argv[argc++] = "fd=4";
}
for (opt = as->optlist; opt != NULL; opt = opt->next) {
if (argc < Nargc - 2) {
argv[argc++] = "-v";
argv[argc++] = opt->opt;
} else {
syslog(LOG_ERR, "too many authentication options");
goto fail;
}
}
while (argc < Nargc - 1 && (argv[argc] = _auth_next_arg(as)))
++argc;
if (argc >= Nargc - 1 && _auth_next_arg(as)) {
if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
va_end(as->ap0);
explicit_bzero(&(as->ap0), sizeof(as->ap0));
}
if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
va_end(as->ap);
explicit_bzero(&(as->ap), sizeof(as->ap));
}
syslog(LOG_ERR, "too many arguments");
goto fail;
}
argv[argc] = NULL;
if (socketpair(PF_LOCAL, SOCK_STREAM, 0, pfd) == -1) {
syslog(LOG_ERR, "unable to create backchannel %m");
warnx("internal resource failure");
goto fail;
}
switch (pid = fork()) {
case -1:
syslog(LOG_ERR, "%s: %m", path);
warnx("internal resource failure");
close(pfd[0]);
close(pfd[1]);
goto fail;
case 0:
#define COMM_FD 3
#define AUTH_FD 4
if (dup2(pfd[1], COMM_FD) == -1)
err(1, "dup of backchannel");
if (as->fd != -1) {
if (dup2(as->fd, AUTH_FD) == -1)
err(1, "dup of auth fd");
closefrom(AUTH_FD + 1);
} else
closefrom(COMM_FD + 1);
execve(path, argv, auth_environ);
syslog(LOG_ERR, "%s: %m", path);
err(1, "%s", path);
default:
close(pfd[1]);
if (as->fd != -1) {
close(as->fd);
as->fd = -1;
}
while ((data = as->data) != NULL) {
as->data = data->next;
if (data->len > 0) {
write(pfd[0], data->ptr, data->len);
explicit_bzero(data->ptr, data->len);
}
free(data);
}
as->index = 0;
_auth_spool(as, pfd[0]);
close(pfd[0]);
do {
if (waitpid(pid, &status, 0) != -1) {
if (!WIFEXITED(status))
goto fail;
break;
}
} while (errno == EINTR);
}
for (line = as->spool; line < as->spool + as->index;) {
if (!strncasecmp(line, BI_REJECT, sizeof(BI_REJECT)-1)) {
line += sizeof(BI_REJECT) - 1;
if (!*line || *line == ' ' || *line == '\t') {
while (*line == ' ' || *line == '\t')
++line;
if (!strcasecmp(line, "silent")) {
as->state = AUTH_SILENT;
break;
}
if (!strcasecmp(line, "challenge")) {
as->state = AUTH_CHALLENGE;
break;
}
if (!strcasecmp(line, "expired")) {
as->state = AUTH_EXPIRED;
break;
}
if (!strcasecmp(line, "pwexpired")) {
as->state = AUTH_PWEXPIRED;
break;
}
}
break;
} else if (!strncasecmp(line, BI_AUTH, sizeof(BI_AUTH)-1)) {
line += sizeof(BI_AUTH) - 1;
if (!*line || *line == ' ' || *line == '\t') {
while (*line == ' ' || *line == '\t')
++line;
if (*line == '\0')
as->state |= AUTH_OKAY;
else if (!strcasecmp(line, "root"))
as->state |= AUTH_ROOTOKAY;
else if (!strcasecmp(line, "secure"))
as->state |= AUTH_SECURE;
}
} else if (!strncasecmp(line, BI_REMOVE, sizeof(BI_REMOVE)-1)) {
line += sizeof(BI_REMOVE) - 1;
while (*line == ' ' || *line == '\t')
++line;
if (*line)
_add_rmlist(as, line);
}
while (*line++)
;
}
if (WEXITSTATUS(status))
as->state &= ~AUTH_ALLOW;
okay = as->state & AUTH_ALLOW;
if (!okay)
auth_clrenv(as);
if (0) {
fail:
auth_clrenv(as);
as->state = 0;
okay = -1;
}
while ((data = as->data) != NULL) {
as->data = data->next;
free(data);
}
if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
va_end(as->ap0);
explicit_bzero(&(as->ap0), sizeof(as->ap0));
}
if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
va_end(as->ap);
explicit_bzero(&(as->ap), sizeof(as->ap));
}
return (okay);
}
DEF_WEAK(auth_call);
static void
_recv_fd(auth_session_t *as, int fd)
{
struct msghdr msg;
struct cmsghdr *cmp;
union {
struct cmsghdr hdr;
char buf[CMSG_SPACE(sizeof(int))];
} cmsgbuf;
memset(&msg, 0, sizeof(msg));
msg.msg_control = &cmsgbuf.buf;
msg.msg_controllen = sizeof(cmsgbuf.buf);
if (recvmsg(fd, &msg, 0) == -1)
syslog(LOG_ERR, "recvmsg: %m");
else if (msg.msg_flags & MSG_TRUNC)
syslog(LOG_ERR, "message truncated");
else if (msg.msg_flags & MSG_CTRUNC)
syslog(LOG_ERR, "control message truncated");
else if ((cmp = CMSG_FIRSTHDR(&msg)) == NULL)
syslog(LOG_ERR, "missing control message");
else {
if (cmp->cmsg_level != SOL_SOCKET)
syslog(LOG_ERR, "unexpected cmsg_level %d",
cmp->cmsg_level);
else if (cmp->cmsg_type != SCM_RIGHTS)
syslog(LOG_ERR, "unexpected cmsg_type %d",
cmp->cmsg_type);
else if (cmp->cmsg_len != CMSG_LEN(sizeof(int)))
syslog(LOG_ERR, "bad cmsg_len %d",
cmp->cmsg_len);
else {
if (as->fd != -1)
close(as->fd);
as->fd = *(int *)CMSG_DATA(cmp);
}
}
}
static void
_auth_spool(auth_session_t *as, int fd)
{
ssize_t r;
char *b, *s;
for (s = as->spool + as->index; as->index < sizeof(as->spool) - 1; ) {
r = read(fd, as->spool + as->index,
sizeof(as->spool) - as->index);
if (r <= 0) {
as->spool[as->index] = '\0';
return;
}
b = as->spool + as->index;
as->index += r;
while (r-- > 0) {
if (*b++ == '\n') {
b[-1] = '\0';
if (strcasecmp(s, BI_FDPASS) == 0)
_recv_fd(as, fd);
s = b;
}
}
}
syslog(LOG_ERR, "Overflowed backchannel spool buffer");
errx(1, "System error in authentication program");
}
static void
_add_rmlist(auth_session_t *as, char *file)
{
struct rmfiles *rm;
size_t i = strlen(file) + 1;
if ((rm = malloc(sizeof(struct rmfiles) + i)) == NULL) {
syslog(LOG_ERR, "Failed to allocate rmfiles: %m");
return;
}
rm->file = (char *)(rm + 1);
rm->next = as->rmlist;
strlcpy(rm->file, file, i);
as->rmlist = rm;
}
static char *
_auth_next_arg(auth_session_t *as)
{
char *arg;
if (memcmp(&nilap, &(as->ap0), sizeof(nilap)) != 0) {
if ((arg = va_arg(as->ap0, char *)) != NULL)
return (arg);
va_end(as->ap0);
explicit_bzero(&(as->ap0), sizeof(as->ap0));
}
if (memcmp(&nilap, &(as->ap), sizeof(nilap)) != 0) {
if ((arg = va_arg(as->ap, char *)) != NULL)
return (arg);
va_end(as->ap);
explicit_bzero(&(as->ap), sizeof(as->ap));
}
return (NULL);
}