/* This file is part of GNU Pies. Copyright (C) 2007-2019 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 ((*puid && *puid != ouid) || (*pgid && *pgid != ogid)) { 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; } } }