diff options
Diffstat (limited to 'src/socket.c')
-rw-r--r-- | src/socket.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..03ee59c --- /dev/null +++ b/src/socket.c @@ -0,0 +1,446 @@ +/* This file is part of Mailfromd. + Copyright (C) 2007, 2008 Sergey Poznyakoff + + This program 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 of the License, or (at your + option) any later version. + + This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "pies.h" + +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <pwd.h> +#include <grp.h> + +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)) + mu_error (_("Cannot switch to EGID %lu: %s"), + (unsigned long) *pgid, mu_strerror (errno)); + if (seteuid (*puid)) + mu_error (_("Cannot switch to EUID %lu: %s"), + (unsigned long) *puid, mu_strerror (errno)); + *puid = ouid; + *pgid = ogid; + *pumask = omask; +} + +int +create_socket (mu_url_t url, 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; + const char *scheme; + uid_t uid = 0; + gid_t gid = 0; + int switch_back; + + rc = mu_url_sget_scheme (url, &scheme); + if (rc) + { + mu_error ("mu_url_sget_scheme: %s", mu_strerror (rc)); + return -1; + } + + if (strcmp (scheme, "unix") == 0 + || strcmp (scheme, "file") == 0 || strcmp (scheme, "socket") == 0) + { + const char *path = NULL; + struct stat st; + size_t vpc; + char **vpairs; + char *group = NULL; + + mu_url_sget_user (url, &user); + if (mu_url_sget_fvpairs (url, &vpc, &vpairs) == 0) + { + size_t i; + for (i = 0; i < vpc; i++) + { + size_t len = strcspn (vpairs[i], "="); + if (strncmp (vpairs[i], "user", len) == 0) + user = vpairs[i] + len + 1; + else if (strncmp (vpairs[i], "group", len) == 0) + group = vpairs[i] + len + 1; + else if (strncmp (vpairs[i], "umask", len) == 0) + { + char *p; + unsigned long n = strtoul (vpairs[i] + len + 1, &p, 8); + if (*p) + mu_error (_("%s: invalid octal number (%s)"), + mu_url_to_string (url), vpairs[i] + len + 1); + else if (n & ~0777) + mu_error (_("%s: invalid umask (%s)"), + mu_url_to_string (url), vpairs[i] + len + 1); + else + umaskval = n & 0777; + } + else if (strncmp (vpairs[i], "mode", len) == 0) + { + char *p; + unsigned long n = strtoul (vpairs[i] + len + 1, &p, 8); + if (*p) + mu_error (_("%s: invalid octal number (%s)"), + mu_url_to_string (url), vpairs[i] + len + 1); + else if (n & ~0777) + mu_error (_("%s: invalid mode (%s)"), + mu_url_to_string (url), vpairs[i] + len + 1); + else + umaskval = 0777 & ~n; + } + } + } + + if (user) + { + struct passwd *pw = getpwnam (user); + if (!pw) + { + mu_error (_("No such user: %s"), user); + return -1; + } + uid = pw->pw_uid; + gid = pw->pw_gid; + } + + if (group) + { + struct group *grp = getgrnam (group); + if (!grp) + { + mu_error (_("No such group: %s"), user); + return -1; + } + gid = grp->gr_gid; + } + + rc = mu_url_sget_path (url, &path); + if (rc) + { + mu_error ("mu_url_sget_path: %s", mu_strerror (rc)); + return -1; + } + + if (strlen (path) > sizeof addr.s_un.sun_path) + { + errno = EINVAL; + mu_error (_("%s: UNIX socket name too long"), + mu_url_to_string (url)); + return -1; + } + addr.sa.sa_family = PF_UNIX; + socklen = sizeof (addr.s_un); + strcpy (addr.s_un.sun_path, path); + if (stat(path, &st)) + { + if (errno != ENOENT) + { + mu_error (_("%s: cannot stat socket: %s"), + mu_url_to_string (url), + mu_strerror (errno)); + return -1; + } + } + else + { + /* FIXME: Check permissions? */ + if (!S_ISSOCK(st.st_mode)) + { + mu_error (_("%s: not a socket"), mu_url_to_string (url)); + return -1; + } + if (/*rmsocket && */ unlink (path)) + { + mu_error (_("%s: cannot unlink: %s"), + path, mu_strerror (errno)); + return -1; + } + } + } + else if (strcmp (scheme, "inet") == 0) + { + long n; + const char *host = NULL; + short port = 0; + + rc = mu_url_get_port (url, &n); + if (rc) + { + mu_error ("mu_url_get_port: %s", mu_strerror (rc)); + return -1; + } + + if (n == 0 || (port = n) != n) + { + mu_error (_("%s: port out of range"), mu_url_to_string (url)); + return -1; + } + + rc = mu_url_sget_host (url, &host); + if (rc == MU_ERR_NOENT) + host = NULL; + else if (rc) + { + mu_error ("mu_url_sget_host: %s", mu_strerror (rc)); + return -1; + } + + 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) + { + mu_error (_("%s: Unknown host name %s"), + mu_url_to_string (url), 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: + mu_error (_("%s: unsupported address family"), + mu_url_to_string (url)); + return -1; + } + } + } + else + { + mu_error ("%s: unknown scheme", mu_url_to_string (url)); + return -1; + } + + fd = socket (addr.sa.sa_family, SOCK_STREAM, 0); + if (fd == -1) + { + mu_error (_("%s: Cannot create socket: %s"), + mu_url_to_string (url), mu_strerror (errno)); + return -1; + } + + rc = 1; + if (addr.sa.sa_family != PF_UNIX + && setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &rc, + sizeof (rc)) == -1) + { + mu_error (_("%s: set reuseaddr failed (%s)"), + mu_url_to_string (url), mu_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 (switch_back) + switch_eids (&uid, &gid, &umaskval); + if (rc < 0) + { + mu_error (_("%s: Cannot bind: %s"), + mu_url_to_string (url), mu_strerror (errno)); + close (fd); + return -1; + } + return fd; +} + +static int +open_unix_socket (const char *socket_name) +{ + int fd; + struct sockaddr_un addr; + + if (strlen (socket_name) > sizeof addr.sun_path) + { + mu_error (_("%s: UNIX socket name too long"), socket_name); + return -1; + } + + fd = socket (PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + mu_error ("socket: %s", mu_strerror (errno)); + return -1; + } + addr.sun_family = AF_UNIX; + strcpy (addr.sun_path, socket_name); + + if (connect (fd, (struct sockaddr *) &addr, sizeof (addr))) + { + mu_error (_("%s: connect failed: %s"), socket_name, mu_strerror (errno)); + 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 + mu_error (_("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, int fd) +{ + int i; + struct stat st; + + for (i = 0; i < 10; i++) + { + if (stat (socket, &st) == 0) + { + int rc; + int sockfd; + + if (!S_ISSOCK (st.st_mode)) + { + mu_error (_("%s: not a socket"), socket); + break; + } + sockfd = open_unix_socket (socket); + if (sockfd == -1) + rc = 1; + else + { + rc = pass_fd0 (sockfd, fd); + close (sockfd); + } + return rc; + } + if (errno != ENOENT) + { + mu_error (_("cannot stat %s: %s"), socket, mu_strerror (errno)); + break; + } + } + return -1; +} + + +fd_set listenset; +int fd_max; + +int +register_listener (int fd) +{ + if (listen (fd, 8) == -1) + { + mu_error (_("listen: %s"), mu_strerror (errno)); + return 1; + } + FD_SET (fd, &listenset); + if (fd > fd_max) + fd_max = fd; + return 0; +} + +void +pies_pause () +{ + while (1) + { + fd_set rdset = listenset; + int rc = select (fd_max + 1, &rdset, NULL, NULL, NULL); + if (rc > 0) + { + int i; + for (i = 0; i <= fd_max; i++) + { + if (FD_ISSET (i, &rdset)) + progman_accept (i); + } + } + else if (rc < 0) + { + if (errno != EINTR) + mu_error ("select: %s", mu_strerror (errno)); + break; + } + } +} + |