2 * Copyright (c) 2022 Omar Polo <op@omarpolo.com>
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 #include <sys/socket.h>
20 #include <sys/types.h>
41 #define PKG_FCGI_DB "/pkg_fcgi/pkgs.sqlite3"
45 #define PKG_FCGI_SOCK "/run/pkg_fcgi.sock"
49 #define PKG_FCGI_USER "www"
52 #define MAX_CHILDREN 32
54 static const char *argv0;
55 static pid_t pids[MAX_CHILDREN];
56 static int children = 3;
58 static volatile sig_atomic_t got_sigchld;
61 handle_sigchld(int sig)
71 for (i = 0; i < children; ++i)
72 (void) kill(pids[i], SIGTERM);
78 bind_socket(const char *path, struct passwd *pw)
80 struct sockaddr_un sun;
83 if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0)) == -1) {
84 log_warn("%s: socket", __func__);
88 memset(&sun, 0, sizeof(sun));
89 sun.sun_family = AF_UNIX;
91 if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >=
92 sizeof(sun.sun_path)) {
93 log_warnx("%s: path too long: %s", __func__, path);
98 if (unlink(path) == -1 && errno != ENOENT) {
99 log_warn("%s: unlink %s", __func__, path);
104 old_umask = umask(S_IXUSR|S_IXGRP|S_IWOTH|S_IROTH|S_IXOTH);
105 if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
106 log_warn("%s: bind: %s (%d)", __func__, path, geteuid());
113 if (chmod(path, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) == -1) {
114 log_warn("%s: chmod %s", __func__, path);
120 if (chown(path, pw->pw_uid, pw->pw_gid) == -1) {
121 log_warn("%s: chown %s %s", __func__, pw->pw_name, path);
127 if (listen(fd, 5) == -1) {
128 log_warn("%s: listen", __func__);
138 start_child(const char *root, const char *user, const char *db,
139 int daemonize, int verbose, int fd)
145 switch (pid = fork()) {
147 fatal("cannot fork");
156 if (dup2(fd, 3) == -1)
157 fatal("cannot setup imsg fd");
158 } else if (fcntl(fd, F_SETFD, 0) == -1)
159 fatal("cannot setup imsg fd");
161 argv[argc++] = (char *)argv0;
162 argv[argc++] = (char *)"-S";
163 argv[argc++] = (char *)"-p"; argv[argc++] = (char *)root;
164 argv[argc++] = (char *)"-u"; argv[argc++] = (char *)user;
166 argv[argc++] = (char *)"-d";
168 argv[argc++] = (char *)"-v";
169 argv[argc++] = (char *)db;
180 "usage: %s [-dv] [-j n] [-p path] [-s socket] [-u user] [db]\n",
186 main(int argc, char **argv)
193 const char *root = NULL;
194 const char *sock = PKG_FCGI_SOCK;
195 const char *user = PKG_FCGI_USER;
196 const char *db = PKG_FCGI_DB;
198 int ch, i, daemonize = 1, verbosity = 0;
199 int server = 0, fd = -1;
203 * Ensure we have fds 0-2 open so that we have no issue with
204 * calling bind_socket before daemon(3).
206 for (i = 0; i < 3; ++i) {
207 if (fstat(i, &sb) == -1) {
208 if ((fd = open("/dev/null", O_RDWR)) != -1) {
209 if (dup2(fd, i) == -1)
218 log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */
220 if ((argv0 = argv[0]) == NULL)
221 fatalx("argv[0] is NULL");
223 while ((ch = getopt(argc, argv, "dj:p:Ss:u:v")) != -1) {
229 children = strtonum(optarg, 1, MAX_CHILDREN, &errstr);
231 fatalx("number of children is %s: %s",
262 fatalx("need root privileges");
266 fatalx("user %s not found", user);
268 fatalx("cannot run as %s: must not be the superuser", user);
276 ret = snprintf(path, sizeof(path), "%s/%s", root, sock);
277 if (ret < 0 || (size_t)ret >= sizeof(path))
278 fatalx("socket path too long");
280 if ((fd = bind_socket(path, pw)) == -1)
281 fatalx("failed to open socket %s", sock);
283 for (i = 0; i < children; ++i) {
286 if ((d = dup(fd)) == -1)
288 pids[i] = start_child(root, user, db,
289 daemonize, verbosity, d);
290 log_debug("forking child %d (pid %lld)", i,
294 signal(SIGCHLD, handle_sigchld);
297 if (chroot(root) == -1)
298 fatal("chroot %s", root);
299 if (chdir("/") == -1)
302 if (setgroups(1, &pw->pw_gid) == -1 ||
303 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
304 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
305 fatal("failed to drop privileges");
307 log_init(daemonize ? 0 : 1, LOG_DAEMON);
308 log_setverbose(verbosity);
311 exit(server_main(db));
313 if (daemonize && daemon(1, 0) == -1)
316 if (pledge("stdio proc", NULL) == -1)
321 pid = waitpid(WAIT_ANY, &status, 0);
322 } while (pid != -1 || errno == EINTR);
327 fatal("waitpid failed");
330 if (WIFSIGNALED(status))
331 cause = "was terminated";
332 else if (WIFEXITED(status)) {
333 if (WEXITSTATUS(status) != 0)
334 cause = "exited abnormally";
336 cause = "exited successfully";
340 log_warnx("child process %lld %s", (long long)pid, cause);