main.c - quark - quark web server
(HTM) git clone git://git.suckless.org/quark
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) LICENSE
---
main.c (8711B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include <errno.h>
3 #include <grp.h>
4 #include <limits.h>
5 #include <pwd.h>
6 #include <regex.h>
7 #include <signal.h>
8 #include <stddef.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/resource.h>
12 #include <sys/time.h>
13 #include <sys/types.h>
14 #include <sys/wait.h>
15 #include <unistd.h>
16
17 #include "arg.h"
18 #include "server.h"
19 #include "sock.h"
20 #include "util.h"
21
22 static char *udsname;
23
24 static void
25 cleanup(void)
26 {
27 if (udsname) {
28 sock_rem_uds(udsname);
29 }
30 }
31
32 static void
33 sigcleanup(int sig)
34 {
35 cleanup();
36 kill(0, sig);
37 _exit(1);
38 }
39
40 static void
41 handlesignals(void(*hdl)(int))
42 {
43 struct sigaction sa = {
44 .sa_handler = hdl,
45 };
46
47 sigemptyset(&sa.sa_mask);
48 sigaction(SIGTERM, &sa, NULL);
49 sigaction(SIGHUP, &sa, NULL);
50 sigaction(SIGINT, &sa, NULL);
51 sigaction(SIGQUIT, &sa, NULL);
52 }
53
54 static void
55 usage(void)
56 {
57 const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] "
58 "[-i file] [-v vhost] ... [-m map] ...";
59
60 die("usage: %s -p port [-h host] %s\n"
61 " %s -U file [-p port] %s", argv0,
62 opts, argv0, opts);
63 }
64
65 int
66 main(int argc, char *argv[])
67 {
68 struct group *grp = NULL;
69 struct passwd *pwd = NULL;
70 struct rlimit rlim;
71 struct server srv = {
72 .docindex = "index.html",
73 };
74 size_t i;
75 int insock, status = 0;
76 const char *err;
77 char *tok[4];
78
79 /* defaults */
80 size_t nthreads = 4;
81 size_t nslots = 64;
82 char *servedir = ".";
83 char *user = "nobody";
84 char *group = "nogroup";
85
86 ARGBEGIN {
87 case 'd':
88 servedir = EARGF(usage());
89 break;
90 case 'g':
91 group = EARGF(usage());
92 break;
93 case 'h':
94 srv.host = EARGF(usage());
95 break;
96 case 'i':
97 srv.docindex = EARGF(usage());
98 if (strchr(srv.docindex, '/')) {
99 die("The document index must not contain '/'");
100 }
101 break;
102 case 'l':
103 srv.listdirs = 1;
104 break;
105 case 'm':
106 if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) {
107 usage();
108 }
109 if (!(srv.map = reallocarray(srv.map, ++srv.map_len,
110 sizeof(struct map)))) {
111 die("reallocarray:");
112 }
113 srv.map[srv.map_len - 1].from = tok[0];
114 srv.map[srv.map_len - 1].to = tok[1];
115 srv.map[srv.map_len - 1].chost = tok[2];
116 break;
117 case 's':
118 err = NULL;
119 nslots = strtonum(EARGF(usage()), 1, INT_MAX, &err);
120 if (err) {
121 die("strtonum '%s': %s", EARGF(usage()), err);
122 }
123 break;
124 case 't':
125 err = NULL;
126 nthreads = strtonum(EARGF(usage()), 1, INT_MAX, &err);
127 if (err) {
128 die("strtonum '%s': %s", EARGF(usage()), err);
129 }
130 break;
131 case 'p':
132 srv.port = EARGF(usage());
133 break;
134 case 'U':
135 udsname = EARGF(usage());
136 break;
137 case 'u':
138 user = EARGF(usage());
139 break;
140 case 'v':
141 if (spacetok(EARGF(usage()), tok, 4) || !tok[0] || !tok[1] ||
142 !tok[2]) {
143 usage();
144 }
145 if (!(srv.vhost = reallocarray(srv.vhost, ++srv.vhost_len,
146 sizeof(*srv.vhost)))) {
147 die("reallocarray:");
148 }
149 srv.vhost[srv.vhost_len - 1].chost = tok[0];
150 srv.vhost[srv.vhost_len - 1].regex = tok[1];
151 srv.vhost[srv.vhost_len - 1].dir = tok[2];
152 srv.vhost[srv.vhost_len - 1].prefix = tok[3];
153 break;
154 default:
155 usage();
156 } ARGEND
157
158 if (argc) {
159 usage();
160 }
161
162 /* can't have both host and UDS but must have one of port or UDS*/
163 if ((srv.host && udsname) || !(srv.port || udsname)) {
164 usage();
165 }
166
167 if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) {
168 die("UNIX-domain socket '%s': %s", udsname, errno ?
169 strerror(errno) : "File exists");
170 }
171
172 /* compile and check the supplied vhost regexes */
173 for (i = 0; i < srv.vhost_len; i++) {
174 if (regcomp(&srv.vhost[i].re, srv.vhost[i].regex,
175 REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
176 die("regcomp '%s': invalid regex",
177 srv.vhost[i].regex);
178 }
179 }
180
181 /* validate user and group */
182 errno = 0;
183 if (!user || !(pwd = getpwnam(user))) {
184 die("getpwnam '%s': %s", user ? user : "null",
185 errno ? strerror(errno) : "Entry not found");
186 }
187 errno = 0;
188 if (!group || !(grp = getgrnam(group))) {
189 die("getgrnam '%s': %s", group ? group : "null",
190 errno ? strerror(errno) : "Entry not found");
191 }
192
193 /* open a new process group */
194 setpgid(0, 0);
195
196 handlesignals(sigcleanup);
197
198 /*
199 * set the maximum number of open file descriptors as needed
200 * - 3 initial fd's
201 * - nthreads fd's for the listening socket
202 * - (nthreads * nslots) fd's for the connection-fd
203 * - (5 * nthreads) fd's for general purpose thread-use
204 */
205 rlim.rlim_cur = rlim.rlim_max = 3 + nthreads + nthreads * nslots +
206 5 * nthreads;
207 if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
208 if (errno == EPERM) {
209 die("You need to run as root or have "
210 "CAP_SYS_RESOURCE set, or are asking for more "
211 "file descriptors than the system can offer");
212 } else {
213 die("setrlimit:");
214 }
215 }
216
217 /*
218 * create the (non-blocking) listening socket
219 *
220 * we could use SO_REUSEPORT and create a listening socket for
221 * each thread (for better load-balancing, given each thread
222 * would get his own kernel-queue), but this increases latency
223 * (as a thread might get stuck on a larger request, making all
224 * other request wait in line behind it).
225 *
226 * socket contention with a single listening socket is a
227 * non-issue and thread-load-balancing is better fixed in the
228 * kernel by changing epoll-sheduling from a FIFO- to a
229 * LIFO-model, especially as it doesn't affect performance
230 */
231 insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) :
232 sock_get_ips(srv.host, srv.port);
233 if (sock_set_nonblocking(insock)) {
234 return 1;
235 }
236
237 /*
238 * before dropping privileges, we fork, as we need to remove
239 * the UNIX-domain socket when we shut down, which we need
240 * privileges for
241 */
242 switch (fork()) {
243 case -1:
244 warn("fork:");
245 break;
246 case 0:
247 /* restore default handlers */
248 handlesignals(SIG_DFL);
249
250 /* reap children automatically */
251 if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
252 die("signal: Failed to set SIG_IGN on SIGCHLD");
253 }
254 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
255 die("signal: Failed to set SIG_IGN on SIGPIPE");
256 }
257
258 /*
259 * try increasing the thread-limit by the number
260 * of threads we need (which is the only reliable
261 * workaround I know given the thread-limit is per user
262 * rather than per process), but ignore EPERM errors,
263 * because this most probably means the user has already
264 * set the value to the kernel's limit, and there's not
265 * much we can do in any other case.
266 * There's also no danger of overflow as the value
267 * returned by getrlimit() is way below the limits of the
268 * rlim_t datatype.
269 */
270 if (getrlimit(RLIMIT_NPROC, &rlim) < 0) {
271 die("getrlimit:");
272 }
273 if (rlim.rlim_max == RLIM_INFINITY) {
274 if (rlim.rlim_cur != RLIM_INFINITY) {
275 /* try increasing current limit by nthreads */
276 rlim.rlim_cur += nthreads;
277 }
278 } else {
279 /* try increasing current and hard limit by nthreads */
280 rlim.rlim_cur = rlim.rlim_max += nthreads;
281 }
282 if (setrlimit(RLIMIT_NPROC, &rlim) < 0 && errno != EPERM) {
283 die("setrlimit()");
284 }
285
286 /* limit ourselves to reading the servedir and block further unveils */
287 eunveil(servedir, "r");
288 eunveil(NULL, NULL);
289
290 /* chroot */
291 if (chdir(servedir) < 0) {
292 die("chdir '%s':", servedir);
293 }
294 if (chroot(".") < 0) {
295 if (errno == EPERM) {
296 die("You need to run as root or have "
297 "CAP_SYS_CHROOT set");
298 } else {
299 die("chroot:");
300 }
301 }
302
303 /* drop root */
304 if (pwd->pw_uid == 0 || grp->gr_gid == 0) {
305 die("Won't run under root %s for hopefully obvious reasons",
306 (pwd->pw_uid == 0) ? (grp->gr_gid == 0) ?
307 "user and group" : "user" : "group");
308 }
309
310 if (setgroups(1, &(grp->gr_gid)) < 0) {
311 if (errno == EPERM) {
312 die("You need to run as root or have "
313 "CAP_SETGID set");
314 } else {
315 die("setgroups:");
316 }
317 }
318 if (setgid(grp->gr_gid) < 0) {
319 if (errno == EPERM) {
320 die("You need to run as root or have "
321 "CAP_SETGID set");
322 } else {
323 die("setgid:");
324 }
325
326 }
327 if (setuid(pwd->pw_uid) < 0) {
328 if (errno == EPERM) {
329 die("You need to run as root or have "
330 "CAP_SETUID set");
331 } else {
332 die("setuid:");
333 }
334 }
335
336 if (udsname) {
337 epledge("stdio rpath proc unix", NULL);
338 } else {
339 epledge("stdio rpath proc inet", NULL);
340 }
341
342 /* accept incoming connections */
343 server_init_thread_pool(insock, nthreads, nslots, &srv);
344
345 exit(0);
346 default:
347 /* limit ourselves even further while we are waiting */
348 if (udsname) {
349 eunveil(udsname, "c");
350 eunveil(NULL, NULL);
351 epledge("stdio cpath", NULL);
352 } else {
353 eunveil("/", "");
354 eunveil(NULL, NULL);
355 epledge("stdio", NULL);
356 }
357
358 while (wait(&status) > 0)
359 ;
360 }
361
362 cleanup();
363 return status;
364 }