Blob


1 /*
2 * This file is in the public domain.
3 */
5 #include <sys/socket.h>
6 #include <sys/stat.h>
7 #include <sys/tree.h>
8 #include <sys/types.h>
9 #include <sys/un.h>
10 #include <sys/wait.h>
12 #include <err.h>
13 #include <errno.h>
14 #include <event.h>
15 #include <fcntl.h>
16 #include <limits.h>
17 #include <pwd.h>
18 #include <signal.h>
19 #include <stdarg.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <syslog.h>
24 #include <unistd.h>
26 #include "log.h"
27 #include "msearchd.h"
29 #ifndef MSEARCHD_DB
30 #define MSEARCHD_DB "/msearchd/mails.sqlite3"
31 #endif
33 #ifndef MSEARCHD_SOCK
34 #define MSEARCHD_SOCK "/run/msearchd.sock"
35 #endif
37 #ifndef MSEARCHD_USER
38 #define MSEARCHD_USER "www"
39 #endif
41 #ifndef MSEARCH_TMPL_DIR
42 #define MSEARCH_TMPL_DIR SYSCONFDIR "/smarc"
43 #endif
45 #define MAX_CHILDREN 32
47 int debug;
48 int verbose;
49 int children = 3;
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;
57 static void
58 sighdlr(int sig)
59 {
60 static volatile sig_atomic_t got_sig;
61 int i, save_errno;
63 if (got_sig)
64 return;
65 got_sig = -1;
67 save_errno = errno;
68 for (i = 0; i < children; ++i)
69 (void)kill(pids[i], SIGTERM);
70 errno = save_errno;
71 }
73 static void
74 load_tmpl(const char **ret, const char *dir, const char *name)
75 {
76 FILE *fp;
77 struct stat sb;
78 char *t;
79 char path[PATH_MAX];
80 int r;
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)
90 fatal("fstat");
92 if (sb.st_size > SIZE_MAX)
93 fatal("file too big %s", path);
95 if ((t = malloc(sb.st_size + 1)) == NULL)
96 fatal("malloc");
98 if (fread(t, 1, sb.st_size, fp) != sb.st_size)
99 fatal("fread %s", path);
101 fclose(fp);
103 t[sb.st_size] = '\0';
104 *ret = t;
107 static int
108 bind_socket(const char *path, struct passwd *pw)
110 struct sockaddr_un sun;
111 int fd, old_umask;
113 if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK, 0)) == -1) {
114 log_warn("%s: socket", __func__);
115 return (-1);
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);
124 close(fd);
125 return (-1);
128 if (unlink(path) == -1 && errno != ENOENT) {
129 log_warn("%s: unlink %s", __func__, path);
130 close(fd);
131 return (-1);
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());
137 close(fd);
138 umask(old_umask);
139 return (-1);
141 umask(old_umask);
143 if (chmod(path, 0660) == -1) {
144 log_warn("%s: chmod 0660 %s", __func__, path);
145 close(fd);
146 (void)unlink(path);
147 return (-1);
150 if (chown(path, pw->pw_uid, pw->pw_gid) == -1) {
151 log_warn("%s: chown %s %s", __func__, pw->pw_name, path);
152 close(fd);
153 (void)unlink(path);
154 return (-1);
157 if (listen(fd, 5) == -1) {
158 log_warn("%s: listen", __func__);
159 close(fd);
160 (void)unlink(path);
161 return (-1);
164 return (fd);
167 static pid_t
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];
172 int argc = 0;
173 pid_t pid;
175 switch (pid = fork()) {
176 case -1:
177 fatal("cannot fork");
178 case 0:
179 break;
180 default:
181 close(fd);
182 return (pid);
185 if (fd != 3) {
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;
192 argv[argc++] = "-S";
193 argv[argc++] = "-p"; argv[argc++] = root;
194 argv[argc++] = "-t"; argv[argc++] = tmpl;
195 argv[argc++] = "-u"; argv[argc++] = user;
196 if (debug)
197 argv[argc++] = "-d";
198 if (verbose--)
199 argv[argc++] = "-v";
200 if (verbose--)
201 argv[argc++] = "-v";
202 argv[argc++] = db;
203 argv[argc++] = NULL;
205 /* obnoxious cast */
206 execvp(argv0, (char * const *) argv);
207 fatal("execvp %s", argv0);
210 static void __dead
211 usage(void)
213 fprintf(stderr, "usage: %s [-dv] [-j n] [-p path] [-s socket]"
214 " [-t tmpldir] [-u user] [db]\n",
215 getprogname());
216 exit(1);
219 int
220 main(int argc, char **argv)
222 struct stat sb;
223 struct passwd *pw;
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;
231 pid_t pid;
232 int ch, i, fd, ret, status, server = 0;
234 /*
235 * Ensure we have fds 0-2 open so that we have no issue with
236 * calling bind_socket before daemon(3).
237 */
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)
242 exit(1);
243 if (fd > i)
244 close(fd);
245 } else
246 exit(1);
250 if ((argv0 = argv[0]) == NULL)
251 argv0 = "msearchd";
253 while ((ch = getopt(argc, argv, "dj:p:Ss:t:u:v")) != -1) {
254 switch (ch) {
255 case 'd':
256 debug = 1;
257 break;
258 case 'j':
259 children = strtonum(optarg, 1, MAX_CHILDREN, &errstr);
260 if (errstr)
261 fatalx("number of children is %s: %s",
262 errstr, optarg);
263 break;
264 case 'p':
265 root = optarg;
266 break;
267 case 'S':
268 server = 1;
269 break;
270 case 's':
271 sock = optarg;
272 break;
273 case 't':
274 tmpldir = optarg;
275 break;
276 case 'u':
277 user = optarg;
278 break;
279 case 'v':
280 verbose++;
281 break;
282 default:
283 usage();
286 argc -= optind;
287 argv += optind;
289 if (argc > 0) {
290 db = argv[0];
291 argv++;
292 argc--;
294 if (argc != 0)
295 usage();
297 if (geteuid())
298 fatalx("need root privileges");
300 pw = getpwnam(user);
301 if (pw == NULL)
302 fatalx("user %s not found", user);
303 if (pw->pw_uid == 0)
304 fatalx("cannot run as %s: must not be the superuser", user);
306 if (root == NULL)
307 root = pw->pw_dir;
309 log_init(debug, LOG_DAEMON);
311 if (!debug && !server && daemon(1, 0) == -1)
312 fatal("daemon");
314 if (!server) {
315 sigset_t set;
317 sigemptyset(&set);
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) {
329 int d;
331 if ((d = dup(fd)) == -1)
332 fatalx("dup");
333 pids[i] = start_child(argv0, root, user, db, tmpldir,
334 debug, verbose, d);
335 log_debug("forking child %d (pid %lld)", i,
336 (long long)pids[i]);
339 signal(SIGINT, sighdlr);
340 signal(SIGTERM, sighdlr);
341 signal(SIGCHLD, sighdlr);
342 signal(SIGHUP, SIG_IGN);
344 sigprocmask(SIG_UNBLOCK, &set, NULL);
345 } else {
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)
357 fatal("chdir /");
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");
364 if (server)
365 return (server_main(db));
367 if (pledge("stdio proc", NULL) == -1)
368 fatal("pledge");
370 for (;;) {
371 do {
372 pid = waitpid(WAIT_ANY, &status, 0);
373 } while (pid != -1 || errno == EINTR);
375 if (pid == -1) {
376 if (errno == ECHILD)
377 break;
378 fatal("waitpid");
381 if (WIFSIGNALED(status))
382 cause = "was terminated";
383 else if (WIFEXITED(status)) {
384 if (WEXITSTATUS(status) != 0)
385 cause = "exited abnormally";
386 else
387 cause = "exited successfully";
388 } else
389 cause = "died";
391 log_warnx("child process %lld %s", (long long)pid, cause);
394 return (1);