Introduction
Introduction Statistics Contact Development Disclaimer Help
main.c - quark - quark web server
git clone git://git.suckless.org/quark
Log
Files
Refs
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[…
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[…
142 !tok[2]) {
143 usage();
144 }
145 if (!(srv.vhost = reallocarray(srv.vhost, ++srv.vhost_le…
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 mor…
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_gi…
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 nthre…
276 rlim.rlim_cur += nthreads;
277 }
278 } else {
279 /* try increasing current and hard limit by nthr…
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 fur…
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 obvio…
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 }
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.