├── .gitignore ├── control ├── Makefile ├── README.md └── darkhttpd.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.deb 2 | darkhttpd 3 | darkhttpd.deb 4 | -------------------------------------------------------------------------------- /control: -------------------------------------------------------------------------------- 1 | Package: darkhttpd 2 | Version: 1.10 3 | Section: custom 4 | Priority: optional 5 | Architecture: all 6 | Essential: no 7 | Installed-Size: 1024 8 | Maintainer: linuxconfig.org 9 | Description: When you need a web server in a hurry. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC?=cc 2 | CFLAGS?=-Wall -O2 3 | #LDFLAGS?= 4 | 5 | all: bin 6 | 7 | bin: darkhttpd 8 | darkhttpd: darkhttpd.c 9 | rm -rf darkhttpd 10 | $(CC) $(CFLAGS) $(LDFLAGS) darkhttpd.c -o darkhttpd 11 | 12 | # builds a .deb package for debian users 13 | # `make debian && dpkg -i darkhttpd.deb` 14 | deb: debian 15 | debian: darkhttpd 16 | mv darkhttpd darkhttpd_ 17 | mkdir -p ./darkhttpd/usr/bin 18 | mkdir -p ./darkhttpd/DEBIAN 19 | cp darkhttpd_ ./darkhttpd/usr/bin/darkhttpd 20 | cp control ./darkhttpd/DEBIAN/ 21 | dpkg-deb --build ./darkhttpd 22 | rm -rf ./darkhttpd 23 | mv darkhttpd_ darkhttpd 24 | chmod +x darkhttpd.deb 25 | 26 | # typical linux `make && make install` 27 | install: darkhttpd 28 | install -Dm 775 darkhttpd /usr/bin/darkhttpd 29 | uninstall: 30 | rm -vf /usr/bin/darkhttpd 31 | 32 | clean: 33 | rm -rf darkhttpd darkhttpd.deb 34 | 35 | .PHONY: clean 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # darkhttpd 2 | 3 | (forked from https://unix4lyfe.org/git/darkhttpd) 4 | 5 | ### If you're here for a Ubuntu/Debian package... 6 | 7 | You can grab it here: https://github.com/ryanmjacobs/darkhttpd/releases/download/r2/darkhttpd-x86_64.deb 8 | ```console 9 | $ curl -O https://github.com/ryanmjacobs/darkhttpd/releases/download/r2/darkhttpd-x86_64.deb 10 | $ dpkg -i darkhttpd-x86_64.deb 11 | ``` 12 | 13 | ### Now with HTTP Basic-Auth support 14 | 15 | ``` 16 | Usage: darkhttpd . --auth username:password 17 | darkhttpd . --port 8080 --auth john:pass123 18 | ``` 19 | 20 | ## How to build darkhttpd 21 | 22 | Simply run: 23 | ```console 24 | $ make bin # produces native binary only 25 | $ make debian # will produce a .deb package too 26 | ``` 27 | 28 | To install: 29 | ``` 30 | $ sudo make install # distro-independent 31 | $ sudo dpkg -i darkhttpd.deb # for Debian-based distros, e.g. Ubuntu 32 | ``` 33 | 34 | ## Usage 35 | 36 | See the full usage by running `darkhttpd --help`. 37 | 38 | ## Release Versions 39 | 40 | This section documents what package/binaries I have compiled and uploaded to 41 | Github Releases. 42 | 43 | * r2 (2019-04-10): 44 | * compiled and statically linked to musl for better compatibility 45 | * CC=musl-gcc LDFLAGS=-static make deb 46 | * r1 (2019-04-10): 47 | * compiled and uploaded 64-bit .deb package 48 | * git hash d7501ec on master branch 49 | -------------------------------------------------------------------------------- /darkhttpd.c: -------------------------------------------------------------------------------- 1 | /* darkhttpd - a simple, single-threaded, static content webserver. 2 | * https://unix4lyfe.org/darkhttpd/ 3 | * Copyright (c) 2003-2019 Emil Mikulic 4 | * 5 | * Permission to use, copy, modify, and distribute this software for any 6 | * purpose with or without fee is hereby granted, provided that the 7 | * above copyright notice and this permission notice appear in all 8 | * copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 11 | * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 12 | * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 13 | * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 14 | * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 15 | * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 16 | * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 17 | * PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | static const char 21 | pkgname[] = "darkhttpd/1.12.from.git", 22 | copyright[] = "https://github.com/ryanmjacobs/darkhttpd\n" 23 | "copyright (c) 2003-2019 Emil Mikulic\n" 24 | "2014-2020 Ryan Jacobs (contrib basic-auth / .deb)\n"; 25 | 26 | /* Possible build options: -DDEBUG -DNO_IPV6 */ 27 | 28 | #ifndef NO_IPV6 29 | # define HAVE_INET6 30 | #endif 31 | 32 | #ifndef DEBUG 33 | # define NDEBUG 34 | static const int debug = 0; 35 | #else 36 | static const int debug = 1; 37 | #endif 38 | 39 | #ifdef __linux 40 | # define _GNU_SOURCE /* for strsignal() and vasprintf() */ 41 | # define _FILE_OFFSET_BITS 64 /* stat() files bigger than 2GB */ 42 | # include 43 | #endif 44 | 45 | #ifdef __sun__ 46 | # include 47 | #endif 48 | 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | #include 65 | #include 66 | #include 67 | #include 68 | #include 69 | #include 70 | #include 71 | #include 72 | #include 73 | #include 74 | 75 | #ifdef __sun__ 76 | # ifndef INADDR_NONE 77 | # define INADDR_NONE -1 78 | # endif 79 | #endif 80 | 81 | #ifndef MAXNAMLEN 82 | # ifdef NAME_MAX 83 | # define MAXNAMLEN NAME_MAX 84 | # else 85 | # define MAXNAMLEN 255 86 | # endif 87 | #endif 88 | 89 | #if defined(O_EXCL) && !defined(O_EXLOCK) 90 | # define O_EXLOCK O_EXCL 91 | #endif 92 | 93 | #ifndef __printflike 94 | # ifdef __GNUC__ 95 | /* [->] borrowed from FreeBSD's src/sys/sys/cdefs.h,v 1.102.2.2.2.1 */ 96 | # define __printflike(fmtarg, firstvararg) \ 97 | __attribute__((__format__(__printf__, fmtarg, firstvararg))) 98 | /* [<-] */ 99 | # else 100 | # define __printflike(fmtarg, firstvararg) 101 | # endif 102 | #endif 103 | 104 | #if defined(__GNUC__) || defined(__INTEL_COMPILER) 105 | # define unused __attribute__((__unused__)) 106 | #else 107 | # define unused 108 | #endif 109 | 110 | /* [->] borrowed from FreeBSD's src/sys/sys/systm.h,v 1.276.2.7.4.1 */ 111 | #ifndef CTASSERT /* Allow lint to override */ 112 | # define CTASSERT(x) _CTASSERT(x, __LINE__) 113 | # define _CTASSERT(x, y) __CTASSERT(x, y) 114 | # define __CTASSERT(x, y) typedef char __assert ## y[(x) ? 1 : -1] 115 | #endif 116 | /* [<-] */ 117 | 118 | CTASSERT(sizeof(unsigned long long) >= sizeof(off_t)); 119 | #define llu(x) ((unsigned long long)(x)) 120 | 121 | #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__linux) 122 | # include 123 | #else 124 | /* err - prints "error: format: strerror(errno)" to stderr and exit()s with 125 | * the given code. 126 | */ 127 | static void err(const int code, const char *format, ...) __printflike(2, 3); 128 | static void err(const int code, const char *format, ...) { 129 | va_list va; 130 | 131 | va_start(va, format); 132 | fprintf(stderr, "error: "); 133 | vfprintf(stderr, format, va); 134 | fprintf(stderr, ": %s\n", strerror(errno)); 135 | va_end(va); 136 | exit(code); 137 | } 138 | 139 | /* errx - err() without the strerror */ 140 | static void errx(const int code, const char *format, ...) __printflike(2, 3); 141 | static void errx(const int code, const char *format, ...) { 142 | va_list va; 143 | 144 | va_start(va, format); 145 | fprintf(stderr, "error: "); 146 | vfprintf(stderr, format, va); 147 | fprintf(stderr, "\n"); 148 | va_end(va); 149 | exit(code); 150 | } 151 | 152 | /* warn - err() without the exit */ 153 | static void warn(const char *format, ...) __printflike(1, 2); 154 | static void warn(const char *format, ...) { 155 | va_list va; 156 | 157 | va_start(va, format); 158 | fprintf(stderr, "warning: "); 159 | vfprintf(stderr, format, va); 160 | fprintf(stderr, ": %s\n", strerror(errno)); 161 | va_end(va); 162 | } 163 | #endif 164 | 165 | /* [->] LIST_* macros taken from FreeBSD's src/sys/sys/queue.h,v 1.56 166 | * Copyright (c) 1991, 1993 167 | * The Regents of the University of California. All rights reserved. 168 | * 169 | * Under a BSD license. 170 | */ 171 | #define LIST_HEAD(name, type) \ 172 | struct name { \ 173 | struct type *lh_first; /* first element */ \ 174 | } 175 | 176 | #define LIST_HEAD_INITIALIZER(head) \ 177 | { NULL } 178 | 179 | #define LIST_ENTRY(type) \ 180 | struct { \ 181 | struct type *le_next; /* next element */ \ 182 | struct type **le_prev; /* address of previous next element */ \ 183 | } 184 | 185 | #define LIST_FIRST(head) ((head)->lh_first) 186 | 187 | #define LIST_FOREACH_SAFE(var, head, field, tvar) \ 188 | for ((var) = LIST_FIRST((head)); \ 189 | (var) && ((tvar) = LIST_NEXT((var), field), 1); \ 190 | (var) = (tvar)) 191 | 192 | #define LIST_INSERT_HEAD(head, elm, field) do { \ 193 | if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ 194 | LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ 195 | LIST_FIRST((head)) = (elm); \ 196 | (elm)->field.le_prev = &LIST_FIRST((head)); \ 197 | } while (0) 198 | 199 | #define LIST_NEXT(elm, field) ((elm)->field.le_next) 200 | 201 | #define LIST_REMOVE(elm, field) do { \ 202 | if (LIST_NEXT((elm), field) != NULL) \ 203 | LIST_NEXT((elm), field)->field.le_prev = \ 204 | (elm)->field.le_prev; \ 205 | *(elm)->field.le_prev = LIST_NEXT((elm), field); \ 206 | } while (0) 207 | /* [<-] */ 208 | 209 | static LIST_HEAD(conn_list_head, connection) connlist = 210 | LIST_HEAD_INITIALIZER(conn_list_head); 211 | 212 | struct connection { 213 | LIST_ENTRY(connection) entries; 214 | 215 | int socket; 216 | #ifdef HAVE_INET6 217 | struct in6_addr client; 218 | #else 219 | in_addr_t client; 220 | #endif 221 | time_t last_active; 222 | enum { 223 | RECV_REQUEST, /* receiving request */ 224 | SEND_HEADER, /* sending generated header */ 225 | SEND_REPLY, /* sending reply */ 226 | DONE /* connection closed, need to remove from queue */ 227 | } state; 228 | 229 | /* char request[request_length+1] is null-terminated */ 230 | char *request; 231 | size_t request_length; 232 | 233 | /* request fields */ 234 | char *method, *url, *referer, *user_agent, *authorization; 235 | off_t range_begin, range_end; 236 | off_t range_begin_given, range_end_given; 237 | 238 | char *header; 239 | size_t header_length, header_sent; 240 | int header_dont_free, header_only, http_code, conn_close; 241 | 242 | enum { REPLY_GENERATED, REPLY_FROMFILE } reply_type; 243 | char *reply; 244 | int reply_dont_free; 245 | int reply_fd; 246 | off_t reply_start, reply_length, reply_sent, 247 | total_sent; /* header + body = total, for logging */ 248 | }; 249 | 250 | struct forward_mapping { 251 | const char *host, *target_url; /* These point at argv. */ 252 | }; 253 | 254 | static struct forward_mapping *forward_map = NULL; 255 | static size_t forward_map_size = 0; 256 | static const char *forward_all_url = NULL; 257 | 258 | struct mime_mapping { 259 | char *extension, *mimetype; 260 | }; 261 | 262 | static struct mime_mapping *mime_map = NULL; 263 | static size_t mime_map_size = 0; 264 | static size_t longest_ext = 0; 265 | 266 | /* If a connection is idle for timeout_secs or more, it gets closed and 267 | * removed from the connlist. 268 | */ 269 | static int timeout_secs = 30; 270 | static char *keep_alive_field = NULL; 271 | 272 | /* Time is cached in the event loop to avoid making an excessive number of 273 | * gettimeofday() calls. 274 | */ 275 | static time_t now; 276 | 277 | /* To prevent a malformed request from eating up too much memory, die once the 278 | * request exceeds this many bytes: 279 | */ 280 | #define MAX_REQUEST_LENGTH 4000 281 | 282 | /* Defaults can be overridden on the command-line */ 283 | static const char *bindaddr; 284 | static uint16_t bindport = 8080; /* or 80 if running as root */ 285 | static int max_connections = -1; /* kern.ipc.somaxconn */ 286 | static const char *index_name = "index.html"; 287 | static int no_listing = 0; 288 | 289 | static int sockin = -1; /* socket to accept connections from */ 290 | #ifdef HAVE_INET6 291 | static int inet6 = 0; /* whether the socket uses inet6 */ 292 | #endif 293 | static char *wwwroot = NULL; /* a path name */ 294 | static char *logfile_name = NULL; /* NULL = no logging */ 295 | static FILE *logfile = NULL; 296 | static char *pidfile_name = NULL; /* NULL = no pidfile */ 297 | static int want_chroot = 0, want_daemon = 0, want_accf = 0, 298 | want_keepalive = 1, want_server_id = 1; 299 | static char *server_hdr = NULL; 300 | static char *auth_key = NULL; 301 | static uint64_t num_requests = 0, total_in = 0, total_out = 0; 302 | static int accepting = 1; /* set to 0 to stop accept()ing */ 303 | 304 | static volatile int running = 1; /* signal handler sets this to false */ 305 | 306 | #define INVALID_UID ((uid_t) -1) 307 | #define INVALID_GID ((gid_t) -1) 308 | 309 | static uid_t drop_uid = INVALID_UID; 310 | static gid_t drop_gid = INVALID_GID; 311 | 312 | /* file with specified extension name will be ignored,directory is not affected. */ 313 | #define MAX_EXTENSIONS_NUM 64 314 | static char *ignored_file_exts[MAX_EXTENSIONS_NUM]; 315 | 316 | /* Default mimetype mappings - make sure this array is NULL terminated. */ 317 | static const char *default_extension_map[] = { 318 | "application/ogg" " ogg", 319 | "application/pdf" " pdf", 320 | "application/xml" " xsl xml", 321 | "application/xml-dtd" " dtd", 322 | "application/xslt+xml" " xslt", 323 | "application/zip" " zip", 324 | "audio/mpeg" " mp2 mp3 mpga", 325 | "image/gif" " gif", 326 | "image/jpeg" " jpeg jpe jpg", 327 | "image/png" " png", 328 | "image/webp" " webp", 329 | "text/css" " css", 330 | "text/html" " html htm", 331 | "text/javascript" " js", 332 | "text/plain" " txt asc", 333 | "video/mpeg" " mpeg mpe mpg", 334 | "video/quicktime" " qt mov", 335 | "video/x-msvideo" " avi", 336 | NULL 337 | }; 338 | 339 | static const char octet_stream[] = "application/octet-stream"; 340 | static const char *default_mimetype = octet_stream; 341 | 342 | /* Prototypes. */ 343 | static void poll_recv_request(struct connection *conn); 344 | static void poll_send_header(struct connection *conn); 345 | static void poll_send_reply(struct connection *conn); 346 | 347 | /* close() that dies on error. */ 348 | static void xclose(const int fd) { 349 | if (close(fd) == -1) 350 | err(1, "close()"); 351 | } 352 | 353 | /* malloc that dies if it can't allocate. */ 354 | static void *xmalloc(const size_t size) { 355 | void *ptr = malloc(size); 356 | if (ptr == NULL) 357 | errx(1, "can't allocate %zu bytes", size); 358 | return ptr; 359 | } 360 | 361 | /* realloc() that dies if it can't reallocate. */ 362 | static void *xrealloc(void *original, const size_t size) { 363 | void *ptr = realloc(original, size); 364 | if (ptr == NULL) 365 | errx(1, "can't reallocate %zu bytes", size); 366 | return ptr; 367 | } 368 | 369 | /* strdup() that dies if it can't allocate. 370 | * Implement this ourselves since regular strdup() isn't C89. 371 | */ 372 | static char *xstrdup(const char *src) { 373 | size_t len = strlen(src) + 1; 374 | char *dest = xmalloc(len); 375 | memcpy(dest, src, len); 376 | return dest; 377 | } 378 | 379 | #ifdef __sun /* unimpressed by Solaris */ 380 | static int vasprintf(char **strp, const char *fmt, va_list ap) { 381 | char tmp; 382 | int result = vsnprintf(&tmp, 1, fmt, ap); 383 | *strp = xmalloc(result+1); 384 | result = vsnprintf(*strp, result+1, fmt, ap); 385 | return result; 386 | } 387 | #endif 388 | 389 | /* vasprintf() that dies if it fails. */ 390 | static unsigned int xvasprintf(char **ret, const char *format, va_list ap) 391 | __printflike(2,0); 392 | static unsigned int xvasprintf(char **ret, const char *format, va_list ap) { 393 | int len = vasprintf(ret, format, ap); 394 | if (ret == NULL || len == -1) 395 | errx(1, "out of memory in vasprintf()"); 396 | return (unsigned int)len; 397 | } 398 | 399 | /* asprintf() that dies if it fails. */ 400 | static unsigned int xasprintf(char **ret, const char *format, ...) 401 | __printflike(2,3); 402 | static unsigned int xasprintf(char **ret, const char *format, ...) { 403 | va_list va; 404 | unsigned int len; 405 | 406 | va_start(va, format); 407 | len = xvasprintf(ret, format, va); 408 | va_end(va); 409 | return len; 410 | } 411 | 412 | /* Append buffer code. A somewhat efficient string buffer with pool-based 413 | * reallocation. 414 | */ 415 | #ifndef APBUF_INIT 416 | # define APBUF_INIT 4096 417 | #endif 418 | #define APBUF_GROW APBUF_INIT 419 | struct apbuf { 420 | size_t length, pool; 421 | char *str; 422 | }; 423 | 424 | static struct apbuf *make_apbuf(void) { 425 | struct apbuf *buf = xmalloc(sizeof(struct apbuf)); 426 | buf->length = 0; 427 | buf->pool = APBUF_INIT; 428 | buf->str = xmalloc(buf->pool); 429 | return buf; 430 | } 431 | 432 | /* Append s (of length len) to buf. */ 433 | static void appendl(struct apbuf *buf, const char *s, const size_t len) { 434 | size_t need = buf->length + len; 435 | if (buf->pool < need) { 436 | /* pool has dried up */ 437 | while (buf->pool < need) 438 | buf->pool += APBUF_GROW; 439 | buf->str = xrealloc(buf->str, buf->pool); 440 | } 441 | memcpy(buf->str + buf->length, s, len); 442 | buf->length += len; 443 | } 444 | 445 | #ifdef __GNUC__ 446 | #define append(buf, s) appendl(buf, s, \ 447 | (__builtin_constant_p(s) ? sizeof(s)-1 : strlen(s)) ) 448 | #else 449 | static void append(struct apbuf *buf, const char *s) { 450 | appendl(buf, s, strlen(s)); 451 | } 452 | #endif 453 | 454 | static void appendf(struct apbuf *buf, const char *format, ...) 455 | __printflike(2, 3); 456 | static void appendf(struct apbuf *buf, const char *format, ...) { 457 | char *tmp; 458 | va_list va; 459 | size_t len; 460 | 461 | va_start(va, format); 462 | len = xvasprintf(&tmp, format, va); 463 | va_end(va); 464 | appendl(buf, tmp, len); 465 | free(tmp); 466 | } 467 | 468 | /* Make the specified socket non-blocking. */ 469 | static void nonblock_socket(const int sock) { 470 | int flags = fcntl(sock, F_GETFL); 471 | 472 | if (flags == -1) 473 | err(1, "fcntl(F_GETFL)"); 474 | flags |= O_NONBLOCK; 475 | if (fcntl(sock, F_SETFL, flags) == -1) 476 | err(1, "fcntl() to set O_NONBLOCK"); 477 | } 478 | 479 | /* Split string out of src with range [left:right-1] */ 480 | static char *split_string(const char *src, 481 | const size_t left, const size_t right) { 482 | char *dest; 483 | assert(left <= right); 484 | assert(left < strlen(src)); /* [left means must be smaller */ 485 | assert(right <= strlen(src)); /* right) means can be equal or smaller */ 486 | 487 | dest = xmalloc(right - left + 1); 488 | memcpy(dest, src+left, right-left); 489 | dest[right-left] = '\0'; 490 | return dest; 491 | } 492 | 493 | /* Resolve /./ and /../ in a URL, in-place. Also strip out query params. 494 | * Returns NULL if the URL is invalid/unsafe, or the original buffer if 495 | * successful. 496 | */ 497 | static char *make_safe_url(char *const url) { 498 | char *src = url, *dst; 499 | #define ends(c) ((c) == '/' || (c) == '\0') 500 | 501 | /* URLs not starting with a slash are illegal. */ 502 | if (*src != '/') 503 | return NULL; 504 | 505 | /* Fast case: skip until first double-slash or dot-dir. */ 506 | for ( ; *src && *src != '?'; ++src) { 507 | if (*src == '/') { 508 | if (src[1] == '/') 509 | break; 510 | else if (src[1] == '.') { 511 | if (ends(src[2])) 512 | break; 513 | else if (src[2] == '.' && ends(src[3])) 514 | break; 515 | } 516 | } 517 | } 518 | 519 | /* Copy to dst, while collapsing multi-slashes and handling dot-dirs. */ 520 | dst = src; 521 | while (*src && *src != '?') { 522 | if (*src != '/') 523 | *dst++ = *src++; 524 | else if (*++src == '/') 525 | ; 526 | else if (*src != '.') 527 | *dst++ = '/'; 528 | else if (ends(src[1])) 529 | /* Ignore single-dot component. */ 530 | ++src; 531 | else if (src[1] == '.' && ends(src[2])) { 532 | /* Double-dot component. */ 533 | src += 2; 534 | if (dst == url) 535 | return NULL; /* Illegal URL */ 536 | else 537 | /* Backtrack to previous slash. */ 538 | while (*--dst != '/' && dst > url); 539 | } 540 | else 541 | *dst++ = '/'; 542 | } 543 | 544 | if (dst == url) 545 | ++dst; 546 | *dst = '\0'; 547 | return url; 548 | #undef ends 549 | } 550 | 551 | static void add_forward_mapping(const char * const host, 552 | const char * const target_url) { 553 | forward_map_size++; 554 | forward_map = xrealloc(forward_map, 555 | sizeof(*forward_map) * forward_map_size); 556 | forward_map[forward_map_size - 1].host = host; 557 | forward_map[forward_map_size - 1].target_url = target_url; 558 | } 559 | 560 | /* Associates an extension with a mimetype in the mime_map. Entries are in 561 | * unsorted order. Makes copies of extension and mimetype strings. 562 | */ 563 | static void add_mime_mapping(const char *extension, const char *mimetype) { 564 | size_t i; 565 | assert(strlen(extension) > 0); 566 | assert(strlen(mimetype) > 0); 567 | 568 | /* update longest_ext */ 569 | i = strlen(extension); 570 | if (i > longest_ext) 571 | longest_ext = i; 572 | 573 | /* look through list and replace an existing entry if possible */ 574 | for (i = 0; i < mime_map_size; i++) 575 | if (strcmp(mime_map[i].extension, extension) == 0) { 576 | free(mime_map[i].mimetype); 577 | mime_map[i].mimetype = xstrdup(mimetype); 578 | return; 579 | } 580 | 581 | /* no replacement - add a new entry */ 582 | mime_map_size++; 583 | mime_map = xrealloc(mime_map, 584 | sizeof(struct mime_mapping) * mime_map_size); 585 | mime_map[mime_map_size - 1].extension = xstrdup(extension); 586 | mime_map[mime_map_size - 1].mimetype = xstrdup(mimetype); 587 | } 588 | 589 | /* qsort() the mime_map. The map must be sorted before it can be 590 | * binary-searched. 591 | */ 592 | static int mime_mapping_cmp(const void *a, const void *b) { 593 | return strcmp(((const struct mime_mapping *)a)->extension, 594 | ((const struct mime_mapping *)b)->extension); 595 | } 596 | 597 | static void sort_mime_map(void) { 598 | qsort(mime_map, mime_map_size, sizeof(struct mime_mapping), 599 | mime_mapping_cmp); 600 | } 601 | 602 | /* Parses a mime.types line and adds the parsed data to the mime_map. */ 603 | static void parse_mimetype_line(const char *line) { 604 | unsigned int pad, bound1, lbound, rbound; 605 | 606 | /* parse mimetype */ 607 | for (pad=0; (line[pad] == ' ') || (line[pad] == '\t'); pad++) 608 | ; 609 | if (line[pad] == '\0' || /* empty line */ 610 | line[pad] == '#') /* comment */ 611 | return; 612 | 613 | for (bound1=pad+1; 614 | (line[bound1] != ' ') && 615 | (line[bound1] != '\t'); 616 | bound1++) { 617 | if (line[bound1] == '\0') 618 | return; /* malformed line */ 619 | } 620 | 621 | lbound = bound1; 622 | for (;;) { 623 | char *mimetype, *extension; 624 | 625 | /* find beginning of extension */ 626 | for (; (line[lbound] == ' ') || (line[lbound] == '\t'); lbound++) 627 | ; 628 | if (line[lbound] == '\0') 629 | return; /* end of line */ 630 | 631 | /* find end of extension */ 632 | for (rbound = lbound; 633 | line[rbound] != ' ' && 634 | line[rbound] != '\t' && 635 | line[rbound] != '\0'; 636 | rbound++) 637 | ; 638 | 639 | mimetype = split_string(line, pad, bound1); 640 | extension = split_string(line, lbound, rbound); 641 | add_mime_mapping(extension, mimetype); 642 | free(mimetype); 643 | free(extension); 644 | 645 | if (line[rbound] == '\0') 646 | return; /* end of line */ 647 | else 648 | lbound = rbound + 1; 649 | } 650 | } 651 | 652 | /* Adds contents of default_extension_map[] to mime_map list. The array must 653 | * be NULL terminated. 654 | */ 655 | static void parse_default_extension_map(void) { 656 | size_t i; 657 | 658 | for (i = 0; default_extension_map[i] != NULL; i++) 659 | parse_mimetype_line(default_extension_map[i]); 660 | } 661 | 662 | /* read a line from fp, return its contents in a dynamically allocated buffer, 663 | * not including the line ending. 664 | * 665 | * Handles CR, CRLF and LF line endings, as well as NOEOL correctly. If 666 | * already at EOF, returns NULL. Will err() or errx() in case of 667 | * unexpected file error or running out of memory. 668 | */ 669 | static char *read_line(FILE *fp) { 670 | char *buf; 671 | long startpos, endpos; 672 | size_t linelen, numread; 673 | int c; 674 | 675 | startpos = ftell(fp); 676 | if (startpos == -1) 677 | err(1, "ftell()"); 678 | 679 | /* find end of line (or file) */ 680 | linelen = 0; 681 | for (;;) { 682 | c = fgetc(fp); 683 | if ((c == EOF) || (c == (int)'\n') || (c == (int)'\r')) 684 | break; 685 | linelen++; 686 | } 687 | 688 | /* return NULL on EOF (and empty line) */ 689 | if (linelen == 0 && c == EOF) 690 | return NULL; 691 | 692 | endpos = ftell(fp); 693 | if (endpos == -1) 694 | err(1, "ftell()"); 695 | 696 | /* skip CRLF */ 697 | if ((c == (int)'\r') && (fgetc(fp) == (int)'\n')) 698 | endpos++; 699 | 700 | buf = xmalloc(linelen + 1); 701 | 702 | /* rewind file to where the line stared and load the line */ 703 | if (fseek(fp, startpos, SEEK_SET) == -1) 704 | err(1, "fseek()"); 705 | numread = fread(buf, 1, linelen, fp); 706 | if (numread != linelen) 707 | errx(1, "fread() %zu bytes, expecting %zu bytes", numread, linelen); 708 | 709 | /* terminate buffer */ 710 | buf[linelen] = 0; 711 | 712 | /* advance file pointer over the endline */ 713 | if (fseek(fp, endpos, SEEK_SET) == -1) 714 | err(1, "fseek()"); 715 | 716 | return buf; 717 | } 718 | 719 | /* --------------------------------------------------------------------------- 720 | * Adds contents of specified file to mime_map list. 721 | */ 722 | static void parse_extension_map_file(const char *filename) { 723 | char *buf; 724 | FILE *fp = fopen(filename, "rb"); 725 | 726 | if (fp == NULL) 727 | err(1, "fopen(\"%s\")", filename); 728 | while ((buf = read_line(fp)) != NULL) { 729 | parse_mimetype_line(buf); 730 | free(buf); 731 | } 732 | fclose(fp); 733 | } 734 | 735 | /* Uses the mime_map to determine a Content-Type: for a requested URL. This 736 | * bsearch()es mime_map, so make sure it's sorted first. 737 | */ 738 | static int mime_mapping_cmp_str(const void *a, const void *b) { 739 | return strcmp((const char *)a, 740 | ((const struct mime_mapping *)b)->extension); 741 | } 742 | 743 | static const char *url_content_type(const char *url) { 744 | int period, urllen = (int)strlen(url); 745 | 746 | for (period = urllen - 1; 747 | (period > 0) && (url[period] != '.') && 748 | (urllen - period - 1 <= (int)longest_ext); 749 | period--) 750 | ; 751 | 752 | if ((period >= 0) && (url[period] == '.')) { 753 | struct mime_mapping *result = 754 | bsearch((url + period + 1), mime_map, mime_map_size, 755 | sizeof(struct mime_mapping), mime_mapping_cmp_str); 756 | if (result != NULL) { 757 | assert(strcmp(url + period + 1, result->extension) == 0); 758 | return result->mimetype; 759 | } 760 | } 761 | /* else no period found in the string */ 762 | return default_mimetype; 763 | } 764 | 765 | static const char *get_address_text(const void *addr) { 766 | #ifdef HAVE_INET6 767 | if (inet6) { 768 | static char text_addr[INET6_ADDRSTRLEN]; 769 | inet_ntop(AF_INET6, (const struct in6_addr *)addr, text_addr, 770 | INET6_ADDRSTRLEN); 771 | return text_addr; 772 | } else 773 | #endif 774 | { 775 | return inet_ntoa(*(const struct in_addr *)addr); 776 | } 777 | } 778 | 779 | /* Initialize the sockin global. This is the socket that we accept 780 | * connections from. 781 | */ 782 | static void init_sockin(void) { 783 | struct sockaddr_in addrin; 784 | #ifdef HAVE_INET6 785 | struct sockaddr_in6 addrin6; 786 | #endif 787 | socklen_t addrin_len; 788 | int sockopt; 789 | 790 | #ifdef HAVE_INET6 791 | if (inet6) { 792 | memset(&addrin6, 0, sizeof(addrin6)); 793 | if (inet_pton(AF_INET6, bindaddr ? bindaddr : "::", 794 | &addrin6.sin6_addr) == -1) { 795 | errx(1, "malformed --addr argument"); 796 | } 797 | sockin = socket(PF_INET6, SOCK_STREAM, 0); 798 | } else 799 | #endif 800 | { 801 | memset(&addrin, 0, sizeof(addrin)); 802 | addrin.sin_addr.s_addr = bindaddr ? inet_addr(bindaddr) : INADDR_ANY; 803 | if (addrin.sin_addr.s_addr == (in_addr_t)INADDR_NONE) 804 | errx(1, "malformed --addr argument"); 805 | sockin = socket(PF_INET, SOCK_STREAM, 0); 806 | } 807 | 808 | if (sockin == -1) 809 | err(1, "socket()"); 810 | 811 | /* reuse address */ 812 | sockopt = 1; 813 | if (setsockopt(sockin, SOL_SOCKET, SO_REUSEADDR, 814 | &sockopt, sizeof(sockopt)) == -1) 815 | err(1, "setsockopt(SO_REUSEADDR)"); 816 | 817 | #if 0 818 | /* disable Nagle since we buffer everything ourselves */ 819 | sockopt = 1; 820 | if (setsockopt(sockin, IPPROTO_TCP, TCP_NODELAY, 821 | &sockopt, sizeof(sockopt)) == -1) 822 | err(1, "setsockopt(TCP_NODELAY)"); 823 | #endif 824 | 825 | #ifdef TORTURE 826 | /* torture: cripple the kernel-side send buffer so we can only squeeze out 827 | * one byte at a time (this is for debugging) 828 | */ 829 | sockopt = 1; 830 | if (setsockopt(sockin, SOL_SOCKET, SO_SNDBUF, 831 | &sockopt, sizeof(sockopt)) == -1) 832 | err(1, "setsockopt(SO_SNDBUF)"); 833 | #endif 834 | 835 | /* bind socket */ 836 | #ifdef HAVE_INET6 837 | if (inet6) { 838 | addrin6.sin6_family = AF_INET6; 839 | addrin6.sin6_port = htons(bindport); 840 | if (bind(sockin, (struct sockaddr *)&addrin6, 841 | sizeof(struct sockaddr_in6)) == -1) 842 | err(1, "bind(port %u)", bindport); 843 | 844 | addrin_len = sizeof(addrin6); 845 | if (getsockname(sockin, (struct sockaddr *)&addrin6, &addrin_len) == -1) 846 | err(1, "getsockname()"); 847 | printf("listening on: http://[%s]:%u/\n", 848 | get_address_text(&addrin6.sin6_addr), bindport); 849 | } else 850 | #endif 851 | { 852 | addrin.sin_family = (u_char)PF_INET; 853 | addrin.sin_port = htons(bindport); 854 | if (bind(sockin, (struct sockaddr *)&addrin, 855 | sizeof(struct sockaddr_in)) == -1) 856 | err(1, "bind(port %u)", bindport); 857 | addrin_len = sizeof(addrin); 858 | if (getsockname(sockin, (struct sockaddr *)&addrin, &addrin_len) == -1) 859 | err(1, "getsockname()"); 860 | printf("listening on: http://%s:%u/\n", 861 | get_address_text(&addrin.sin_addr), bindport); 862 | } 863 | 864 | /* listen on socket */ 865 | if (listen(sockin, max_connections) == -1) 866 | err(1, "listen()"); 867 | 868 | /* enable acceptfilter (this is only available on FreeBSD) */ 869 | if (want_accf) { 870 | #if defined(__FreeBSD__) 871 | struct accept_filter_arg filt = {"httpready", ""}; 872 | if (setsockopt(sockin, SOL_SOCKET, SO_ACCEPTFILTER, 873 | &filt, sizeof(filt)) == -1) 874 | fprintf(stderr, "cannot enable acceptfilter: %s\n", 875 | strerror(errno)); 876 | else 877 | printf("enabled acceptfilter\n"); 878 | #else 879 | printf("this platform doesn't support acceptfilter\n"); 880 | #endif 881 | } 882 | } 883 | 884 | static void usage(const char *argv0) { 885 | printf("usage:\t%s /path/to/wwwroot [flags]\n\n", argv0); 886 | printf("flags:\t--port number (default: %u, or 80 if running as root)\n" 887 | "\t\tSpecifies which port to listen on for connections.\n" 888 | "\t\tPass 0 to let the system choose any free port for you.\n\n", bindport); 889 | printf("\t--addr ip (default: all)\n" 890 | "\t\tIf multiple interfaces are present, specifies\n" 891 | "\t\twhich one to bind the listening port to.\n\n"); 892 | printf("\t--maxconn number (default: system maximum)\n" 893 | "\t\tSpecifies how many concurrent connections to accept.\n\n"); 894 | printf("\t--log filename (default: stdout)\n" 895 | "\t\tSpecifies which file to append the request log to.\n\n"); 896 | printf("\t--chroot (default: don't chroot)\n" 897 | "\t\tLocks server into wwwroot directory for added security.\n\n"); 898 | printf("\t--daemon (default: don't daemonize)\n" 899 | "\t\tDetach from the controlling terminal and run in the background.\n\n"); 900 | printf("\t--index filename (default: %s)\n" 901 | "\t\tDefault file to serve when a directory is requested.\n\n", 902 | index_name); 903 | printf("\t--no-listing\n" 904 | "\t\tDo not serve listing if directory is requested.\n\n"); 905 | printf("\t--ignored-extensions (optional)\n" 906 | "\t\tfile with specified extension name will not be indexed and showed,\n" 907 | "\t\textension name starts with '.' and is separated by ','.\n\n"); 908 | printf("\t--mimetypes filename (optional)\n" 909 | "\t\tParses specified file for extension-MIME associations.\n\n"); 910 | printf("\t--default-mimetype string (optional, default: %s)\n" 911 | "\t\tFiles with unknown extensions are served as this mimetype.\n\n", 912 | octet_stream); 913 | printf("\t--uid uid/uname, --gid gid/gname (default: don't privdrop)\n" 914 | "\t\tDrops privileges to given uid:gid after initialization.\n\n"); 915 | printf("\t--pidfile filename (default: no pidfile)\n" 916 | "\t\tWrite PID to the specified file. Note that if you are\n" 917 | "\t\tusing --chroot, then the pidfile must be relative to,\n" 918 | "\t\tand inside the wwwroot.\n\n"); 919 | printf("\t--no-keepalive\n" 920 | "\t\tDisables HTTP Keep-Alive functionality.\n\n"); 921 | #ifdef __FreeBSD__ 922 | printf("\t--accf (default: don't use acceptfilter)\n" 923 | "\t\tUse acceptfilter. Needs the accf_http module loaded.\n\n"); 924 | #endif 925 | printf("\t--forward host url (default: don't forward)\n" 926 | "\t\tWeb forward (301 redirect).\n" 927 | "\t\tRequests to the host are redirected to the corresponding url.\n" 928 | "\t\tThe option may be specified multiple times, in which case\n" 929 | "\t\tthe host is matched in order of appearance.\n\n"); 930 | printf("\t--forward-all url (default: don't forward)\n" 931 | "\t\tWeb forward (301 redirect).\n" 932 | "\t\tAll requests are redirected to the corresponding url.\n\n"); 933 | printf("\t--no-server-id\n" 934 | "\t\tDon't identify the server type in headers\n" 935 | "\t\tor directory listings.\n\n"); 936 | printf("\t--auth username:password\n" 937 | "\t\tEnable basic authentication.\n\n"); 938 | printf("\t--timeout secs (default: %d)\n" 939 | "\t\tIf a connection is idle for more than this many seconds,\n" 940 | "\t\tit will be closed. Set to zero to disable timeouts.\n\n", 941 | timeout_secs); 942 | #ifdef HAVE_INET6 943 | printf("\t--ipv6\n" 944 | "\t\tListen on IPv6 address.\n\n"); 945 | #else 946 | printf("\t(This binary was built without IPv6 support: -DNO_IPV6)\n\n"); 947 | #endif 948 | } 949 | 950 | static char *base64_encode(char *str) { 951 | const char base64_table[] = { 952 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 953 | 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 954 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 955 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 956 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 957 | 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 958 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', 959 | '4', '5', '6', '7', '8', '9', '+', '/'}; 960 | 961 | int input_length = strlen(str); 962 | int output_length = 4 * ((input_length + 2) / 3); 963 | 964 | char *encoded_data = malloc(output_length+1); 965 | if (encoded_data == NULL) return NULL; 966 | 967 | for (int i = 0, j = 0; i < input_length;) { 968 | 969 | uint32_t octet_a = i < input_length ? (unsigned char)str[i++] : 0; 970 | uint32_t octet_b = i < input_length ? (unsigned char)str[i++] : 0; 971 | uint32_t octet_c = i < input_length ? (unsigned char)str[i++] : 0; 972 | 973 | uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; 974 | 975 | encoded_data[j++] = base64_table[(triple >> 3 * 6) & 0x3F]; 976 | encoded_data[j++] = base64_table[(triple >> 2 * 6) & 0x3F]; 977 | encoded_data[j++] = base64_table[(triple >> 1 * 6) & 0x3F]; 978 | encoded_data[j++] = base64_table[(triple >> 0 * 6) & 0x3F]; 979 | } 980 | 981 | const int mod_table[] = {0, 2, 1}; 982 | for (int i = 0; i < mod_table[input_length % 3]; i++) 983 | encoded_data[output_length - 1 - i] = '='; 984 | encoded_data[output_length] = '\0'; 985 | 986 | return encoded_data; 987 | } 988 | 989 | /* Returns 1 if string is a number, 0 otherwise. Set num to NULL if 990 | * disinterested in value. 991 | */ 992 | static int str_to_num(const char *str, long long *num) { 993 | char *endptr; 994 | long long n; 995 | 996 | errno = 0; 997 | n = strtoll(str, &endptr, 10); 998 | if (*endptr != '\0') 999 | return 0; 1000 | if (n == LLONG_MIN && errno == ERANGE) 1001 | return 0; 1002 | if (n == LLONG_MAX && errno == ERANGE) 1003 | return 0; 1004 | if (num != NULL) 1005 | *num = n; 1006 | return 1; 1007 | } 1008 | 1009 | /* Returns a valid number or dies. */ 1010 | static long long xstr_to_num(const char *str) { 1011 | long long ret; 1012 | 1013 | if (!str_to_num(str, &ret)) { 1014 | errx(1, "number \"%s\" is invalid", str); 1015 | } 1016 | return ret; 1017 | } 1018 | 1019 | static void parse_commandline(const int argc, char *argv[]) { 1020 | int i; 1021 | size_t len; 1022 | 1023 | if ((argc < 2) || (argc == 2 && strcmp(argv[1], "--help") == 0)) { 1024 | usage(argv[0]); /* no wwwroot given */ 1025 | exit(EXIT_SUCCESS); 1026 | } 1027 | 1028 | if (getuid() == 0) 1029 | bindport = 80; 1030 | 1031 | wwwroot = xstrdup(argv[1]); 1032 | /* Strip ending slash. */ 1033 | len = strlen(wwwroot); 1034 | if (len > 0) 1035 | if (wwwroot[len - 1] == '/') 1036 | wwwroot[len - 1] = '\0'; 1037 | 1038 | /* walk through the remainder of the arguments (if any) */ 1039 | for (i = 2; i < argc; i++) { 1040 | if (strcmp(argv[i], "--port") == 0) { 1041 | if (++i >= argc) 1042 | errx(1, "missing number after --port"); 1043 | bindport = (uint16_t)xstr_to_num(argv[i]); 1044 | } 1045 | else if (strcmp(argv[i], "--addr") == 0) { 1046 | if (++i >= argc) 1047 | errx(1, "missing ip after --addr"); 1048 | bindaddr = argv[i]; 1049 | } 1050 | else if (strcmp(argv[i], "--maxconn") == 0) { 1051 | if (++i >= argc) 1052 | errx(1, "missing number after --maxconn"); 1053 | max_connections = (int)xstr_to_num(argv[i]); 1054 | } 1055 | else if (strcmp(argv[i], "--log") == 0) { 1056 | if (++i >= argc) 1057 | errx(1, "missing filename after --log"); 1058 | logfile_name = argv[i]; 1059 | } 1060 | else if (strcmp(argv[i], "--chroot") == 0) { 1061 | want_chroot = 1; 1062 | } 1063 | else if (strcmp(argv[i], "--daemon") == 0) { 1064 | want_daemon = 1; 1065 | } 1066 | else if (strcmp(argv[i], "--index") == 0) { 1067 | if (++i >= argc) 1068 | errx(1, "missing filename after --index"); 1069 | index_name = argv[i]; 1070 | } 1071 | else if (strcmp(argv[i], "--no-listing") == 0) { 1072 | no_listing = 1; 1073 | } 1074 | else if (strcmp(argv[i], "--ignored-extensions") == 0) { 1075 | if (++i >= argc) 1076 | errx(1, "missing extension name list after --ignored-extensions"); 1077 | 1078 | char * comma_ocr,*start_pos=argv[i]; 1079 | int i=0; 1080 | for(;i= argc) 1107 | errx(1, "missing filename after --mimetypes"); 1108 | parse_extension_map_file(argv[i]); 1109 | } 1110 | else if (strcmp(argv[i], "--default-mimetype") == 0) { 1111 | if (++i >= argc) 1112 | errx(1, "missing string after --default-mimetype"); 1113 | default_mimetype = argv[i]; 1114 | } 1115 | else if (strcmp(argv[i], "--uid") == 0) { 1116 | struct passwd *p; 1117 | if (++i >= argc) 1118 | errx(1, "missing uid after --uid"); 1119 | p = getpwnam(argv[i]); 1120 | if (!p) { 1121 | p = getpwuid((uid_t)xstr_to_num(argv[i])); 1122 | } 1123 | if (!p) 1124 | errx(1, "no such uid: `%s'", argv[i]); 1125 | drop_uid = p->pw_uid; 1126 | } 1127 | else if (strcmp(argv[i], "--gid") == 0) { 1128 | struct group *g; 1129 | if (++i >= argc) 1130 | errx(1, "missing gid after --gid"); 1131 | g = getgrnam(argv[i]); 1132 | if (!g) { 1133 | g = getgrgid((gid_t)xstr_to_num(argv[i])); 1134 | } 1135 | if (!g) { 1136 | errx(1, "no such gid: `%s'", argv[i]); 1137 | } 1138 | drop_gid = g->gr_gid; 1139 | } 1140 | else if (strcmp(argv[i], "--pidfile") == 0) { 1141 | if (++i >= argc) 1142 | errx(1, "missing filename after --pidfile"); 1143 | pidfile_name = argv[i]; 1144 | } 1145 | else if (strcmp(argv[i], "--no-keepalive") == 0) { 1146 | want_keepalive = 0; 1147 | } 1148 | else if (strcmp(argv[i], "--accf") == 0) { 1149 | want_accf = 1; 1150 | } 1151 | else if (strcmp(argv[i], "--forward") == 0) { 1152 | const char *host, *url; 1153 | if (++i >= argc) 1154 | errx(1, "missing host after --forward"); 1155 | host = argv[i]; 1156 | if (++i >= argc) 1157 | errx(1, "missing url after --forward"); 1158 | url = argv[i]; 1159 | add_forward_mapping(host, url); 1160 | } 1161 | else if (strcmp(argv[i], "--forward-all") == 0) { 1162 | if (++i >= argc) 1163 | errx(1, "missing url after --forward-all"); 1164 | forward_all_url = argv[i]; 1165 | } 1166 | else if (strcmp(argv[i], "--no-server-id") == 0) { 1167 | want_server_id = 0; 1168 | } 1169 | else if (strcmp(argv[i], "--auth") == 0) { 1170 | if (++i >= argc || strchr(argv[i], ':') == NULL) 1171 | errx(1, "missing 'user:pass' after --auth"); 1172 | 1173 | char *key = base64_encode(argv[i]); 1174 | xasprintf(&auth_key, "Basic %s", key); 1175 | free(key); 1176 | } else if (strcmp(argv[i], "--timeout") == 0) { 1177 | if (++i >= argc) 1178 | errx(1, "missing number after --timeout"); 1179 | timeout_secs = (int)xstr_to_num(argv[i]); 1180 | } 1181 | #ifdef HAVE_INET6 1182 | else if (strcmp(argv[i], "--ipv6") == 0) { 1183 | inet6 = 1; 1184 | } 1185 | #endif 1186 | else 1187 | errx(1, "unknown argument `%s'", argv[i]); 1188 | } 1189 | } 1190 | 1191 | /* Allocate and initialize an empty connection. */ 1192 | static struct connection *new_connection(void) { 1193 | struct connection *conn = xmalloc(sizeof(struct connection)); 1194 | 1195 | conn->socket = -1; 1196 | memset(&conn->client, 0, sizeof(conn->client)); 1197 | conn->last_active = now; 1198 | conn->request = NULL; 1199 | conn->request_length = 0; 1200 | conn->method = NULL; 1201 | conn->url = NULL; 1202 | conn->referer = NULL; 1203 | conn->user_agent = NULL; 1204 | conn->authorization = NULL; 1205 | conn->range_begin = 0; 1206 | conn->range_end = 0; 1207 | conn->range_begin_given = 0; 1208 | conn->range_end_given = 0; 1209 | conn->header = NULL; 1210 | conn->header_length = 0; 1211 | conn->header_sent = 0; 1212 | conn->header_dont_free = 0; 1213 | conn->header_only = 0; 1214 | conn->http_code = 0; 1215 | conn->conn_close = 1; 1216 | conn->reply = NULL; 1217 | conn->reply_dont_free = 0; 1218 | conn->reply_fd = -1; 1219 | conn->reply_start = 0; 1220 | conn->reply_length = 0; 1221 | conn->reply_sent = 0; 1222 | conn->total_sent = 0; 1223 | 1224 | /* Make it harmless so it gets garbage-collected if it should, for some 1225 | * reason, fail to be correctly filled out. 1226 | */ 1227 | conn->state = DONE; 1228 | 1229 | return conn; 1230 | } 1231 | 1232 | /* Accept a connection from sockin and add it to the connection queue. */ 1233 | static void accept_connection(void) { 1234 | struct sockaddr_in addrin; 1235 | #ifdef HAVE_INET6 1236 | struct sockaddr_in6 addrin6; 1237 | #endif 1238 | socklen_t sin_size; 1239 | struct connection *conn; 1240 | int fd; 1241 | 1242 | #ifdef HAVE_INET6 1243 | if (inet6) { 1244 | sin_size = sizeof(addrin6); 1245 | memset(&addrin6, 0, sin_size); 1246 | fd = accept(sockin, (struct sockaddr *)&addrin6, &sin_size); 1247 | } else 1248 | #endif 1249 | { 1250 | sin_size = sizeof(addrin); 1251 | memset(&addrin, 0, sin_size); 1252 | fd = accept(sockin, (struct sockaddr *)&addrin, &sin_size); 1253 | } 1254 | 1255 | if (fd == -1) { 1256 | /* Failed to accept, but try to keep serving existing connections. */ 1257 | if (errno == EMFILE || errno == ENFILE) accepting = 0; 1258 | warn("accept()"); 1259 | return; 1260 | } 1261 | 1262 | /* Allocate and initialize struct connection. */ 1263 | conn = new_connection(); 1264 | conn->socket = fd; 1265 | nonblock_socket(conn->socket); 1266 | conn->state = RECV_REQUEST; 1267 | 1268 | #ifdef HAVE_INET6 1269 | if (inet6) { 1270 | conn->client = addrin6.sin6_addr; 1271 | } else 1272 | #endif 1273 | { 1274 | *(in_addr_t *)&conn->client = addrin.sin_addr.s_addr; 1275 | } 1276 | LIST_INSERT_HEAD(&connlist, conn, entries); 1277 | 1278 | if (debug) 1279 | printf("accepted connection from %s:%u (fd %d)\n", 1280 | inet_ntoa(addrin.sin_addr), 1281 | ntohs(addrin.sin_port), 1282 | conn->socket); 1283 | 1284 | /* Try to read straight away rather than going through another iteration 1285 | * of the select() loop. 1286 | */ 1287 | poll_recv_request(conn); 1288 | } 1289 | 1290 | /* Should this character be logencoded? 1291 | */ 1292 | static int needs_logencoding(const unsigned char c) { 1293 | return ((c <= 0x1F) || (c >= 0x7F) || (c == '"')); 1294 | } 1295 | 1296 | /* Encode string for logging. 1297 | */ 1298 | static void logencode(const char *src, char *dest) { 1299 | static const char hex[] = "0123456789ABCDEF"; 1300 | int i, j; 1301 | 1302 | for (i = j = 0; src[i] != '\0'; i++) { 1303 | if (needs_logencoding((unsigned char)src[i])) { 1304 | dest[j++] = '%'; 1305 | dest[j++] = hex[(src[i] >> 4) & 0xF]; 1306 | dest[j++] = hex[ src[i] & 0xF]; 1307 | } 1308 | else 1309 | dest[j++] = src[i]; 1310 | } 1311 | dest[j] = '\0'; 1312 | } 1313 | 1314 | /* Add a connection's details to the logfile. */ 1315 | static void log_connection(const struct connection *conn) { 1316 | char *safe_method, *safe_url, *safe_referer, *safe_user_agent; 1317 | 1318 | if (logfile == NULL) 1319 | return; 1320 | if (conn->http_code == 0) 1321 | return; /* invalid - died in request */ 1322 | if (conn->method == NULL) 1323 | return; /* invalid - didn't parse - maybe too long */ 1324 | 1325 | #define make_safe(x) \ 1326 | if (conn->x) { \ 1327 | safe_##x = xmalloc(strlen(conn->x)*3 + 1); \ 1328 | logencode(conn->x, safe_##x); \ 1329 | } else { \ 1330 | safe_##x = NULL; \ 1331 | } 1332 | 1333 | make_safe(method); 1334 | make_safe(url); 1335 | make_safe(referer); 1336 | make_safe(user_agent); 1337 | 1338 | #define use_safe(x) safe_##x ? safe_##x : "" 1339 | 1340 | fprintf(logfile, "%lu %s \"%s %s\" %d %llu \"%s\" \"%s\"\n", 1341 | (unsigned long int)now, 1342 | get_address_text(&conn->client), 1343 | use_safe(method), 1344 | use_safe(url), 1345 | conn->http_code, 1346 | llu(conn->total_sent), 1347 | use_safe(referer), 1348 | use_safe(user_agent) 1349 | ); 1350 | fflush(logfile); 1351 | 1352 | #define free_safe(x) if (safe_##x) free(safe_##x); 1353 | 1354 | free_safe(method); 1355 | free_safe(url); 1356 | free_safe(referer); 1357 | free_safe(user_agent); 1358 | 1359 | #undef make_safe 1360 | #undef use_safe 1361 | #undef free_safe 1362 | } 1363 | 1364 | /* Log a connection, then cleanly deallocate its internals. */ 1365 | static void free_connection(struct connection *conn) { 1366 | if (debug) printf("free_connection(%d)\n", conn->socket); 1367 | log_connection(conn); 1368 | if (conn->socket != -1) xclose(conn->socket); 1369 | if (conn->request != NULL) free(conn->request); 1370 | if (conn->method != NULL) free(conn->method); 1371 | if (conn->url != NULL) free(conn->url); 1372 | if (conn->referer != NULL) free(conn->referer); 1373 | if (conn->user_agent != NULL) free(conn->user_agent); 1374 | if (conn->authorization != NULL) free(conn->authorization); 1375 | if (conn->header != NULL && !conn->header_dont_free) free(conn->header); 1376 | if (conn->reply != NULL && !conn->reply_dont_free) free(conn->reply); 1377 | if (conn->reply_fd != -1) xclose(conn->reply_fd); 1378 | /* If we ran out of sockets, try to resume accepting. */ 1379 | accepting = 1; 1380 | } 1381 | 1382 | /* Recycle a finished connection for HTTP/1.1 Keep-Alive. */ 1383 | static void recycle_connection(struct connection *conn) { 1384 | int socket_tmp = conn->socket; 1385 | if (debug) 1386 | printf("recycle_connection(%d)\n", socket_tmp); 1387 | conn->socket = -1; /* so free_connection() doesn't close it */ 1388 | free_connection(conn); 1389 | conn->socket = socket_tmp; 1390 | 1391 | /* don't reset conn->client */ 1392 | conn->request = NULL; 1393 | conn->request_length = 0; 1394 | conn->method = NULL; 1395 | conn->url = NULL; 1396 | conn->referer = NULL; 1397 | conn->user_agent = NULL; 1398 | conn->authorization = NULL; 1399 | conn->range_begin = 0; 1400 | conn->range_end = 0; 1401 | conn->range_begin_given = 0; 1402 | conn->range_end_given = 0; 1403 | conn->header = NULL; 1404 | conn->header_length = 0; 1405 | conn->header_sent = 0; 1406 | conn->header_dont_free = 0; 1407 | conn->header_only = 0; 1408 | conn->http_code = 0; 1409 | conn->conn_close = 1; 1410 | conn->reply = NULL; 1411 | conn->reply_dont_free = 0; 1412 | conn->reply_fd = -1; 1413 | conn->reply_start = 0; 1414 | conn->reply_length = 0; 1415 | conn->reply_sent = 0; 1416 | conn->total_sent = 0; 1417 | 1418 | conn->state = RECV_REQUEST; /* ready for another */ 1419 | } 1420 | 1421 | /* Uppercasify all characters in a string of given length. */ 1422 | static void strntoupper(char *str, const size_t length) { 1423 | size_t i; 1424 | 1425 | for (i = 0; i < length; i++) 1426 | str[i] = (char)toupper(str[i]); 1427 | } 1428 | 1429 | /* If a connection has been idle for more than timeout_secs, it will be 1430 | * marked as DONE and killed off in httpd_poll(). 1431 | */ 1432 | static void poll_check_timeout(struct connection *conn) { 1433 | if (timeout_secs > 0) { 1434 | if (now - conn->last_active >= timeout_secs) { 1435 | if (debug) 1436 | printf("poll_check_timeout(%d) closing connection\n", 1437 | conn->socket); 1438 | conn->conn_close = 1; 1439 | conn->state = DONE; 1440 | } 1441 | } 1442 | } 1443 | 1444 | /* Format [when] as an RFC1123 date, stored in the specified buffer. The same 1445 | * buffer is returned for convenience. 1446 | */ 1447 | #define DATE_LEN 30 /* strlen("Fri, 28 Feb 2003 00:02:08 GMT")+1 */ 1448 | static char *rfc1123_date(char *dest, const time_t when) { 1449 | time_t when_copy = when; 1450 | if (strftime(dest, DATE_LEN, 1451 | "%a, %d %b %Y %H:%M:%S GMT", gmtime(&when_copy)) == 0) 1452 | errx(1, "strftime() failed [%s]", dest); 1453 | return dest; 1454 | } 1455 | 1456 | /* Decode URL by converting %XX (where XX are hexadecimal digits) to the 1457 | * character it represents. Don't forget to free the return value. 1458 | */ 1459 | static char *urldecode(const char *url) { 1460 | size_t i, pos, len = strlen(url); 1461 | char *out = xmalloc(len+1); 1462 | 1463 | for (i = 0, pos = 0; i < len; i++) { 1464 | if ((url[i] == '%') && (i+2 < len) && 1465 | isxdigit(url[i+1]) && isxdigit(url[i+2])) { 1466 | /* decode %XX */ 1467 | #define HEX_TO_DIGIT(hex) ( \ 1468 | ((hex) >= 'A' && (hex) <= 'F') ? ((hex)-'A'+10): \ 1469 | ((hex) >= 'a' && (hex) <= 'f') ? ((hex)-'a'+10): \ 1470 | ((hex)-'0') ) 1471 | 1472 | out[pos++] = HEX_TO_DIGIT(url[i+1]) * 16 + 1473 | HEX_TO_DIGIT(url[i+2]); 1474 | i += 2; 1475 | #undef HEX_TO_DIGIT 1476 | } else { 1477 | /* straight copy */ 1478 | out[pos++] = url[i]; 1479 | } 1480 | } 1481 | out[pos] = '\0'; 1482 | return out; 1483 | } 1484 | 1485 | /* Returns Connection or Keep-Alive header, depending on conn_close. */ 1486 | static const char *keep_alive(const struct connection *conn) 1487 | { 1488 | return (conn->conn_close ? "Connection: close\r\n" : keep_alive_field); 1489 | } 1490 | 1491 | /* "Generated by " + pkgname + " on " + date + "\n" 1492 | * 1234567890123 1234 2 ('\n' and '\0') 1493 | */ 1494 | static char _generated_on_buf[13 + sizeof(pkgname) - 1 + 4 + DATE_LEN + 2]; 1495 | static const char *generated_on(const char date[DATE_LEN]) { 1496 | if (!want_server_id) 1497 | return ""; 1498 | snprintf(_generated_on_buf, sizeof(_generated_on_buf), 1499 | "Generated by %s on %s\n", 1500 | pkgname, date); 1501 | return _generated_on_buf; 1502 | } 1503 | 1504 | /* A default reply for any (erroneous) occasion. */ 1505 | static void default_reply(struct connection *conn, 1506 | const int errcode, const char *errname, const char *format, ...) 1507 | __printflike(4, 5); 1508 | static void default_reply(struct connection *conn, 1509 | const int errcode, const char *errname, const char *format, ...) { 1510 | char *reason, date[DATE_LEN]; 1511 | va_list va; 1512 | 1513 | va_start(va, format); 1514 | xvasprintf(&reason, format, va); 1515 | va_end(va); 1516 | 1517 | /* Only really need to calculate the date once. */ 1518 | rfc1123_date(date, now); 1519 | 1520 | conn->reply_length = xasprintf(&(conn->reply), 1521 | "%d %s\n" 1522 | "

