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 }