aboutsummaryrefslogtreecommitdiff
path: root/src/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/socket.c')
-rw-r--r--src/socket.c446
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;
+ }
+ }
+}
+

Return to:

Send suggestions and report system problems to the System administrator.