%s

\n" /* errname */ 1523 | "%s\n" /* reason */ 1524 | "
\n" 1525 | "%s" /* generated on */ 1526 | "\n", 1527 | errcode, errname, errname, reason, generated_on(date)); 1528 | free(reason); 1529 | 1530 | const char *auth_header = 1531 | "WWW-Authenticate: Basic realm=\"User Visible Realm\""; 1532 | 1533 | conn->header_length = xasprintf(&(conn->header), 1534 | "HTTP/1.1 %d %s\r\n" 1535 | "Date: %s\r\n" 1536 | "%s" /* server */ 1537 | "Accept-Ranges: bytes\r\n" 1538 | "%s" /* keep-alive */ 1539 | "Content-Length: %llu\r\n" 1540 | "Content-Type: text/html; charset=UTF-8\r\n" 1541 | "%s\r\n" 1542 | "\r\n", 1543 | errcode, errname, date, server_hdr, keep_alive(conn), 1544 | llu(conn->reply_length), 1545 | (auth_key != NULL ? auth_header : "")); 1546 | 1547 | conn->reply_type = REPLY_GENERATED; 1548 | conn->http_code = errcode; 1549 | } 1550 | 1551 | static void redirect(struct connection *conn, const char *format, ...) 1552 | __printflike(2, 3); 1553 | static void redirect(struct connection *conn, const char *format, ...) { 1554 | char *where, date[DATE_LEN]; 1555 | va_list va; 1556 | 1557 | va_start(va, format); 1558 | xvasprintf(&where, format, va); 1559 | va_end(va); 1560 | 1561 | /* Only really need to calculate the date once. */ 1562 | rfc1123_date(date, now); 1563 | 1564 | conn->reply_length = xasprintf(&(conn->reply), 1565 | "301 Moved Permanently\n" 1566 | "

Moved Permanently

\n" 1567 | "Moved to: %s\n" /* where x 2 */ 1568 | "
\n" 1569 | "%s" /* generated on */ 1570 | "\n", 1571 | where, where, generated_on(date)); 1572 | 1573 | conn->header_length = xasprintf(&(conn->header), 1574 | "HTTP/1.1 301 Moved Permanently\r\n" 1575 | "Date: %s\r\n" 1576 | "%s" /* server */ 1577 | /* "Accept-Ranges: bytes\r\n" - not relevant here */ 1578 | "Location: %s\r\n" 1579 | "%s" /* keep-alive */ 1580 | "Content-Length: %llu\r\n" 1581 | "Content-Type: text/html; charset=UTF-8\r\n" 1582 | "\r\n", 1583 | date, server_hdr, where, keep_alive(conn), llu(conn->reply_length)); 1584 | 1585 | free(where); 1586 | conn->reply_type = REPLY_GENERATED; 1587 | conn->http_code = 301; 1588 | } 1589 | 1590 | /* Parses a single HTTP request field. Returns string from end of [field] to 1591 | * first \r, \n or end of request string. Returns NULL if [field] can't be 1592 | * matched. 1593 | * 1594 | * You need to remember to deallocate the result. 1595 | * example: parse_field(conn, "Referer: "); 1596 | */ 1597 | static char *parse_field(const struct connection *conn, const char *field) { 1598 | size_t bound1, bound2; 1599 | char *pos; 1600 | 1601 | /* find start */ 1602 | pos = strstr(conn->request, field); 1603 | if (pos == NULL) 1604 | return NULL; 1605 | assert(pos >= conn->request); 1606 | bound1 = (size_t)(pos - conn->request) + strlen(field); 1607 | 1608 | /* find end */ 1609 | for (bound2 = bound1; 1610 | ((bound2 < conn->request_length) && 1611 | (conn->request[bound2] != '\r') && 1612 | (conn->request[bound2] != '\n')); 1613 | bound2++) 1614 | ; 1615 | 1616 | /* copy to buffer */ 1617 | return split_string(conn->request, bound1, bound2); 1618 | } 1619 | 1620 | /* Parse a Range: field into range_begin and range_end. Only handles the 1621 | * first range if a list is given. Sets range_{begin,end}_given to 1 if 1622 | * either part of the range is given. 1623 | */ 1624 | static void parse_range_field(struct connection *conn) { 1625 | char *range; 1626 | 1627 | range = parse_field(conn, "Range: bytes="); 1628 | if (range == NULL) 1629 | return; 1630 | 1631 | do { 1632 | size_t bound1, bound2, len; 1633 | len = strlen(range); 1634 | 1635 | /* parse number up to hyphen */ 1636 | bound1 = 0; 1637 | for (bound2=0; 1638 | (bound2 < len) && isdigit((int)range[bound2]); 1639 | bound2++) 1640 | ; 1641 | 1642 | if ((bound2 == len) || (range[bound2] != '-')) 1643 | break; /* there must be a hyphen here */ 1644 | 1645 | if (bound1 != bound2) { 1646 | conn->range_begin_given = 1; 1647 | conn->range_begin = (off_t)strtoll(range+bound1, NULL, 10); 1648 | } 1649 | 1650 | /* parse number after hyphen */ 1651 | bound2++; 1652 | for (bound1=bound2; 1653 | (bound2 < len) && isdigit((int)range[bound2]); 1654 | bound2++) 1655 | ; 1656 | 1657 | if ((bound2 != len) && (range[bound2] != ',')) 1658 | break; /* must be end of string or a list to be valid */ 1659 | 1660 | if (bound1 != bound2) { 1661 | conn->range_end_given = 1; 1662 | conn->range_end = (off_t)strtoll(range+bound1, NULL, 10); 1663 | } 1664 | } while(0); 1665 | free(range); 1666 | } 1667 | 1668 | /* Parse an HTTP request like "GET / HTTP/1.1" to get the method (GET), the 1669 | * url (/), the referer (if given) and the user-agent (if given). Remember to 1670 | * deallocate all these buffers. The method will be returned in uppercase. 1671 | */ 1672 | static int parse_request(struct connection *conn) { 1673 | size_t bound1, bound2; 1674 | char *tmp; 1675 | assert(conn->request_length == strlen(conn->request)); 1676 | 1677 | /* parse method */ 1678 | for (bound1 = 0; 1679 | (bound1 < conn->request_length) && 1680 | (conn->request[bound1] != ' '); 1681 | bound1++) 1682 | ; 1683 | 1684 | conn->method = split_string(conn->request, 0, bound1); 1685 | strntoupper(conn->method, bound1); 1686 | 1687 | /* parse url */ 1688 | for (; 1689 | (bound1 < conn->request_length) && 1690 | (conn->request[bound1] == ' '); 1691 | bound1++) 1692 | ; 1693 | 1694 | if (bound1 == conn->request_length) 1695 | return 0; /* fail */ 1696 | 1697 | for (bound2 = bound1 + 1; 1698 | (bound2 < conn->request_length) && 1699 | (conn->request[bound2] != ' ') && 1700 | (conn->request[bound2] != '\r') && 1701 | (conn->request[bound2] != '\n'); 1702 | bound2++) 1703 | ; 1704 | 1705 | conn->url = split_string(conn->request, bound1, bound2); 1706 | 1707 | /* parse protocol to determine conn_close */ 1708 | if (conn->request[bound2] == ' ') { 1709 | char *proto; 1710 | for (bound1 = bound2; 1711 | (bound1 < conn->request_length) && 1712 | (conn->request[bound1] == ' '); 1713 | bound1++) 1714 | ; 1715 | 1716 | for (bound2 = bound1 + 1; 1717 | (bound2 < conn->request_length) && 1718 | (conn->request[bound2] != ' ') && 1719 | (conn->request[bound2] != '\r'); 1720 | bound2++) 1721 | ; 1722 | 1723 | proto = split_string(conn->request, bound1, bound2); 1724 | if (strcasecmp(proto, "HTTP/1.1") == 0) 1725 | conn->conn_close = 0; 1726 | free(proto); 1727 | } 1728 | 1729 | /* parse connection field */ 1730 | tmp = parse_field(conn, "Connection: "); 1731 | if (tmp != NULL) { 1732 | if (strcasecmp(tmp, "close") == 0) 1733 | conn->conn_close = 1; 1734 | else if (strcasecmp(tmp, "keep-alive") == 0) 1735 | conn->conn_close = 0; 1736 | free(tmp); 1737 | } 1738 | 1739 | /* cmdline flag can be used to deny keep-alive */ 1740 | if (!want_keepalive) 1741 | conn->conn_close = 1; 1742 | 1743 | /* parse important fields */ 1744 | conn->referer = parse_field(conn, "Referer: "); 1745 | conn->user_agent = parse_field(conn, "User-Agent: "); 1746 | conn->authorization = parse_field(conn, "Authorization: "); 1747 | parse_range_field(conn); 1748 | return 1; 1749 | } 1750 | 1751 | static int file_exists(const char *path) { 1752 | struct stat filestat; 1753 | if ((stat(path, &filestat) == -1) && (errno == ENOENT)) 1754 | return 0; 1755 | else 1756 | return 1; 1757 | } 1758 | 1759 | static int file_to_be_ignored(const char * path) { 1760 | const char * ext = strrchr(path, '.'); 1761 | if(ext==NULL) 1762 | return 0; 1763 | const char * ignored_ext; 1764 | for(int i=0; i < MAX_EXTENSIONS_NUM && ignored_file_exts[i] != NULL; i++) { 1765 | ignored_ext = ignored_file_exts[i]; 1766 | if(!strcmp(ext, ignored_ext)) 1767 | return 1; 1768 | } 1769 | return 0; 1770 | } 1771 | 1772 | struct dlent { 1773 | char *name; 1774 | int is_dir; 1775 | off_t size; 1776 | }; 1777 | 1778 | static int dlent_cmp(const void *a, const void *b) { 1779 | return strcmp((*((const struct dlent * const *)a))->name, 1780 | (*((const struct dlent * const *)b))->name); 1781 | } 1782 | 1783 | /* Make sorted list of files in a directory. Returns number of entries, or -1 1784 | * if error occurs. 1785 | */ 1786 | static ssize_t make_sorted_dirlist(const char *path, struct dlent ***output) { 1787 | DIR *dir; 1788 | struct dirent *ent; 1789 | size_t entries = 0; 1790 | size_t pool = 128; 1791 | char *currname; 1792 | struct dlent **list = NULL; 1793 | 1794 | dir = opendir(path); 1795 | if (dir == NULL) 1796 | return -1; 1797 | 1798 | currname = xmalloc(strlen(path) + MAXNAMLEN + 1); 1799 | list = xmalloc(sizeof(struct dlent*) * pool); 1800 | 1801 | /* construct list */ 1802 | while ((ent = readdir(dir)) != NULL) { 1803 | struct stat s; 1804 | 1805 | if ((ent->d_name[0] == '.') && (ent->d_name[1] == '\0')) 1806 | continue; /* skip "." */ 1807 | assert(strlen(ent->d_name) <= MAXNAMLEN); 1808 | sprintf(currname, "%s%s", path, ent->d_name); 1809 | if (stat(currname, &s) == -1) 1810 | continue; /* skip un-stat-able files */ 1811 | if (entries == pool) { 1812 | pool *= 2; 1813 | list = xrealloc(list, sizeof(struct dlent*) * pool); 1814 | } 1815 | //skip files with specified extensions 1816 | if(!S_ISDIR(s.st_mode) && file_to_be_ignored(ent->d_name)) 1817 | continue; 1818 | 1819 | list[entries] = xmalloc(sizeof(struct dlent)); 1820 | list[entries]->name = xstrdup(ent->d_name); 1821 | list[entries]->is_dir = S_ISDIR(s.st_mode); 1822 | list[entries]->size = s.st_size; 1823 | entries++; 1824 | } 1825 | closedir(dir); 1826 | free(currname); 1827 | qsort(list, entries, sizeof(struct dlent*), dlent_cmp); 1828 | *output = list; 1829 | return (ssize_t)entries; 1830 | } 1831 | 1832 | /* Cleanly deallocate a sorted list of directory files. */ 1833 | static void cleanup_sorted_dirlist(struct dlent **list, const ssize_t size) { 1834 | ssize_t i; 1835 | 1836 | for (i = 0; i < size; i++) { 1837 | free(list[i]->name); 1838 | free(list[i]); 1839 | } 1840 | } 1841 | 1842 | /* Is this an unreserved character according to 1843 | * https://tools.ietf.org/html/rfc3986#section-2.3 1844 | */ 1845 | static int is_unreserved(const unsigned char c) { 1846 | if (c >= 'a' && c <= 'z') return 1; 1847 | if (c >= 'A' && c <= 'Z') return 1; 1848 | if (c >= '0' && c <= '9') return 1; 1849 | switch (c) { 1850 | case '-': 1851 | case '.': 1852 | case '_': 1853 | case '~': 1854 | return 1; 1855 | } 1856 | return 0; 1857 | } 1858 | 1859 | /* Encode string to be an RFC3986-compliant URL part. 1860 | * Contributed by nf. 1861 | */ 1862 | static void urlencode(const char *src, char *dest) { 1863 | static const char hex[] = "0123456789ABCDEF"; 1864 | int i, j; 1865 | 1866 | for (i = j = 0; src[i] != '\0'; i++) { 1867 | if (!is_unreserved((unsigned char)src[i])) { 1868 | dest[j++] = '%'; 1869 | dest[j++] = hex[(src[i] >> 4) & 0xF]; 1870 | dest[j++] = hex[ src[i] & 0xF]; 1871 | } 1872 | else 1873 | dest[j++] = src[i]; 1874 | } 1875 | dest[j] = '\0'; 1876 | } 1877 | 1878 | static void generate_dir_listing(struct connection *conn, const char *path) { 1879 | char date[DATE_LEN], *spaces; 1880 | struct dlent **list; 1881 | ssize_t listsize; 1882 | size_t maxlen = 2; /* There has to be ".." */ 1883 | int i; 1884 | struct apbuf *listing; 1885 | 1886 | listsize = make_sorted_dirlist(path, &list); 1887 | if (listsize == -1) { 1888 | default_reply(conn, 500, "Internal Server Error", 1889 | "Couldn't list directory: %s", strerror(errno)); 1890 | return; 1891 | } 1892 | 1893 | for (i=0; iname); 1895 | if (maxlen < tmp) 1896 | maxlen = tmp; 1897 | } 1898 | 1899 | listing = make_apbuf(); 1900 | append(listing, "\n\n "); 1901 | append(listing, conn->url); 1902 | append(listing, "\n\n\n

"); 1903 | append(listing, conn->url); 1904 | append(listing, "

\n
\n");
1905 | 
1906 |     spaces = xmalloc(maxlen);
1907 |     memset(spaces, ' ', maxlen);
1908 | 
1909 |     for (i=0; iname, safe_url);
1916 | 
1917 |         append(listing, "");
1920 |         append(listing, list[i]->name);
1921 |         append(listing, "");
1922 | 
1923 |         if (list[i]->is_dir)
1924 |             append(listing, "/\n");
1925 |         else {
1926 |             appendl(listing, spaces, maxlen-strlen(list[i]->name));
1927 |             appendf(listing, "%10llu\n", llu(list[i]->size));
1928 |         }
1929 |     }
1930 | 
1931 |     cleanup_sorted_dirlist(list, listsize);
1932 |     free(list);
1933 |     free(spaces);
1934 | 
1935 |     append(listing,
1936 |      "
\n" 1937 | "
\n"); 1938 | 1939 | rfc1123_date(date, now); 1940 | append(listing, generated_on(date)); 1941 | append(listing, "\n\n"); 1942 | 1943 | conn->reply = listing->str; 1944 | conn->reply_length = (off_t)listing->length; 1945 | free(listing); /* don't free inside of listing */ 1946 | 1947 | conn->header_length = xasprintf(&(conn->header), 1948 | "HTTP/1.1 200 OK\r\n" 1949 | "Date: %s\r\n" 1950 | "%s" /* server */ 1951 | "Accept-Ranges: bytes\r\n" 1952 | "%s" /* keep-alive */ 1953 | "Content-Length: %llu\r\n" 1954 | "Content-Type: text/html; charset=UTF-8\r\n" 1955 | "\r\n", 1956 | date, server_hdr, keep_alive(conn), llu(conn->reply_length)); 1957 | 1958 | conn->reply_type = REPLY_GENERATED; 1959 | conn->http_code = 200; 1960 | } 1961 | 1962 | /* Process a GET/HEAD request. */ 1963 | static void process_get(struct connection *conn) { 1964 | char *decoded_url, *target, *if_mod_since; 1965 | char date[DATE_LEN], lastmod[DATE_LEN]; 1966 | const char *mimetype = NULL; 1967 | const char *forward_to = NULL; 1968 | struct stat filestat; 1969 | 1970 | /* work out path of file being requested */ 1971 | decoded_url = urldecode(conn->url); 1972 | 1973 | /* make sure it's safe */ 1974 | if (make_safe_url(decoded_url) == NULL) { 1975 | default_reply(conn, 400, "Bad Request", 1976 | "You requested an invalid URL: %s", conn->url); 1977 | free(decoded_url); 1978 | return; 1979 | } 1980 | 1981 | /* test the host against web forward options */ 1982 | if (forward_map) { 1983 | char *host = parse_field(conn, "Host: "); 1984 | if (host) { 1985 | size_t i; 1986 | if (debug) 1987 | printf("host=\"%s\"\n", host); 1988 | for (i = 0; i < forward_map_size; i++) { 1989 | if (strcasecmp(forward_map[i].host, host) == 0) { 1990 | forward_to = forward_map[i].target_url; 1991 | break; 1992 | } 1993 | } 1994 | free(host); 1995 | } 1996 | } 1997 | if (!forward_to) { 1998 | forward_to = forward_all_url; 1999 | } 2000 | if (forward_to) { 2001 | redirect(conn, "%s%s", forward_to, decoded_url); 2002 | free(decoded_url); 2003 | return; 2004 | } 2005 | 2006 | /* does it end in a slash? serve up url/index_name */ 2007 | if (decoded_url[strlen(decoded_url)-1] == '/') { 2008 | xasprintf(&target, "%s%s%s", wwwroot, decoded_url, index_name); 2009 | if (!file_exists(target)) { 2010 | free(target); 2011 | if (no_listing) { 2012 | free(decoded_url); 2013 | /* Return 404 instead of 403 to make --no-listing 2014 | * indistinguishable from the directory not existing. 2015 | * i.e.: Don't leak information. 2016 | */ 2017 | default_reply(conn, 404, "Not Found", 2018 | "The URL you requested (%s) was not found.", conn->url); 2019 | return; 2020 | } 2021 | xasprintf(&target, "%s%s", wwwroot, decoded_url); 2022 | generate_dir_listing(conn, target); 2023 | free(target); 2024 | free(decoded_url); 2025 | return; 2026 | } 2027 | mimetype = url_content_type(index_name); 2028 | } 2029 | else { 2030 | /* points to a file */ 2031 | xasprintf(&target, "%s%s", wwwroot, decoded_url); 2032 | mimetype = url_content_type(decoded_url); 2033 | } 2034 | free(decoded_url); 2035 | if (debug) 2036 | printf("url=\"%s\", target=\"%s\", content-type=\"%s\"\n", 2037 | conn->url, target, mimetype); 2038 | 2039 | /* open file */ 2040 | conn->reply_fd = open(target, O_RDONLY | O_NONBLOCK); 2041 | 2042 | int file_ignored_flag = file_to_be_ignored(target); 2043 | free(target); 2044 | 2045 | if (conn->reply_fd == -1) { 2046 | /* open() failed */ 2047 | if (errno == EACCES) 2048 | default_reply(conn, 403, "Forbidden", 2049 | "You don't have permission to access (%s).", conn->url); 2050 | else if (errno == ENOENT) 2051 | default_reply(conn, 404, "Not Found", 2052 | "The URL you requested (%s) was not found.", conn->url); 2053 | else 2054 | default_reply(conn, 500, "Internal Server Error", 2055 | "The URL you requested (%s) cannot be returned: %s.", 2056 | conn->url, strerror(errno)); 2057 | 2058 | return; 2059 | } 2060 | 2061 | /* stat the file */ 2062 | if (fstat(conn->reply_fd, &filestat) == -1) { 2063 | default_reply(conn, 500, "Internal Server Error", 2064 | "fstat() failed: %s.", strerror(errno)); 2065 | return; 2066 | } 2067 | 2068 | /* make sure it's a regular file */ 2069 | if (S_ISDIR(filestat.st_mode)) { 2070 | redirect(conn, "%s/", conn->url); 2071 | return; 2072 | } 2073 | else if (!S_ISREG(filestat.st_mode)) { 2074 | default_reply(conn, 403, "Forbidden", "Not a regular file."); 2075 | return; 2076 | } 2077 | // detect whether it should be ignored 2078 | else if (file_ignored_flag && !S_ISDIR(filestat.st_mode)) { 2079 | default_reply(conn, 404, "Not Found", 2080 | "The URL you requested (%s) was not found.", conn->url); 2081 | return; 2082 | } 2083 | 2084 | 2085 | conn->reply_type = REPLY_FROMFILE; 2086 | rfc1123_date(lastmod, filestat.st_mtime); 2087 | 2088 | /* check for If-Modified-Since, may not have to send */ 2089 | if_mod_since = parse_field(conn, "If-Modified-Since: "); 2090 | if ((if_mod_since != NULL) && 2091 | (strcmp(if_mod_since, lastmod) == 0)) { 2092 | if (debug) 2093 | printf("not modified since %s\n", if_mod_since); 2094 | conn->http_code = 304; 2095 | conn->header_length = xasprintf(&(conn->header), 2096 | "HTTP/1.1 304 Not Modified\r\n" 2097 | "Date: %s\r\n" 2098 | "%s" /* server */ 2099 | "Accept-Ranges: bytes\r\n" 2100 | "%s" /* keep-alive */ 2101 | "\r\n", 2102 | rfc1123_date(date, now), server_hdr, keep_alive(conn)); 2103 | conn->reply_length = 0; 2104 | conn->reply_type = REPLY_GENERATED; 2105 | conn->header_only = 1; 2106 | 2107 | free(if_mod_since); 2108 | return; 2109 | } 2110 | free(if_mod_since); 2111 | 2112 | if (conn->range_begin_given || conn->range_end_given) { 2113 | off_t from, to; 2114 | 2115 | if (conn->range_begin_given && conn->range_end_given) { 2116 | /* 100-200 */ 2117 | from = conn->range_begin; 2118 | to = conn->range_end; 2119 | 2120 | /* clamp end to filestat.st_size-1 */ 2121 | if (to > (filestat.st_size - 1)) 2122 | to = filestat.st_size - 1; 2123 | } 2124 | else if (conn->range_begin_given && !conn->range_end_given) { 2125 | /* 100- :: yields 100 to end */ 2126 | from = conn->range_begin; 2127 | to = filestat.st_size - 1; 2128 | } 2129 | else if (!conn->range_begin_given && conn->range_end_given) { 2130 | /* -200 :: yields last 200 */ 2131 | to = filestat.st_size - 1; 2132 | from = to - conn->range_end + 1; 2133 | 2134 | /* clamp start */ 2135 | if (from < 0) 2136 | from = 0; 2137 | } 2138 | else 2139 | errx(1, "internal error - from/to mismatch"); 2140 | 2141 | if (from >= filestat.st_size) { 2142 | default_reply(conn, 416, "Requested Range Not Satisfiable", 2143 | "You requested a range outside of the file."); 2144 | return; 2145 | } 2146 | 2147 | if (to < from) { 2148 | default_reply(conn, 416, "Requested Range Not Satisfiable", 2149 | "You requested a backward range."); 2150 | return; 2151 | } 2152 | 2153 | conn->reply_start = from; 2154 | conn->reply_length = to - from + 1; 2155 | 2156 | conn->header_length = xasprintf(&(conn->header), 2157 | "HTTP/1.1 206 Partial Content\r\n" 2158 | "Date: %s\r\n" 2159 | "%s" /* server */ 2160 | "Accept-Ranges: bytes\r\n" 2161 | "%s" /* keep-alive */ 2162 | "Content-Length: %llu\r\n" 2163 | "Content-Range: bytes %llu-%llu/%llu\r\n" 2164 | "Content-Type: %s\r\n" 2165 | "Last-Modified: %s\r\n" 2166 | "\r\n" 2167 | , 2168 | rfc1123_date(date, now), server_hdr, keep_alive(conn), 2169 | llu(conn->reply_length), llu(from), llu(to), 2170 | llu(filestat.st_size), mimetype, lastmod 2171 | ); 2172 | conn->http_code = 206; 2173 | if (debug) 2174 | printf("sending %llu-%llu/%llu\n", 2175 | llu(from), llu(to), llu(filestat.st_size)); 2176 | } 2177 | else { 2178 | /* no range stuff */ 2179 | conn->reply_length = filestat.st_size; 2180 | conn->header_length = xasprintf(&(conn->header), 2181 | "HTTP/1.1 200 OK\r\n" 2182 | "Date: %s\r\n" 2183 | "%s" /* server */ 2184 | "Accept-Ranges: bytes\r\n" 2185 | "%s" /* keep-alive */ 2186 | "Content-Length: %llu\r\n" 2187 | "Content-Type: %s\r\n" 2188 | "Last-Modified: %s\r\n" 2189 | "\r\n" 2190 | , 2191 | rfc1123_date(date, now), server_hdr, keep_alive(conn), 2192 | llu(conn->reply_length), mimetype, lastmod 2193 | ); 2194 | conn->http_code = 200; 2195 | } 2196 | } 2197 | 2198 | /* Process a request: build the header and reply, advance state. */ 2199 | static void process_request(struct connection *conn) { 2200 | num_requests++; 2201 | 2202 | if (!parse_request(conn)) { 2203 | default_reply(conn, 400, "Bad Request", 2204 | "You sent a request that the server couldn't understand."); 2205 | } 2206 | // fail if: (auth_enabled) AND (client supplied invalid credentials) 2207 | if (auth_key != NULL && 2208 | (conn->authorization == NULL || 2209 | strcmp(conn->authorization, auth_key))) 2210 | { 2211 | default_reply(conn, 401, "Unauthorized", 2212 | "Access denied due to invalid credentials."); 2213 | } 2214 | else if (strcmp(conn->method, "GET") == 0) { 2215 | process_get(conn); 2216 | } 2217 | else if (strcmp(conn->method, "HEAD") == 0) { 2218 | process_get(conn); 2219 | conn->header_only = 1; 2220 | } 2221 | else if ((strcmp(conn->method, "OPTIONS") == 0) || 2222 | (strcmp(conn->method, "POST") == 0) || 2223 | (strcmp(conn->method, "PUT") == 0) || 2224 | (strcmp(conn->method, "DELETE") == 0) || 2225 | (strcmp(conn->method, "TRACE") == 0) || 2226 | (strcmp(conn->method, "CONNECT") == 0)) { 2227 | default_reply(conn, 501, "Not Implemented", 2228 | "The method you specified (%s) is not implemented.", 2229 | conn->method); 2230 | } 2231 | else { 2232 | default_reply(conn, 400, "Bad Request", 2233 | "%s is not a valid HTTP/1.1 method.", conn->method); 2234 | } 2235 | 2236 | /* advance state */ 2237 | conn->state = SEND_HEADER; 2238 | 2239 | /* request not needed anymore */ 2240 | free(conn->request); 2241 | conn->request = NULL; /* important: don't free it again later */ 2242 | } 2243 | 2244 | /* Receiving request. */ 2245 | static void poll_recv_request(struct connection *conn) { 2246 | char buf[1<<15]; 2247 | ssize_t recvd; 2248 | 2249 | assert(conn->state == RECV_REQUEST); 2250 | recvd = recv(conn->socket, buf, sizeof(buf), 0); 2251 | if (debug) 2252 | printf("poll_recv_request(%d) got %d bytes\n", 2253 | conn->socket, (int)recvd); 2254 | if (recvd < 1) { 2255 | if (recvd == -1) { 2256 | if (errno == EAGAIN) { 2257 | if (debug) printf("poll_recv_request would have blocked\n"); 2258 | return; 2259 | } 2260 | if (debug) printf("recv(%d) error: %s\n", 2261 | conn->socket, strerror(errno)); 2262 | } 2263 | conn->conn_close = 1; 2264 | conn->state = DONE; 2265 | return; 2266 | } 2267 | conn->last_active = now; 2268 | 2269 | /* append to conn->request */ 2270 | assert(recvd > 0); 2271 | conn->request = xrealloc( 2272 | conn->request, conn->request_length + (size_t)recvd + 1); 2273 | memcpy(conn->request+conn->request_length, buf, (size_t)recvd); 2274 | conn->request_length += (size_t)recvd; 2275 | conn->request[conn->request_length] = 0; 2276 | total_in += (size_t)recvd; 2277 | 2278 | /* process request if we have all of it */ 2279 | if ((conn->request_length > 2) && 2280 | (memcmp(conn->request+conn->request_length-2, "\n\n", 2) == 0)) 2281 | process_request(conn); 2282 | else if ((conn->request_length > 4) && 2283 | (memcmp(conn->request+conn->request_length-4, "\r\n\r\n", 4) == 0)) 2284 | process_request(conn); 2285 | 2286 | /* die if it's too large */ 2287 | if (conn->request_length > MAX_REQUEST_LENGTH) { 2288 | default_reply(conn, 413, "Request Entity Too Large", 2289 | "Your request was dropped because it was too long."); 2290 | conn->state = SEND_HEADER; 2291 | } 2292 | 2293 | /* if we've moved on to the next state, try to send right away, instead of 2294 | * going through another iteration of the select() loop. 2295 | */ 2296 | if (conn->state == SEND_HEADER) 2297 | poll_send_header(conn); 2298 | } 2299 | 2300 | /* Sending header. Assumes conn->header is not NULL. */ 2301 | static void poll_send_header(struct connection *conn) { 2302 | ssize_t sent; 2303 | 2304 | assert(conn->state == SEND_HEADER); 2305 | assert(conn->header_length == strlen(conn->header)); 2306 | 2307 | sent = send(conn->socket, 2308 | conn->header + conn->header_sent, 2309 | conn->header_length - conn->header_sent, 2310 | 0); 2311 | conn->last_active = now; 2312 | if (debug) 2313 | printf("poll_send_header(%d) sent %d bytes\n", 2314 | conn->socket, (int)sent); 2315 | 2316 | /* handle any errors (-1) or closure (0) in send() */ 2317 | if (sent < 1) { 2318 | if ((sent == -1) && (errno == EAGAIN)) { 2319 | if (debug) printf("poll_send_header would have blocked\n"); 2320 | return; 2321 | } 2322 | if (debug && (sent == -1)) 2323 | printf("send(%d) error: %s\n", conn->socket, strerror(errno)); 2324 | conn->conn_close = 1; 2325 | conn->state = DONE; 2326 | return; 2327 | } 2328 | assert(sent > 0); 2329 | conn->header_sent += (size_t)sent; 2330 | conn->total_sent += (size_t)sent; 2331 | total_out += (size_t)sent; 2332 | 2333 | /* check if we're done sending header */ 2334 | if (conn->header_sent == conn->header_length) { 2335 | if (conn->header_only) 2336 | conn->state = DONE; 2337 | else { 2338 | conn->state = SEND_REPLY; 2339 | /* go straight on to body, don't go through another iteration of 2340 | * the select() loop. 2341 | */ 2342 | poll_send_reply(conn); 2343 | } 2344 | } 2345 | } 2346 | 2347 | /* Send chunk on socket from FILE *fp, starting at and of size 2348 | * . Use sendfile() if possible since it's zero-copy on some platforms. 2349 | * Returns the number of bytes sent, 0 on closure, -1 if send() failed, -2 if 2350 | * read error. 2351 | */ 2352 | static ssize_t send_from_file(const int s, const int fd, 2353 | off_t ofs, size_t size) { 2354 | #ifdef __FreeBSD__ 2355 | off_t sent; 2356 | int ret = sendfile(fd, s, ofs, size, NULL, &sent, 0); 2357 | 2358 | /* It is possible for sendfile to send zero bytes due to a blocking 2359 | * condition. Handle this correctly. 2360 | */ 2361 | if (ret == -1) 2362 | if (errno == EAGAIN) 2363 | if (sent == 0) 2364 | return -1; 2365 | else 2366 | return sent; 2367 | else 2368 | return -1; 2369 | else 2370 | return size; 2371 | #else 2372 | #if defined(__linux) || defined(__sun__) 2373 | /* Limit truly ridiculous (LARGEFILE) requests. */ 2374 | if (size > 1<<20) 2375 | size = 1<<20; 2376 | return sendfile(s, fd, &ofs, size); 2377 | #else 2378 | /* Fake sendfile() with read(). */ 2379 | # ifndef min 2380 | # define min(a,b) ( ((a)<(b)) ? (a) : (b) ) 2381 | # endif 2382 | char buf[1<<15]; 2383 | size_t amount = min(sizeof(buf), size); 2384 | ssize_t numread; 2385 | 2386 | if (lseek(fd, ofs, SEEK_SET) == -1) 2387 | err(1, "fseek(%d)", (int)ofs); 2388 | numread = read(fd, buf, amount); 2389 | if (numread == 0) { 2390 | fprintf(stderr, "premature eof on fd %d\n", fd); 2391 | return -1; 2392 | } 2393 | else if (numread == -1) { 2394 | fprintf(stderr, "error reading on fd %d: %s", fd, strerror(errno)); 2395 | return -1; 2396 | } 2397 | else if ((size_t)numread != amount) { 2398 | fprintf(stderr, "read %zd bytes, expecting %zu bytes on fd %d\n", 2399 | numread, amount, fd); 2400 | return -1; 2401 | } 2402 | else 2403 | return send(s, buf, amount, 0); 2404 | #endif 2405 | #endif 2406 | } 2407 | 2408 | /* Sending reply. */ 2409 | static void poll_send_reply(struct connection *conn) 2410 | { 2411 | ssize_t sent; 2412 | 2413 | assert(conn->state == SEND_REPLY); 2414 | assert(!conn->header_only); 2415 | if (conn->reply_type == REPLY_GENERATED) { 2416 | assert(conn->reply_length >= conn->reply_sent); 2417 | sent = send(conn->socket, 2418 | conn->reply + conn->reply_start + conn->reply_sent, 2419 | (size_t)(conn->reply_length - conn->reply_sent), 0); 2420 | } 2421 | else { 2422 | errno = 0; 2423 | assert(conn->reply_length >= conn->reply_sent); 2424 | sent = send_from_file(conn->socket, conn->reply_fd, 2425 | conn->reply_start + conn->reply_sent, 2426 | (size_t)(conn->reply_length - conn->reply_sent)); 2427 | if (debug && (sent < 1)) 2428 | printf("send_from_file returned %lld (errno=%d %s)\n", 2429 | (long long)sent, errno, strerror(errno)); 2430 | } 2431 | conn->last_active = now; 2432 | if (debug) 2433 | printf("poll_send_reply(%d) sent %d: %llu+[%llu-%llu] of %llu\n", 2434 | conn->socket, (int)sent, llu(conn->reply_start), 2435 | llu(conn->reply_sent), llu(conn->reply_sent + sent - 1), 2436 | llu(conn->reply_length)); 2437 | 2438 | /* handle any errors (-1) or closure (0) in send() */ 2439 | if (sent < 1) { 2440 | if (sent == -1) { 2441 | if (errno == EAGAIN) { 2442 | if (debug) 2443 | printf("poll_send_reply would have blocked\n"); 2444 | return; 2445 | } 2446 | if (debug) 2447 | printf("send(%d) error: %s\n", conn->socket, strerror(errno)); 2448 | } 2449 | else if (sent == 0) { 2450 | if (debug) 2451 | printf("send(%d) closure\n", conn->socket); 2452 | } 2453 | conn->conn_close = 1; 2454 | conn->state = DONE; 2455 | return; 2456 | } 2457 | conn->reply_sent += sent; 2458 | conn->total_sent += (size_t)sent; 2459 | total_out += (size_t)sent; 2460 | 2461 | /* check if we're done sending */ 2462 | if (conn->reply_sent == conn->reply_length) 2463 | conn->state = DONE; 2464 | } 2465 | 2466 | /* Main loop of the httpd - a select() and then delegation to accept 2467 | * connections, handle receiving of requests, and sending of replies. 2468 | */ 2469 | static void httpd_poll(void) { 2470 | fd_set recv_set, send_set; 2471 | int max_fd, select_ret; 2472 | struct connection *conn, *next; 2473 | int bother_with_timeout = 0; 2474 | struct timeval timeout, t0, t1; 2475 | 2476 | timeout.tv_sec = timeout_secs; 2477 | timeout.tv_usec = 0; 2478 | 2479 | FD_ZERO(&recv_set); 2480 | FD_ZERO(&send_set); 2481 | max_fd = 0; 2482 | 2483 | /* set recv/send fd_sets */ 2484 | #define MAX_FD_SET(sock, fdset) { FD_SET(sock,fdset); \ 2485 | max_fd = (max_fdstate) { 2490 | case DONE: 2491 | /* do nothing */ 2492 | break; 2493 | 2494 | case RECV_REQUEST: 2495 | MAX_FD_SET(conn->socket, &recv_set); 2496 | bother_with_timeout = 1; 2497 | break; 2498 | 2499 | case SEND_HEADER: 2500 | case SEND_REPLY: 2501 | MAX_FD_SET(conn->socket, &send_set); 2502 | bother_with_timeout = 1; 2503 | break; 2504 | } 2505 | } 2506 | #undef MAX_FD_SET 2507 | 2508 | /* -select- */ 2509 | if (debug) { 2510 | printf("select() with max_fd %d timeout %d\n", 2511 | max_fd, bother_with_timeout ? (int)timeout.tv_sec : 0); 2512 | gettimeofday(&t0, NULL); 2513 | } 2514 | select_ret = select(max_fd + 1, &recv_set, &send_set, NULL, 2515 | (bother_with_timeout) ? &timeout : NULL); 2516 | if (select_ret == 0) { 2517 | if (!bother_with_timeout) 2518 | errx(1, "select() timed out"); 2519 | } 2520 | if (select_ret == -1) { 2521 | if (errno == EINTR) 2522 | return; /* interrupted by signal */ 2523 | else 2524 | err(1, "select() failed"); 2525 | } 2526 | if (debug) { 2527 | long long sec, usec; 2528 | gettimeofday(&t1, NULL); 2529 | sec = t1.tv_sec - t0.tv_sec; 2530 | usec = t1.tv_usec - t0.tv_usec; 2531 | if (usec < 0) { 2532 | usec += 1000000; 2533 | sec--; 2534 | } 2535 | printf("select() returned %d after %lld.%06lld secs\n", 2536 | select_ret, sec, usec); 2537 | } 2538 | 2539 | /* update time */ 2540 | now = time(NULL); 2541 | 2542 | /* poll connections that select() says need attention */ 2543 | if (FD_ISSET(sockin, &recv_set)) 2544 | accept_connection(); 2545 | 2546 | LIST_FOREACH_SAFE(conn, &connlist, entries, next) { 2547 | poll_check_timeout(conn); 2548 | switch (conn->state) { 2549 | case RECV_REQUEST: 2550 | if (FD_ISSET(conn->socket, &recv_set)) poll_recv_request(conn); 2551 | break; 2552 | 2553 | case SEND_HEADER: 2554 | if (FD_ISSET(conn->socket, &send_set)) poll_send_header(conn); 2555 | break; 2556 | 2557 | case SEND_REPLY: 2558 | if (FD_ISSET(conn->socket, &send_set)) poll_send_reply(conn); 2559 | break; 2560 | 2561 | case DONE: 2562 | /* (handled later; ignore for now as it's a valid state) */ 2563 | break; 2564 | } 2565 | 2566 | /* Handling SEND_REPLY could have set the state to done. */ 2567 | if (conn->state == DONE) { 2568 | /* clean out finished connection */ 2569 | if (conn->conn_close) { 2570 | LIST_REMOVE(conn, entries); 2571 | free_connection(conn); 2572 | free(conn); 2573 | } else { 2574 | recycle_connection(conn); 2575 | /* and go right back to recv_request without going through 2576 | * select() again. 2577 | */ 2578 | poll_recv_request(conn); 2579 | } 2580 | } 2581 | } 2582 | } 2583 | 2584 | /* Daemonize helpers. */ 2585 | #define PATH_DEVNULL "/dev/null" 2586 | static int lifeline[2] = { -1, -1 }; 2587 | static int fd_null = -1; 2588 | 2589 | static void daemonize_start(void) { 2590 | pid_t f; 2591 | 2592 | if (pipe(lifeline) == -1) 2593 | err(1, "pipe(lifeline)"); 2594 | 2595 | fd_null = open(PATH_DEVNULL, O_RDWR, 0); 2596 | if (fd_null == -1) 2597 | err(1, "open(" PATH_DEVNULL ")"); 2598 | 2599 | f = fork(); 2600 | if (f == -1) 2601 | err(1, "fork"); 2602 | else if (f != 0) { 2603 | /* parent: wait for child */ 2604 | char tmp[1]; 2605 | int status; 2606 | pid_t w; 2607 | 2608 | if (close(lifeline[1]) == -1) 2609 | warn("close lifeline in parent"); 2610 | if (read(lifeline[0], tmp, sizeof(tmp)) == -1) 2611 | warn("read lifeline in parent"); 2612 | w = waitpid(f, &status, WNOHANG); 2613 | if (w == -1) 2614 | err(1, "waitpid"); 2615 | else if (w == 0) 2616 | /* child is running happily */ 2617 | exit(EXIT_SUCCESS); 2618 | else 2619 | /* child init failed, pass on its exit status */ 2620 | exit(WEXITSTATUS(status)); 2621 | } 2622 | /* else we are the child: continue initializing */ 2623 | } 2624 | 2625 | static void daemonize_finish(void) { 2626 | if (fd_null == -1) 2627 | return; /* didn't daemonize_start() so we're not daemonizing */ 2628 | 2629 | if (setsid() == -1) 2630 | err(1, "setsid"); 2631 | if (close(lifeline[0]) == -1) 2632 | warn("close read end of lifeline in child"); 2633 | if (close(lifeline[1]) == -1) 2634 | warn("couldn't cut the lifeline"); 2635 | 2636 | /* close all our std fds */ 2637 | if (dup2(fd_null, STDIN_FILENO) == -1) 2638 | warn("dup2(stdin)"); 2639 | if (dup2(fd_null, STDOUT_FILENO) == -1) 2640 | warn("dup2(stdout)"); 2641 | if (dup2(fd_null, STDERR_FILENO) == -1) 2642 | warn("dup2(stderr)"); 2643 | if (fd_null > 2) 2644 | close(fd_null); 2645 | } 2646 | 2647 | /* [->] pidfile helpers, based on FreeBSD src/lib/libutil/pidfile.c,v 1.3 2648 | * Original was copyright (c) 2005 Pawel Jakub Dawidek 2649 | */ 2650 | static int pidfile_fd = -1; 2651 | #define PIDFILE_MODE 0600 2652 | 2653 | static void pidfile_remove(void) { 2654 | if (unlink(pidfile_name) == -1) 2655 | err(1, "unlink(pidfile) failed"); 2656 | /* if (flock(pidfile_fd, LOCK_UN) == -1) 2657 | err(1, "unlock(pidfile) failed"); */ 2658 | xclose(pidfile_fd); 2659 | pidfile_fd = -1; 2660 | } 2661 | 2662 | static int pidfile_read(void) { 2663 | char buf[16]; 2664 | int fd, i; 2665 | long long pid; 2666 | 2667 | fd = open(pidfile_name, O_RDONLY); 2668 | if (fd == -1) 2669 | err(1, " after create failed"); 2670 | 2671 | i = (int)read(fd, buf, sizeof(buf) - 1); 2672 | if (i == -1) 2673 | err(1, "read from pidfile failed"); 2674 | xclose(fd); 2675 | buf[i] = '\0'; 2676 | 2677 | if (!str_to_num(buf, &pid)) { 2678 | err(1, "invalid pidfile contents: \"%s\"", buf); 2679 | } 2680 | return (int)pid; 2681 | } 2682 | 2683 | static void pidfile_create(void) { 2684 | int error, fd; 2685 | char pidstr[16]; 2686 | 2687 | /* Open the PID file and obtain exclusive lock. */ 2688 | fd = open(pidfile_name, 2689 | O_WRONLY | O_CREAT | O_EXLOCK | O_TRUNC | O_NONBLOCK, PIDFILE_MODE); 2690 | if (fd == -1) { 2691 | if ((errno == EWOULDBLOCK) || (errno == EEXIST)) 2692 | errx(1, "daemon already running with PID %d", pidfile_read()); 2693 | else 2694 | err(1, "can't create pidfile %s", pidfile_name); 2695 | } 2696 | pidfile_fd = fd; 2697 | 2698 | if (ftruncate(fd, 0) == -1) { 2699 | error = errno; 2700 | pidfile_remove(); 2701 | errno = error; 2702 | err(1, "ftruncate() failed"); 2703 | } 2704 | 2705 | snprintf(pidstr, sizeof(pidstr), "%d", (int)getpid()); 2706 | if (pwrite(fd, pidstr, strlen(pidstr), 0) != (ssize_t)strlen(pidstr)) { 2707 | error = errno; 2708 | pidfile_remove(); 2709 | errno = error; 2710 | err(1, "pwrite() failed"); 2711 | } 2712 | } 2713 | /* [<-] end of pidfile helpers. */ 2714 | 2715 | /* Close all sockets and FILEs and exit. */ 2716 | static void stop_running(int sig unused) { 2717 | running = 0; 2718 | } 2719 | 2720 | /* Execution starts here. */ 2721 | int main(int argc, char **argv) { 2722 | printf("%s, %s\n", pkgname, copyright); 2723 | parse_default_extension_map(); 2724 | parse_commandline(argc, argv); 2725 | /* parse_commandline() might override parts of the extension map by 2726 | * parsing a user-specified file. 2727 | */ 2728 | sort_mime_map(); 2729 | xasprintf(&keep_alive_field, "Keep-Alive: timeout=%d\r\n", timeout_secs); 2730 | if (want_server_id) 2731 | xasprintf(&server_hdr, "Server: %s\r\n", pkgname); 2732 | else 2733 | server_hdr = xstrdup(""); 2734 | init_sockin(); 2735 | 2736 | /* open logfile */ 2737 | if (logfile_name == NULL) 2738 | logfile = stdout; 2739 | else { 2740 | logfile = fopen(logfile_name, "ab"); 2741 | if (logfile == NULL) 2742 | err(1, "opening logfile: fopen(\"%s\")", logfile_name); 2743 | } 2744 | 2745 | if (want_daemon) 2746 | daemonize_start(); 2747 | 2748 | /* signals */ 2749 | if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) 2750 | err(1, "signal(ignore SIGPIPE)"); 2751 | if (signal(SIGINT, stop_running) == SIG_ERR) 2752 | err(1, "signal(SIGINT)"); 2753 | if (signal(SIGTERM, stop_running) == SIG_ERR) 2754 | err(1, "signal(SIGTERM)"); 2755 | 2756 | /* security */ 2757 | if (want_chroot) { 2758 | tzset(); /* read /etc/localtime before we chroot */ 2759 | if (chdir(wwwroot) == -1) 2760 | err(1, "chdir(%s)", wwwroot); 2761 | if (chroot(wwwroot) == -1) 2762 | err(1, "chroot(%s)", wwwroot); 2763 | printf("chrooted to `%s'\n", wwwroot); 2764 | wwwroot[0] = '\0'; /* empty string */ 2765 | } 2766 | if (drop_gid != INVALID_GID) { 2767 | gid_t list[1]; 2768 | list[0] = drop_gid; 2769 | if (setgroups(1, list) == -1) 2770 | err(1, "setgroups([%d])", (int)drop_gid); 2771 | if (setgid(drop_gid) == -1) 2772 | err(1, "setgid(%d)", (int)drop_gid); 2773 | printf("set gid to %d\n", (int)drop_gid); 2774 | } 2775 | if (drop_uid != INVALID_UID) { 2776 | if (setuid(drop_uid) == -1) 2777 | err(1, "setuid(%d)", (int)drop_uid); 2778 | printf("set uid to %d\n", (int)drop_uid); 2779 | } 2780 | 2781 | /* create pidfile */ 2782 | if (pidfile_name) pidfile_create(); 2783 | 2784 | if (want_daemon) daemonize_finish(); 2785 | 2786 | /* main loop */ 2787 | while (running) httpd_poll(); 2788 | 2789 | /* clean exit */ 2790 | xclose(sockin); 2791 | if (logfile != NULL) fclose(logfile); 2792 | if (pidfile_name) pidfile_remove(); 2793 | 2794 | /* close and free connections */ 2795 | { 2796 | struct connection *conn, *next; 2797 | 2798 | LIST_FOREACH_SAFE(conn, &connlist, entries, next) { 2799 | LIST_REMOVE(conn, entries); 2800 | free_connection(conn); 2801 | free(conn); 2802 | } 2803 | } 2804 | 2805 | /* free the mallocs */ 2806 | { 2807 | size_t i; 2808 | for (i=0; i