2 * This file is in the public domain.
5 #include <sys/socket.h>
30 #define MSEARCHD_DB "/msearchd/mails.sqlite3"
34 #define MSEARCHD_SOCK "/run/msearchd.sock"
38 #define MSEARCHD_USER "www"
41 #ifndef MSEARCH_TMPL_DIR
42 #define MSEARCH_TMPL_DIR SYSCONFDIR "/smarc"
45 #define MAX_CHILDREN 32
50 pid_t pids[MAX_CHILDREN];
52 const char *tmpl_head;
53 const char *tmpl_search;
54 const char *tmpl_search_header;
55 const char *tmpl_foot;
60 static volatile sig_atomic_t got_sig;
68 for (i = 0; i < children; ++i)
69 (void)kill(pids[i], SIGTERM);
74 load_tmpl(const char **ret, const char *dir, const char *name)
82 r = snprintf(path, sizeof(path), "%s/%s", dir, name);
83 if (r < 0 || (size_t)r >= sizeof(path))
84 fatalx("path too long: %s/%s", dir, name);
86 if ((fp = fopen(path, "r")) == NULL)
87 fatal("can't open %s", path);
89 if (fstat(fileno(fp), &sb) == -1)
92 if (sb.st_size > SIZE_MAX)
93 fatal("file too big %s", path);
95 if ((t = malloc(sb.st_size + 1)) == NULL)
98 if (fread(t, 1, sb.st_size, fp) != sb.st_size)
99 fatal("fread %s", path);
103 t[sb.st_size] = '\0';
108 bind_socket(const char *path, struct passwd *pw)
110 struct sockaddr_un sun;
113 if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0)) == -1) {
114 log_warn("%s: socket", __func__);
118 memset(&sun, 0, sizeof(sun));
119 sun.sun_family = AF_UNIX;
121 if (strlcpy(sun.sun_path, path, sizeof(sun.sun_path)) >=
122 sizeof(sun.sun_path)) {
123 log_warnx("%s: path too long: %s", __func__, path);
128 if (unlink(path) == -1 && errno != ENOENT) {
129 log_warn("%s: unlink %s", __func__, path);
134 old_umask = umask(0117);
135 if (bind(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
136 log_warn("%s: bind: %s (%d)", __func__, path, geteuid());
143 if (chmod(path, 0660) == -1) {
144 log_warn("%s: chmod 0660 %s", __func__, path);
150 if (chown(path, pw->pw_uid, pw->pw_gid) == -1) {
151 log_warn("%s: chown %s %s", __func__, pw->pw_name, path);
157 if (listen(fd, 5) == -1) {
158 log_warn("%s: listen", __func__);
168 start_child(const char *argv0, const char *root, const char *user,
169 const char *db, const char *tmpl, int debug, int verbose, int fd)
171 const char *argv[13];
175 switch (pid = fork()) {
177 fatal("cannot fork");
186 if (dup2(fd, 3) == -1)
187 fatal("cannot setup socket fd");
188 } else if (fcntl(fd, F_SETFD, 0) == -1)
189 fatal("cannot setup socket fd");
191 argv[argc++] = argv0;
193 argv[argc++] = "-p"; argv[argc++] = root;
194 argv[argc++] = "-t"; argv[argc++] = tmpl;
195 argv[argc++] = "-u"; argv[argc++] = user;
206 execvp(argv0, (char * const *) argv);
207 fatal("execvp %s", argv0);
213 fprintf(stderr, "usage: %s [-dv] [-j n] [-p path] [-s socket]"
214 " [-t tmpldir] [-u user] [db]\n",
220 main(int argc, char **argv)
224 char sockp[PATH_MAX];
225 const char *sock = MSEARCHD_SOCK;
226 const char *user = MSEARCHD_USER;
227 const char *root = NULL;
228 const char *db = MSEARCHD_DB;
229 const char *tmpldir = MSEARCH_TMPL_DIR;
230 const char *errstr, *cause, *argv0;
232 int ch, i, fd, ret, status, server = 0;
235 * Ensure we have fds 0-2 open so that we have no issue with
236 * calling bind_socket before daemon(3).
238 for (i = 0; i < 3; ++i) {
239 if (fstat(i, &sb) == -1) {
240 if ((fd = open("/dev/null", O_RDWR)) != -1) {
241 if (dup2(fd, i) == -1)
250 if ((argv0 = argv[0]) == NULL)
253 while ((ch = getopt(argc, argv, "dj:p:Ss:t:u:v")) != -1) {
259 children = strtonum(optarg, 1, MAX_CHILDREN, &errstr);
261 fatalx("number of children is %s: %s",
298 fatalx("need root privileges");
302 fatalx("user %s not found", user);
304 fatalx("cannot run as %s: must not be the superuser", user);
309 log_init(debug, LOG_DAEMON);
311 if (!debug && !server && daemon(1, 0) == -1)
318 sigaddset(&set, SIGCHLD);
319 sigaddset(&set, SIGINT);
320 sigaddset(&set, SIGTERM);
321 sigprocmask(SIG_BLOCK, &set, NULL);
323 ret = snprintf(sockp, sizeof(sockp), "%s/%s", root, sock);
324 if (ret < 0 || (size_t)ret >= sizeof(sockp))
325 fatalx("socket path too long");
326 if ((fd = bind_socket(sockp, pw)) == -1)
327 fatalx("failed to open socket %s", sock);
328 for (i = 0; i < children; ++i) {
331 if ((d = dup(fd)) == -1)
333 pids[i] = start_child(argv0, root, user, db, tmpldir,
335 log_debug("forking child %d (pid %lld)", i,
339 signal(SIGINT, sighdlr);
340 signal(SIGTERM, sighdlr);
341 signal(SIGCHLD, sighdlr);
342 signal(SIGHUP, SIG_IGN);
344 sigprocmask(SIG_UNBLOCK, &set, NULL);
346 load_tmpl(&tmpl_head, tmpldir, "head.html");
347 load_tmpl(&tmpl_search, tmpldir, "search.html");
348 load_tmpl(&tmpl_search_header, tmpldir, "search-header.html");
349 load_tmpl(&tmpl_foot, tmpldir, "foot.html");
351 setproctitle("server");
354 if (chroot(root) == -1)
355 fatal("chroot %s", root);
356 if (chdir("/") == -1)
359 if (setgroups(1, &pw->pw_gid) == -1 ||
360 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) == -1 ||
361 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) == -1)
362 fatal("failed to drop privileges");
365 return (server_main(db));
367 if (pledge("stdio proc", NULL) == -1)
372 pid = waitpid(WAIT_ANY, &status, 0);
373 } while (pid != -1 || errno == EINTR);
381 if (WIFSIGNALED(status))
382 cause = "was terminated";
383 else if (WIFEXITED(status)) {
384 if (WEXITSTATUS(status) != 0)
385 cause = "exited abnormally";
387 cause = "exited successfully";
391 log_warnx("child process %lld %s", (long long)pid, cause);