/* This file is part of GNU Pies.
Copyright (C) 2007-2010, 2013, 2016-2017 Sergey Poznyakoff
GNU Pies is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GNU Pies is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GNU Pies. If not, see . */
#include "pies.h"
#include
static void
switch_eids (uid_t *puid, gid_t *pgid, mode_t *pumask)
{
uid_t ouid = geteuid ();
gid_t ogid = getegid ();
mode_t omask = umask (*pumask);
if (setegid (*pgid))
logmsg (LOG_ERR, _("cannot switch to EGID %lu: %s"),
(unsigned long) *pgid, strerror (errno));
if (seteuid (*puid))
logmsg (LOG_ERR, _("cannot switch to EUID %lu: %s"),
(unsigned long) *puid, strerror (errno));
*puid = ouid;
*pgid = ogid;
*pumask = omask;
}
int
create_socket (struct pies_url *url, int socket_type,
const char *user, mode_t umaskval)
{
int rc;
int fd;
union
{
struct sockaddr sa;
struct sockaddr_in s_in;
struct sockaddr_un s_un;
} addr;
socklen_t socklen;
uid_t uid = 0;
gid_t gid = 0;
int switch_back;
if (strcmp (url->scheme, "unix") == 0
|| strcmp (url->scheme, "file") == 0
|| strcmp (url->scheme, "socket") == 0)
{
struct stat st;
const char *group = NULL;
user = url->user;
if (url->argc)
{
size_t i;
for (i = 0; i < url->argc; i++)
{
const char *arg = url->argv[i];
size_t len = strcspn (arg, "=");
if (strncmp (arg, "user", len) == 0)
user = arg + len + 1;
else if (strncmp (arg, "group", len) == 0)
group = arg + len + 1;
else if (strncmp (arg, "umask", len) == 0)
{
char *p;
unsigned long n = strtoul (arg + len + 1, &p, 8);
if (*p)
logmsg (LOG_ERR, _("%s: invalid octal number (%s)"),
url->string, arg + len + 1);
else if (n & ~0777)
logmsg (LOG_ERR, _("%s: invalid umask (%s)"),
url->string, arg + len + 1);
else
umaskval = n & 0777;
}
else if (strncmp (arg, "mode", len) == 0)
{
char *p;
unsigned long n = strtoul (arg + len + 1, &p, 8);
if (*p)
logmsg (LOG_ERR, _("%s: invalid octal number (%s)"),
url->string, arg + len + 1);
else if (n & ~0777)
logmsg (LOG_ERR, _("%s: invalid mode (%s)"),
url->string, arg + len + 1);
else
umaskval = 0777 & ~n;
}
}
}
if (user)
{
struct passwd *pw = getpwnam (user);
if (!pw)
{
logmsg (LOG_ERR, _("no such user: %s"), user);
return -1;
}
uid = pw->pw_uid;
gid = pw->pw_gid;
}
if (group)
{
struct group *grp = getgrnam (group);
if (!grp)
{
logmsg (LOG_ERR, _("no such group: %s"), user);
return -1;
}
gid = grp->gr_gid;
}
if (strlen (url->path) > sizeof addr.s_un.sun_path)
{
errno = EINVAL;
logmsg (LOG_ERR, _("%s: UNIX socket name too long"), url->path);
return -1;
}
addr.sa.sa_family = PF_UNIX;
socklen = sizeof (addr.s_un);
strcpy (addr.s_un.sun_path, url->path);
if (stat (url->path, &st))
{
if (errno != ENOENT)
{
logfuncall ("stat", url->string, errno);
return -1;
}
}
else
{
/* FIXME: Check permissions? */
if (!S_ISSOCK (st.st_mode))
{
logmsg (LOG_ERR, _("%s: not a socket"), url->string);
return -1;
}
if (/*rmsocket && */ unlink (url->path))
{
logfuncall ("unlink", url->path, errno);
return -1;
}
}
}
else if (strcmp (url->scheme, "inet") == 0)
{
const char *host = url->host;
short port = url->port;
uid = 0;
gid = 0;
umaskval = 0;
addr.sa.sa_family = PF_INET;
socklen = sizeof (addr.s_in);
if (!host)
addr.s_in.sin_addr.s_addr = INADDR_ANY;
else
{
struct hostent *hp = gethostbyname (host);
if (!hp)
{
logmsg (LOG_ERR, _("%s: unknown host name %s"),
url->string, host);
return -1;
}
addr.sa.sa_family = hp->h_addrtype;
switch (hp->h_addrtype)
{
case AF_INET:
memmove (&addr.s_in.sin_addr, hp->h_addr, 4);
addr.s_in.sin_port = htons (port);
break;
default:
logmsg (LOG_ERR, _("%s: unsupported address family"),
url->string);
return -1;
}
}
}
else
{
logmsg (LOG_ERR, "%s: unknown scheme", url->string);
return -1;
}
fd = socket (addr.sa.sa_family, socket_type, url->proto);
if (fd == -1)
{
logfuncall ("socket", url->string, errno);
return -1;
}
rc = 1;
if (addr.sa.sa_family != PF_UNIX
&& setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &rc,
sizeof (rc)) == -1)
{
logmsg (LOG_ERR, _("%s: set reuseaddr failed: %s"),
url->string, strerror (errno));
close (fd);
return -1;
}
if (uid || gid || umaskval)
{
switch_eids (&uid, &gid, &umaskval);
switch_back = 1;
}
else
switch_back = 0;
rc = bind (fd, &addr.sa, socklen);
if (rc < 0)
logfuncall ("bind", NULL, errno);
if (switch_back)
switch_eids (&uid, &gid, &umaskval);
if (rc < 0)
{
close (fd);
return -1;
}
return fd;
}
static int
pass_fd0 (int fd, int payload)
{
struct msghdr msg;
struct iovec iov[1];
#if HAVE_STRUCT_MSGHDR_MSG_CONTROL
# ifndef CMSG_LEN
# define CMSG_LEN(size) (sizeof(struct cmsghdr) + (size))
# endif /* ! CMSG_LEN */
# ifndef CMSG_SPACE
# define CMSG_SPACE(size) (sizeof(struct cmsghdr) + (size))
# endif /* ! CMSG_SPACE */
char control[CMSG_SPACE (sizeof (int))];
struct cmsghdr *cmptr;
msg.msg_control = (caddr_t) control;
msg.msg_controllen = CMSG_LEN (sizeof (int));
cmptr = CMSG_FIRSTHDR (&msg);
cmptr->cmsg_len = CMSG_LEN (sizeof(int));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
*((int *) CMSG_DATA (cmptr)) = payload;
#elif HAVE_STRUCT_MSGHDR_MSG_ACCRIGHTS
msg.msg_accrights = (caddr_t) &payload;
msg.msg_accrightslen = sizeof (int);
#else
logmsg (LOG_ERR, _("no way to send fd"));
return 1;
#endif /* HAVE_MSGHDR_MSG_CONTROL */
msg.msg_name = NULL;
msg.msg_namelen = 0;
iov[0].iov_base = "";
iov[0].iov_len = 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
return sendmsg (fd, &msg, 0) == -1;
}
int
pass_fd (const char *socket_name, int fd, unsigned maxtime)
{
enum { fds_init, fds_open, fds_connected, fds_ready } state = fds_init;
static char *fds_descr[] = { "init", "open", "connected", "ready" };
time_t start = time (NULL);
int sockfd = -1;
int res = -1;
struct sockaddr_un addr;
if (strlen (socket_name) > sizeof addr.sun_path)
{
logmsg (LOG_ERR, _("%s: UNIX socket name too long"), socket_name);
return -1;
}
addr.sun_family = AF_UNIX;
strcpy (addr.sun_path, socket_name);
for (;;)
{
time_t now = time (NULL);
if (now - start > maxtime)
{
logmsg (LOG_ERR, _("pass-fd timed out in state %s"),
fds_descr[state]);
break;
}
if (state == fds_init)
{
struct stat st;
if (stat (socket_name, &st) == 0)
{
if (!S_ISSOCK (st.st_mode))
{
logmsg (LOG_ERR, _("%s: not a socket"), socket_name);
break;
}
sockfd = socket (PF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1)
{
if (errno == EINTR)
continue;
logmsg (LOG_ERR, "socket: %s", strerror (errno));
break;
}
state = fds_open;
}
else if (errno != ENOENT)
{
logmsg (LOG_ERR, _("cannot stat %s: %s"),
socket_name, strerror (errno));
break;
}
}
if (state == fds_open)
{
if (connect (sockfd, (struct sockaddr *) &addr, sizeof (addr)))
{
switch (errno)
{
case EINTR:
case ECONNREFUSED:
case EAGAIN:
continue;
}
logfuncall ("connect", socket_name, errno);
break;
}
state = fds_connected;
}
if (state == fds_connected)
{
int rc;
fd_set fds;
struct timeval tv;
FD_ZERO (&fds);
FD_SET (sockfd, &fds);
tv.tv_usec = 0;
tv.tv_sec = maxtime - (now - start);
rc = select (sockfd + 1, NULL, &fds, NULL, &tv);
if (rc == 0)
continue;
if (rc < 0)
{
if (errno == EINTR)
continue;
logfuncall ("select", NULL, errno);
break;
}
state = fds_ready;
}
if (state == fds_ready)
{
res = pass_fd0 (sockfd, fd);
break;
}
}
if (sockfd >= 0)
close (sockfd);
return res;
}
fd_set fdset[3];
int fd_max;
struct sockinst
{
struct sockinst *prev, *next;
int fd;
int dead;
socket_handler_t handler[3];
void *data;
};
struct sockinst *si_head, *si_tail;
static int si_iterating;
static struct sockinst *
find_socket (int fd)
{
struct sockinst *sp;
for (sp = si_head; sp; sp = sp->next)
if (sp->fd == fd)
break;
return sp;
}
static void
calc_fd_max (void)
{
struct sockinst *sp;
fd_max = -1;
for (sp = si_head; sp; sp = sp->next)
if (sp->fd > fd_max)
fd_max = sp->fd;
}
void *
register_socket (int fd,
socket_handler_t rd,
socket_handler_t wr,
socket_handler_t ex,
void *data)
{
struct sockinst *sip = grecs_malloc (sizeof *sip);
sip->fd = fd;
sip->dead = 0;
sip->handler[PIES_EVT_RD] = rd;
sip->handler[PIES_EVT_WR] = wr;
sip->handler[PIES_EVT_EX] = ex;
sip->data = data;
sip->next = NULL;
sip->prev = si_tail;
if (si_tail)
si_tail->next = sip;
else
{
FD_ZERO (&fdset[PIES_EVT_RD]);
FD_ZERO (&fdset[PIES_EVT_WR]);
FD_ZERO (&fdset[PIES_EVT_EX]);
si_head = sip;
}
si_tail = sip;
if (rd)
FD_SET (fd, &fdset[PIES_EVT_RD]);
if (wr)
FD_SET (fd, &fdset[PIES_EVT_WR]);
if (ex)
FD_SET (fd, &fdset[PIES_EVT_EX]);
if (fd_max == -1)
calc_fd_max ();
else if (fd > fd_max)
fd_max = fd;
return sip;
}
/* FIXME: Don't use with disable_socket/enable_socket */
void
update_socket (int fd, int evt, socket_handler_t f)
{
struct sockinst *sp = find_socket (fd);
assert (sp != NULL);
sp->handler[evt] = f;
if (f)
FD_SET (fd, &fdset[evt]);
else
FD_CLR (fd, &fdset[evt]);
}
static void
delete_sockinst (struct sockinst *sp)
{
close (sp->fd);
if (sp->handler[PIES_EVT_RD])
FD_CLR (sp->fd, &fdset[PIES_EVT_RD]);
if (sp->handler[PIES_EVT_WR])
FD_CLR (sp->fd, &fdset[PIES_EVT_WR]);
if (sp->handler[PIES_EVT_EX])
FD_CLR (sp->fd, &fdset[PIES_EVT_EX]);
fd_max = -1;
if (sp->prev)
sp->prev->next = sp->next;
else
si_head = sp->next;
if (sp->next)
sp->next->prev = sp->prev;
else
si_tail = sp->prev;
free (sp);
}
void
deregister_socket (int fd)
{
struct sockinst *sp = find_socket (fd);
if (!sp)
return;
if (si_iterating)
sp->dead = 1;
else
delete_sockinst (sp);
}
int
register_program_socket (int socktype, int fd, void *data)
{
if (socktype == SOCK_STREAM && listen (fd, 8) == -1)
{
logmsg (LOG_ERR, "listen: %s", strerror (errno));
return 1;
}
register_socket (fd, progman_accept, NULL, NULL, data);
return 0;
}
void
disable_socket (int fd)
{
if (fd < 0)
return;
debug (2, (_("disabling fd %d"), fd));
FD_CLR (fd, &fdset[PIES_EVT_RD]);
FD_CLR (fd, &fdset[PIES_EVT_WR]);
FD_CLR (fd, &fdset[PIES_EVT_EX]);
}
void
enable_socket (int fd)
{
struct sockinst *sp;
if (fd < 0)
return;
debug (2, (_("enabling fd %d"), fd));
sp = find_socket (fd);
assert (sp != NULL);
if (sp->handler[PIES_EVT_RD])
FD_SET (fd, &fdset[PIES_EVT_RD]);
if (sp->handler[PIES_EVT_WR])
FD_SET (fd, &fdset[PIES_EVT_WR]);
if (sp->handler[PIES_EVT_EX])
FD_SET (fd, &fdset[PIES_EVT_EX]);
}
static int (*pies_pause_hook) (void);
void
pies_set_hook (int (*f) (void))
{
pies_pause_hook = f;
}
void
pies_pause (void)
{
if (pies_pause_hook && pies_pause_hook ())
return;
if (fd_max == -1)
calc_fd_max ();
while (1)
{
fd_set rdset = fdset[PIES_EVT_RD];
fd_set wrset = fdset[PIES_EVT_WR];
fd_set exset = fdset[PIES_EVT_EX];
int rc = select (fd_max + 1, &rdset, &wrset, &exset, NULL);
if (rc > 0)
{
struct sockinst *sp;
int delete = 0;
++si_iterating;
for (sp = si_head; sp; sp = sp->next)
{
if (sp->dead)
{
delete = 1;
continue;
}
if (sp->handler[PIES_EVT_RD] && FD_ISSET (sp->fd, &rdset))
sp->handler[PIES_EVT_RD] (sp->fd, sp->data);
if (sp->dead)
{
delete = 1;
continue;
}
if (sp->handler[PIES_EVT_WR] && FD_ISSET (sp->fd, &wrset))
sp->handler[PIES_EVT_WR] (sp->fd, sp->data);
if (sp->dead)
{
delete = 1;
continue;
}
if (sp->handler[PIES_EVT_EX] && FD_ISSET (sp->fd, &exset))
sp->handler[PIES_EVT_EX] (sp->fd, sp->data);
}
--si_iterating;
if (delete)
{
for (sp = si_head; sp; )
{
struct sockinst *next = sp->next;
if (sp->dead)
delete_sockinst (sp);
sp = next;
}
calc_fd_max ();
}
break;
}
else if (rc < 0)
{
if (errno != EINTR)
logmsg (LOG_ERR, "select: %s", strerror (errno));
break;
}
}
}