2016-09-02 07:59:02 +00:00
|
|
|
/* See LICENSE file for license details. */
|
|
|
|
#include <sys/resource.h>
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
|
|
|
|
#include <arpa/inet.h>
|
|
|
|
#include <ctype.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <grp.h>
|
|
|
|
#include <inttypes.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <netdb.h>
|
|
|
|
#include <pwd.h>
|
2017-07-11 10:34:55 +00:00
|
|
|
#include <regex.h>
|
2016-09-02 07:59:02 +00:00
|
|
|
#include <signal.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "arg.h"
|
|
|
|
|
|
|
|
char *argv0;
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
#undef MIN
|
|
|
|
#define MIN(x,y) ((x) < (y) ? (x) : (y))
|
2017-07-11 10:34:55 +00:00
|
|
|
#undef LEN
|
|
|
|
#define LEN(x) (sizeof (x) / sizeof *(x))
|
|
|
|
#undef RELPATH
|
|
|
|
#define RELPATH(x) ((!*(x) || !strcmp(x, "/")) ? "." : ((x) + 1))
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
#define TIMESTAMP_LEN 30
|
|
|
|
|
|
|
|
enum req_field {
|
2017-07-07 11:33:43 +00:00
|
|
|
REQ_HOST,
|
2017-06-20 19:40:00 +00:00
|
|
|
REQ_RANGE,
|
|
|
|
REQ_MOD,
|
|
|
|
NUM_REQ_FIELDS,
|
|
|
|
};
|
|
|
|
|
|
|
|
static char *req_field_str[] = {
|
2017-07-07 11:33:43 +00:00
|
|
|
[REQ_HOST] = "Host",
|
2017-06-20 19:40:00 +00:00
|
|
|
[REQ_RANGE] = "Range",
|
|
|
|
[REQ_MOD] = "If-Modified-Since",
|
|
|
|
};
|
|
|
|
|
|
|
|
enum req_method {
|
|
|
|
M_GET,
|
|
|
|
M_HEAD,
|
|
|
|
NUM_REQ_METHODS,
|
|
|
|
};
|
|
|
|
|
|
|
|
static char *req_method_str[] = {
|
2017-06-27 19:36:14 +00:00
|
|
|
[M_GET] = "GET",
|
|
|
|
[M_HEAD] = "HEAD",
|
2017-06-20 19:40:00 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct request {
|
|
|
|
enum req_method method;
|
|
|
|
char target[PATH_MAX];
|
|
|
|
char field[NUM_REQ_FIELDS][FIELD_MAX];
|
|
|
|
};
|
|
|
|
|
|
|
|
enum status {
|
2016-09-04 21:58:20 +00:00
|
|
|
S_OK = 200,
|
|
|
|
S_PARTIAL_CONTENT = 206,
|
|
|
|
S_MOVED_PERMANENTLY = 301,
|
2017-06-20 19:40:00 +00:00
|
|
|
S_NOT_MODIFIED = 304,
|
2016-09-04 21:58:20 +00:00
|
|
|
S_BAD_REQUEST = 400,
|
|
|
|
S_FORBIDDEN = 403,
|
|
|
|
S_NOT_FOUND = 404,
|
|
|
|
S_METHOD_NOT_ALLOWED = 405,
|
|
|
|
S_REQUEST_TIMEOUT = 408,
|
2017-07-04 19:27:20 +00:00
|
|
|
S_RANGE_NOT_SATISFIABLE = 416,
|
2016-09-04 21:58:20 +00:00
|
|
|
S_REQUEST_TOO_LARGE = 431,
|
|
|
|
S_INTERNAL_SERVER_ERROR = 500,
|
|
|
|
S_VERSION_NOT_SUPPORTED = 505,
|
|
|
|
};
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
static char *status_str[] = {
|
2016-09-04 21:58:20 +00:00
|
|
|
[S_OK] = "OK",
|
|
|
|
[S_PARTIAL_CONTENT] = "Partial Content",
|
|
|
|
[S_MOVED_PERMANENTLY] = "Moved Permanently",
|
2017-06-20 19:40:00 +00:00
|
|
|
[S_NOT_MODIFIED] = "Not Modified",
|
2016-09-04 21:58:20 +00:00
|
|
|
[S_BAD_REQUEST] = "Bad Request",
|
|
|
|
[S_FORBIDDEN] = "Forbidden",
|
|
|
|
[S_NOT_FOUND] = "Not Found",
|
|
|
|
[S_METHOD_NOT_ALLOWED] = "Method Not Allowed",
|
|
|
|
[S_REQUEST_TIMEOUT] = "Request Time-out",
|
2017-07-04 19:27:20 +00:00
|
|
|
[S_RANGE_NOT_SATISFIABLE] = "Range Not Satisfiable",
|
2016-09-04 21:58:20 +00:00
|
|
|
[S_REQUEST_TOO_LARGE] = "Request Header Fields Too Large",
|
|
|
|
[S_INTERNAL_SERVER_ERROR] = "Internal Server Error",
|
|
|
|
[S_VERSION_NOT_SUPPORTED] = "HTTP Version not supported",
|
2016-09-02 07:59:02 +00:00
|
|
|
};
|
|
|
|
|
2017-07-04 20:21:36 +00:00
|
|
|
long long strtonum(const char *, long long, long long, const char **);
|
|
|
|
|
2016-09-02 07:59:02 +00:00
|
|
|
static char *
|
2017-06-20 19:40:00 +00:00
|
|
|
timestamp(time_t t, char buf[TIMESTAMP_LEN])
|
2016-09-02 07:59:02 +00:00
|
|
|
{
|
2017-06-20 19:40:00 +00:00
|
|
|
strftime(buf, TIMESTAMP_LEN, "%a, %d %b %Y %T GMT", gmtime(&t));
|
2016-09-02 07:59:02 +00:00
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
return buf;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2017-06-28 17:07:57 +00:00
|
|
|
static void
|
2016-09-02 07:59:02 +00:00
|
|
|
decode(char src[PATH_MAX], char dest[PATH_MAX])
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
uint8_t n;
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
for (s = src, i = 0; *s; s++, i++) {
|
|
|
|
if (*s == '+') {
|
|
|
|
dest[i] = ' ';
|
|
|
|
} else if (*s == '%' && (sscanf(s + 1, "%2hhx", &n) == 1)) {
|
2016-09-05 18:10:16 +00:00
|
|
|
dest[i] = n;
|
2016-09-02 07:59:02 +00:00
|
|
|
s += 2;
|
|
|
|
} else {
|
|
|
|
dest[i] = *s;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dest[i] = '\0';
|
|
|
|
}
|
|
|
|
|
2017-06-28 17:07:57 +00:00
|
|
|
static void
|
2016-09-02 07:59:02 +00:00
|
|
|
encode(char src[PATH_MAX], char dest[PATH_MAX])
|
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
char *s;
|
|
|
|
|
|
|
|
for (s = src, i = 0; *s; s++) {
|
2017-06-20 19:40:00 +00:00
|
|
|
if (iscntrl(*s) || (unsigned char)*s > 127) {
|
|
|
|
i += snprintf(dest + i, PATH_MAX - i, "%%%02X",
|
|
|
|
(unsigned char)*s);
|
2016-09-02 07:59:02 +00:00
|
|
|
} else {
|
|
|
|
dest[i] = *s;
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
static enum status
|
|
|
|
sendstatus(int fd, enum status s)
|
|
|
|
{
|
2017-06-21 05:41:52 +00:00
|
|
|
static char t[TIMESTAMP_LEN];
|
|
|
|
|
|
|
|
if (dprintf(fd,
|
|
|
|
"HTTP/1.1 %d %s\r\n"
|
|
|
|
"Date: %s\r\n"
|
|
|
|
"Connection: close\r\n"
|
|
|
|
"%s"
|
|
|
|
"Content-Type: text/html\r\n"
|
|
|
|
"\r\n"
|
|
|
|
"<!DOCTYPE html>\n<html>\n\t<head>\n"
|
|
|
|
"\t\t<title>%d %s</title>\n\t</head>\n\t<body>\n"
|
2017-06-27 19:36:52 +00:00
|
|
|
"\t\t<h1>%d %s</h1>\n\t</body>\n</html>\n",
|
2017-07-04 19:47:28 +00:00
|
|
|
s, status_str[s], timestamp(time(NULL), t),
|
2017-06-21 05:41:52 +00:00
|
|
|
(s == S_METHOD_NOT_ALLOWED) ? "Allow: HEAD, GET\r\n" : "",
|
|
|
|
s, status_str[s], s, status_str[s]) < 0) {
|
|
|
|
return S_REQUEST_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
return s;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2017-06-20 19:40:00 +00:00
|
|
|
getrequest(int fd, struct request *r)
|
2016-09-02 07:59:02 +00:00
|
|
|
{
|
2017-06-20 19:40:00 +00:00
|
|
|
size_t hlen, i, mlen;
|
|
|
|
ssize_t off;
|
|
|
|
char h[HEADER_MAX], *p, *q;
|
|
|
|
|
2017-06-28 16:39:38 +00:00
|
|
|
/* empty all fields */
|
|
|
|
memset(r, 0, sizeof(*r));
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
/*
|
|
|
|
* receive header
|
|
|
|
*/
|
|
|
|
for (hlen = 0; ;) {
|
|
|
|
if ((off = read(fd, h + hlen, sizeof(h) - hlen)) < 0) {
|
|
|
|
return sendstatus(fd, S_REQUEST_TIMEOUT);
|
2016-09-02 07:59:02 +00:00
|
|
|
} else if (off == 0) {
|
|
|
|
break;
|
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
hlen += off;
|
|
|
|
if (hlen >= 4 && !memcmp(h + hlen - 4, "\r\n\r\n", 4)) {
|
2016-09-02 07:59:02 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
if (hlen == sizeof(h)) {
|
|
|
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* remove terminating empty line */
|
|
|
|
if (hlen < 2) {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
hlen -= 2;
|
2016-09-02 07:59:02 +00:00
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
/* null-terminate the header */
|
|
|
|
h[hlen] = '\0';
|
|
|
|
|
|
|
|
/*
|
|
|
|
* parse request line
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* METHOD */
|
|
|
|
for (i = 0; i < NUM_REQ_METHODS; i++) {
|
|
|
|
mlen = strlen(req_method_str[i]);
|
|
|
|
if (!strncmp(req_method_str[i], h, mlen)) {
|
|
|
|
r->method = i;
|
|
|
|
break;
|
|
|
|
}
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
if (i == NUM_REQ_METHODS) {
|
2017-06-27 19:37:18 +00:00
|
|
|
return sendstatus(fd, S_METHOD_NOT_ALLOWED);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* a single space must follow the method */
|
|
|
|
if (h[mlen] != ' ') {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* basis for next step */
|
|
|
|
p = h + mlen + 1;
|
|
|
|
|
|
|
|
/* TARGET */
|
|
|
|
if (!(q = strchr(p, ' '))) {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
|
|
|
}
|
|
|
|
*q = '\0';
|
|
|
|
if (q - p + 1 > PATH_MAX) {
|
|
|
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
memcpy(r->target, p, q - p + 1);
|
|
|
|
decode(r->target, r->target);
|
|
|
|
|
|
|
|
/* basis for next step */
|
|
|
|
p = q + 1;
|
|
|
|
|
|
|
|
/* HTTP-VERSION */
|
2016-09-02 07:59:02 +00:00
|
|
|
if (strncmp(p, "HTTP/", sizeof("HTTP/") - 1)) {
|
2017-06-20 19:40:00 +00:00
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
p += sizeof("HTTP/") - 1;
|
|
|
|
if (strncmp(p, "1.0", sizeof("1.0") - 1) &&
|
|
|
|
strncmp(p, "1.1", sizeof("1.1") - 1)) {
|
2017-06-20 19:40:00 +00:00
|
|
|
return sendstatus(fd, S_VERSION_NOT_SUPPORTED);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
p += sizeof("1.*") - 1;
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* check terminator */
|
2016-09-02 07:59:02 +00:00
|
|
|
if (strncmp(p, "\r\n", sizeof("\r\n") - 1)) {
|
2017-06-20 19:40:00 +00:00
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* basis for next step */
|
2016-09-02 07:59:02 +00:00
|
|
|
p += sizeof("\r\n") - 1;
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
/*
|
|
|
|
* parse request-fields
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* match field type */
|
|
|
|
for (; *p != '\0';) {
|
|
|
|
for (i = 0; i < NUM_REQ_FIELDS; i++) {
|
2017-06-27 20:19:27 +00:00
|
|
|
if (!strncasecmp(p, req_field_str[i],
|
2017-06-27 20:49:37 +00:00
|
|
|
strlen(req_field_str[i]))) {
|
2017-06-20 19:40:00 +00:00
|
|
|
break;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
|
|
|
if (i == NUM_REQ_FIELDS) {
|
|
|
|
/* unmatched field, skip this line */
|
|
|
|
if (!(q = strstr(p, "\r\n"))) {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
p = q + (sizeof("\r\n") - 1);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
p += strlen(req_field_str[i]);
|
|
|
|
|
|
|
|
/* a single colon must follow the field name */
|
|
|
|
if (*p != ':') {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* skip whitespace */
|
2017-06-27 20:19:27 +00:00
|
|
|
for (++p; *p == ' ' || *p == '\t'; p++)
|
2017-06-20 19:40:00 +00:00
|
|
|
;
|
|
|
|
|
|
|
|
/* extract field content */
|
|
|
|
if (!(q = strstr(p, "\r\n"))) {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
|
|
|
}
|
|
|
|
*q = '\0';
|
|
|
|
if (q - p + 1 > FIELD_MAX) {
|
|
|
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
|
|
|
}
|
|
|
|
memcpy(r->field[i], p, q - p + 1);
|
|
|
|
|
|
|
|
/* go to next line */
|
|
|
|
p = q + (sizeof("\r\n") - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-27 19:37:49 +00:00
|
|
|
static int
|
|
|
|
compareent(const struct dirent **d1, const struct dirent **d2)
|
|
|
|
{
|
|
|
|
int v;
|
|
|
|
|
|
|
|
v = ((*d2)->d_type == DT_DIR ? 1 : -1) -
|
|
|
|
((*d1)->d_type == DT_DIR ? 1 : -1);
|
2017-06-27 20:49:37 +00:00
|
|
|
if (v) {
|
2017-06-27 19:37:49 +00:00
|
|
|
return v;
|
2017-06-27 20:49:37 +00:00
|
|
|
}
|
|
|
|
|
2017-06-27 19:37:49 +00:00
|
|
|
return strcmp((*d1)->d_name, (*d2)->d_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
2017-06-28 06:40:34 +00:00
|
|
|
suffix(int t)
|
2017-06-27 19:37:49 +00:00
|
|
|
{
|
|
|
|
switch (t) {
|
|
|
|
case DT_FIFO: return "|";
|
|
|
|
case DT_DIR: return "/";
|
|
|
|
case DT_LNK: return "@";
|
|
|
|
case DT_SOCK: return "=";
|
|
|
|
}
|
2017-06-27 20:49:37 +00:00
|
|
|
|
2017-06-27 19:37:49 +00:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
static enum status
|
|
|
|
senddir(int fd, char *name, struct request *r)
|
|
|
|
{
|
|
|
|
struct dirent **e;
|
2017-06-21 05:41:52 +00:00
|
|
|
size_t i;
|
2017-06-27 20:24:43 +00:00
|
|
|
int dirlen, s;
|
2017-06-21 05:41:52 +00:00
|
|
|
static char t[TIMESTAMP_LEN];
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* read directory */
|
2017-06-27 19:37:49 +00:00
|
|
|
if ((dirlen = scandir(name, &e, NULL, compareent)) < 0) {
|
2017-06-20 19:40:00 +00:00
|
|
|
return sendstatus(fd, S_FORBIDDEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* send header as late as possible */
|
2017-06-21 05:41:52 +00:00
|
|
|
if (dprintf(fd,
|
|
|
|
"HTTP/1.1 %d %s\r\n"
|
|
|
|
"Date: %s\r\n"
|
|
|
|
"Connection: close\r\n"
|
|
|
|
"Content-Type: text/html\r\n"
|
|
|
|
"\r\n",
|
2017-07-04 19:47:28 +00:00
|
|
|
S_OK, status_str[S_OK], timestamp(time(NULL), t)) < 0) {
|
2017-06-27 20:24:43 +00:00
|
|
|
s = S_REQUEST_TIMEOUT;
|
|
|
|
goto cleanup;
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (r->method == M_GET) {
|
|
|
|
/* listing header */
|
2017-06-21 05:41:52 +00:00
|
|
|
if (dprintf(fd,
|
|
|
|
"<!DOCTYPE html>\n<html>\n\t<head>"
|
|
|
|
"<title>Index of %s</title></head>\n"
|
|
|
|
"\t<body>\n\t\t<a href=\"..\">..</a>",
|
|
|
|
name) < 0) {
|
2017-06-27 20:24:43 +00:00
|
|
|
s = S_REQUEST_TIMEOUT;
|
|
|
|
goto cleanup;
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* listing */
|
|
|
|
for (i = 0; i < dirlen; i++) {
|
|
|
|
/* skip hidden files, "." and ".." */
|
|
|
|
if (e[i]->d_name[0] == '.') {
|
|
|
|
continue;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* entry line */
|
2017-06-27 20:34:37 +00:00
|
|
|
if (dprintf(fd, "<br />\n\t\t<a href=\"%s%s\">%s%s</a>",
|
|
|
|
e[i]->d_name,
|
2017-06-28 06:29:31 +00:00
|
|
|
(e[i]->d_type == DT_DIR) ? "/" : "",
|
2017-06-27 20:34:37 +00:00
|
|
|
e[i]->d_name,
|
2017-06-28 06:40:34 +00:00
|
|
|
suffix(e[i]->d_type)) < 0) {
|
2017-06-27 20:24:43 +00:00
|
|
|
s = S_REQUEST_TIMEOUT;
|
|
|
|
goto cleanup;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* listing footer */
|
2017-06-27 19:36:52 +00:00
|
|
|
if (dprintf(fd, "\n\t</body>\n</html>\n") < 0) {
|
2017-06-27 20:24:43 +00:00
|
|
|
s = S_REQUEST_TIMEOUT;
|
|
|
|
goto cleanup;
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-27 20:24:43 +00:00
|
|
|
s = S_OK;
|
2016-09-02 07:59:02 +00:00
|
|
|
|
2017-06-27 20:24:43 +00:00
|
|
|
cleanup:
|
2017-06-27 20:49:37 +00:00
|
|
|
while (dirlen--) {
|
2017-06-27 20:24:43 +00:00
|
|
|
free(e[dirlen]);
|
2017-06-27 20:49:37 +00:00
|
|
|
}
|
2017-06-27 20:24:43 +00:00
|
|
|
free(e);
|
|
|
|
|
|
|
|
return s;
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static enum status
|
|
|
|
sendfile(int fd, char *name, struct request *r, struct stat *st, char *mime,
|
|
|
|
off_t lower, off_t upper)
|
|
|
|
{
|
|
|
|
FILE *fp;
|
|
|
|
enum status s;
|
|
|
|
ssize_t bread, bwritten;
|
|
|
|
off_t remaining;
|
|
|
|
int range;
|
2017-06-21 05:41:52 +00:00
|
|
|
static char buf[BUFSIZ], *p, t1[TIMESTAMP_LEN], t2[TIMESTAMP_LEN];
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* open file */
|
|
|
|
if (!(fp = fopen(name, "r"))) {
|
2017-06-27 19:39:24 +00:00
|
|
|
s = sendstatus(fd, S_FORBIDDEN);
|
|
|
|
goto cleanup;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
/* seek to lower bound */
|
|
|
|
if (fseek(fp, lower, SEEK_SET)) {
|
2017-06-27 19:39:24 +00:00
|
|
|
s = sendstatus(fd, S_INTERNAL_SERVER_ERROR);
|
|
|
|
goto cleanup;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* send header as late as possible */
|
|
|
|
range = r->field[REQ_RANGE][0];
|
|
|
|
s = range ? S_PARTIAL_CONTENT : S_OK;
|
|
|
|
|
2017-06-21 05:41:52 +00:00
|
|
|
if (dprintf(fd,
|
|
|
|
"HTTP/1.1 %d %s\r\n"
|
|
|
|
"Date: %s\r\n"
|
|
|
|
"Connection: close\r\n"
|
2017-07-11 11:03:24 +00:00
|
|
|
"Last-Modified: %s\r\n"
|
|
|
|
"Content-Type: %s\r\n"
|
|
|
|
"Content-Length: %zu\r\n",
|
2017-07-04 19:47:28 +00:00
|
|
|
s, status_str[s], timestamp(time(NULL), t1),
|
2017-07-11 11:03:24 +00:00
|
|
|
timestamp(st->st_mtim.tv_sec, t2), mime,
|
2017-07-11 10:33:21 +00:00
|
|
|
upper - lower + (st->st_size > 0)) < 0) {
|
2017-06-27 19:39:24 +00:00
|
|
|
s = S_REQUEST_TIMEOUT;
|
|
|
|
goto cleanup;
|
2017-06-21 05:41:52 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
if (range) {
|
2017-06-21 05:41:52 +00:00
|
|
|
if (dprintf(fd, "Content-Range: bytes %zu-%zu/%zu\r\n",
|
2017-07-04 19:27:20 +00:00
|
|
|
lower, upper, st->st_size) < 0) {
|
2017-06-27 19:39:24 +00:00
|
|
|
s = S_REQUEST_TIMEOUT;
|
|
|
|
goto cleanup;
|
2017-06-21 05:41:52 +00:00
|
|
|
}
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-21 05:41:52 +00:00
|
|
|
if (dprintf(fd, "\r\n") < 0) {
|
2017-06-27 19:39:24 +00:00
|
|
|
s = S_REQUEST_TIMEOUT;
|
|
|
|
goto cleanup;
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (r->method == M_GET) {
|
|
|
|
/* write data until upper bound is hit */
|
|
|
|
remaining = upper - lower + 1;
|
|
|
|
|
|
|
|
while ((bread = fread(buf, 1, MIN(sizeof(buf), remaining), fp))) {
|
|
|
|
if (bread < 0) {
|
|
|
|
return S_INTERNAL_SERVER_ERROR;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
remaining -= bread;
|
|
|
|
p = buf;
|
|
|
|
while (bread > 0) {
|
|
|
|
bwritten = write(fd, p, bread);
|
|
|
|
if (bwritten <= 0) {
|
|
|
|
return S_REQUEST_TIMEOUT;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
bread -= bwritten;
|
|
|
|
p += bwritten;
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-27 19:39:24 +00:00
|
|
|
cleanup:
|
2017-06-27 20:49:37 +00:00
|
|
|
if (fp) {
|
2017-06-27 19:39:24 +00:00
|
|
|
fclose(fp);
|
2017-06-27 20:49:37 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
static int
|
|
|
|
normabspath(char *path)
|
|
|
|
{
|
|
|
|
size_t len;
|
2017-06-21 09:02:35 +00:00
|
|
|
int last = 0;
|
|
|
|
char *p, *q;
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
|
2017-06-21 08:37:39 +00:00
|
|
|
/* require and skip first slash */
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
if (path[0] != '/') {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
p = path + 1;
|
|
|
|
|
|
|
|
/* get length of path */
|
|
|
|
len = strlen(p);
|
|
|
|
|
2017-06-21 09:02:35 +00:00
|
|
|
for (; !last; ) {
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
/* bound path component within (p,q) */
|
|
|
|
if (!(q = strchr(p, '/'))) {
|
|
|
|
q = strchr(p, '\0');
|
2017-06-21 09:02:35 +00:00
|
|
|
last = 1;
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (p == q || (q - p == 1 && p[0] == '.')) {
|
|
|
|
/* "/" or "./" */
|
2017-06-21 09:02:35 +00:00
|
|
|
goto squash;
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
} else if (q - p == 2 && p[0] == '.' && p[1] == '.') {
|
|
|
|
/* "../" */
|
2017-06-21 09:02:35 +00:00
|
|
|
if (p != path + 1) {
|
|
|
|
/* place p right after the previous / */
|
|
|
|
for (p -= 2; p > path && *p != '/'; p--);
|
|
|
|
p++;
|
|
|
|
}
|
|
|
|
goto squash;
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
} else {
|
2017-06-21 09:02:35 +00:00
|
|
|
/* move on */
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
p = q + 1;
|
2017-06-21 09:02:35 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
squash:
|
|
|
|
/* squash (p,q) into void */
|
|
|
|
if (last) {
|
|
|
|
*p = '\0';
|
|
|
|
len = p - path;
|
|
|
|
} else {
|
2017-07-05 17:06:04 +00:00
|
|
|
memmove(p, q + 1, len - ((q + 1) - path) + 2);
|
2017-06-21 09:02:35 +00:00
|
|
|
len -= (q + 1) - p;
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
static enum status
|
|
|
|
sendresponse(int fd, struct request *r)
|
|
|
|
{
|
2017-07-07 11:33:43 +00:00
|
|
|
struct in6_addr res;
|
2017-06-20 19:40:00 +00:00
|
|
|
struct stat st;
|
|
|
|
struct tm tm;
|
|
|
|
size_t len, i;
|
|
|
|
off_t lower, upper;
|
2017-07-07 11:33:43 +00:00
|
|
|
int hasport, ipv6host;
|
2017-06-21 05:41:52 +00:00
|
|
|
static char realtarget[PATH_MAX], tmptarget[PATH_MAX], t[TIMESTAMP_LEN];
|
2017-06-20 19:40:00 +00:00
|
|
|
char *p, *q, *mime;
|
2017-07-04 20:21:36 +00:00
|
|
|
const char *err;
|
2017-06-20 19:40:00 +00:00
|
|
|
|
2017-07-11 10:34:55 +00:00
|
|
|
/* match vhost */
|
|
|
|
if (vhosts) {
|
|
|
|
for (i = 0; i < LEN(vhost); i++) {
|
2017-07-11 11:36:40 +00:00
|
|
|
if (!regexec(&vhost[i].re, r->field[REQ_HOST], 0,
|
|
|
|
NULL, 0) &&
|
|
|
|
/* switch to vhost directory */
|
|
|
|
chdir(vhost[i].dir) < 0) {
|
2017-07-11 10:34:55 +00:00
|
|
|
return sendstatus(fd, (errno == EACCES) ?
|
|
|
|
S_FORBIDDEN : S_NOT_FOUND);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
/* normalize target */
|
Add normabspath() to replace realpath(), making quark work with musl
Compiled against musl, quark will not work as musl needs the presence
of procfs to process paths in realpath().
We could wait for it to be implemented[0] or also notice that we don't
want to overengineer the target-resolving. I don't think it's very
suckless if we deploy such a huge infrastructure to resolve paths.
To counteract this and given there are no good solutions available, I
set out to write the function normabspath(), which normalizes an
absolute path.
It is idempotent and works on the buffer passed to it. We don't need a
target, as the resulting resolved path is guaranteed to be of equal
length or shorter. This requires a memcpy in our case before calling it,
but I see it as a nice demonstration of the possibilities and it might
prove to be useful for other projects.
Not requiring a target buffer (that needs to have its length specified),
the one-string-call also simplifies the calling semantics drasticly.
With this function in place, quark works with musl. Statically linked,
stripped and with -Os, it only weighs 102K.
[0]: http://www.openwall.com/lists/musl/2016/11/03/5
2017-06-21 08:27:17 +00:00
|
|
|
memcpy(realtarget, r->target, sizeof(realtarget));
|
|
|
|
if (normabspath(realtarget)) {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* reject hidden target */
|
|
|
|
if (realtarget[0] == '.' || strstr(realtarget, "/.")) {
|
|
|
|
return sendstatus(fd, S_FORBIDDEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* stat the target */
|
2017-07-11 10:34:55 +00:00
|
|
|
if (stat(RELPATH(realtarget), &st) < 0) {
|
2017-06-20 19:40:00 +00:00
|
|
|
return sendstatus(fd, (errno == EACCES) ? S_FORBIDDEN : S_NOT_FOUND);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
/* add / to target if not present */
|
|
|
|
len = strlen(realtarget);
|
|
|
|
if (len == PATH_MAX - 2) {
|
|
|
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
|
|
|
}
|
|
|
|
if (len && realtarget[len - 1] != '/') {
|
|
|
|
realtarget[len] = '/';
|
|
|
|
realtarget[len + 1] = '\0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-11 10:34:55 +00:00
|
|
|
/* redirect if targets differ or host is non-canonical */
|
|
|
|
if (strcmp(r->target, realtarget) || (vhosts && r->field[REQ_HOST][0] &&
|
|
|
|
strcmp(r->field[REQ_HOST], vhost[i].name))) {
|
2017-07-07 11:33:43 +00:00
|
|
|
/* do we need to add a port to the Location? */
|
|
|
|
hasport = strcmp(port, "80");
|
|
|
|
|
|
|
|
/* RFC 2732 specifies to use brackets for IPv6-addresses in
|
|
|
|
* URLs, so we need to check if our host is one and honor that
|
|
|
|
* later when we fill the "Location"-field */
|
2017-07-08 22:11:30 +00:00
|
|
|
if ((ipv6host = inet_pton(AF_INET6, r->field[REQ_HOST][0] ?
|
|
|
|
r->field[REQ_HOST] : host, &res)) < 0) {
|
|
|
|
return sendstatus(fd, S_INTERNAL_SERVER_ERROR);
|
|
|
|
}
|
2017-07-07 11:33:43 +00:00
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
/* encode realtarget */
|
|
|
|
encode(realtarget, tmptarget);
|
|
|
|
|
2017-07-07 11:33:43 +00:00
|
|
|
/* send redirection header */
|
2017-06-21 05:41:52 +00:00
|
|
|
if (dprintf(fd,
|
|
|
|
"HTTP/1.1 %d %s\r\n"
|
|
|
|
"Date: %s\r\n"
|
|
|
|
"Connection: close\r\n"
|
2017-07-07 11:33:43 +00:00
|
|
|
"Location: http://%s%s%s%s%s%s\r\n"
|
2017-06-21 05:41:52 +00:00
|
|
|
"\r\n",
|
|
|
|
S_MOVED_PERMANENTLY,
|
2017-07-04 19:47:28 +00:00
|
|
|
status_str[S_MOVED_PERMANENTLY],
|
2017-07-07 11:33:43 +00:00
|
|
|
timestamp(time(NULL), t), ipv6host ? "[" : "",
|
2017-07-11 10:34:55 +00:00
|
|
|
r->field[REQ_HOST][0] ? (vhosts && i < LEN(vhost)) ?
|
|
|
|
vhost[i].name : r->field[REQ_HOST] : host,
|
2017-07-07 11:33:43 +00:00
|
|
|
ipv6host ? "]" : "", hasport ? ":" : "",
|
|
|
|
hasport ? port : "", tmptarget) < 0) {
|
2017-06-21 05:41:52 +00:00
|
|
|
return S_REQUEST_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
return S_MOVED_PERMANENTLY;
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
|
|
/* append docindex to target */
|
|
|
|
if (snprintf(realtarget, sizeof(realtarget), "%s%s",
|
|
|
|
r->target, docindex) >= sizeof(realtarget)) {
|
|
|
|
return sendstatus(fd, S_REQUEST_TOO_LARGE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* stat the docindex, which must be a regular file */
|
2017-07-11 10:34:55 +00:00
|
|
|
if (stat(RELPATH(realtarget), &st) < 0 || !S_ISREG(st.st_mode)) {
|
2017-06-20 19:40:00 +00:00
|
|
|
if (listdirs) {
|
|
|
|
/* remove index suffix and serve dir */
|
|
|
|
realtarget[strlen(realtarget) -
|
|
|
|
strlen(docindex)] = '\0';
|
2017-07-11 10:34:55 +00:00
|
|
|
return senddir(fd, RELPATH(realtarget), r);
|
2017-06-20 19:40:00 +00:00
|
|
|
} else {
|
|
|
|
/* reject */
|
|
|
|
if (!S_ISREG(st.st_mode) || errno == EACCES) {
|
|
|
|
return sendstatus(fd, S_FORBIDDEN);
|
|
|
|
} else {
|
|
|
|
return sendstatus(fd, S_NOT_FOUND);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* modified since */
|
|
|
|
if (r->field[REQ_MOD][0]) {
|
|
|
|
/* parse field */
|
|
|
|
if (!strptime(r->field[REQ_MOD], "%a, %d %b %Y %T GMT", &tm)) {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* compare with last modification date of the file */
|
|
|
|
if (difftime(st.st_mtim.tv_sec, mktime(&tm)) <= 0) {
|
2017-06-21 05:41:52 +00:00
|
|
|
if (dprintf(fd,
|
|
|
|
"HTTP/1.1 %d %s\r\n"
|
|
|
|
"Date: %s\r\n"
|
|
|
|
"Connection: close\r\n"
|
|
|
|
"\r\n",
|
|
|
|
S_NOT_MODIFIED, status_str[S_NOT_MODIFIED],
|
2017-07-04 19:47:28 +00:00
|
|
|
timestamp(time(NULL), t)) < 0) {
|
2017-06-20 19:40:00 +00:00
|
|
|
return S_REQUEST_TIMEOUT;
|
|
|
|
}
|
|
|
|
}
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* range */
|
|
|
|
lower = 0;
|
2017-06-20 19:40:00 +00:00
|
|
|
upper = st.st_size;
|
|
|
|
|
|
|
|
if (r->field[REQ_RANGE][0]) {
|
|
|
|
/* parse field */
|
|
|
|
p = r->field[REQ_RANGE];
|
|
|
|
|
|
|
|
if (strncmp(p, "bytes=", sizeof("bytes=") - 1)) {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
p += sizeof("bytes=") - 1;
|
|
|
|
|
2016-09-02 07:59:02 +00:00
|
|
|
if (!(q = strchr(p, '-'))) {
|
2017-06-20 19:40:00 +00:00
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
*(q++) = '\0';
|
|
|
|
if (p[0]) {
|
2017-07-04 20:21:36 +00:00
|
|
|
lower = strtonum(p, 0, LLONG_MAX, &err);
|
|
|
|
}
|
|
|
|
if (!err && q[0]) {
|
|
|
|
upper = strtonum(q, 0, LLONG_MAX, &err);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-07-04 20:21:36 +00:00
|
|
|
if (err) {
|
|
|
|
return sendstatus(fd, S_BAD_REQUEST);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2017-07-04 19:27:20 +00:00
|
|
|
/* check range */
|
|
|
|
if (lower < 0 || upper < 0 || lower > upper ||
|
|
|
|
upper >= st.st_size) {
|
|
|
|
if (dprintf(fd,
|
|
|
|
"HTTP/1.1 %d %s\r\n"
|
|
|
|
"Date: %s\r\n"
|
|
|
|
"Content-Range: bytes */%zu\r\n"
|
|
|
|
"Connection: close\r\n"
|
|
|
|
"\r\n",
|
|
|
|
S_RANGE_NOT_SATISFIABLE,
|
|
|
|
status_str[S_RANGE_NOT_SATISFIABLE],
|
2017-07-04 19:47:28 +00:00
|
|
|
timestamp(time(NULL), t),
|
2017-07-04 19:27:20 +00:00
|
|
|
st.st_size) < 0) {
|
|
|
|
return S_REQUEST_TIMEOUT;
|
|
|
|
}
|
|
|
|
return S_RANGE_NOT_SATISFIABLE;
|
2017-06-20 19:40:00 +00:00
|
|
|
}
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
2017-06-20 19:40:00 +00:00
|
|
|
|
|
|
|
/* mime */
|
2017-06-27 19:40:00 +00:00
|
|
|
mime = "application/octet-stream";
|
2017-06-20 19:40:00 +00:00
|
|
|
if ((p = strrchr(realtarget, '.'))) {
|
|
|
|
for (i = 0; i < sizeof(mimes) / sizeof(*mimes); i++) {
|
2016-09-02 07:59:02 +00:00
|
|
|
if (!strcmp(mimes[i].ext, p + 1)) {
|
|
|
|
mime = mimes[i].type;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-11 10:34:55 +00:00
|
|
|
return sendfile(fd, RELPATH(realtarget), r, &st, mime, lower, upper);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
serve(int insock)
|
|
|
|
{
|
2017-06-20 19:40:00 +00:00
|
|
|
struct request r;
|
2016-09-02 07:59:02 +00:00
|
|
|
struct sockaddr_storage in_sa;
|
|
|
|
struct timeval tv;
|
|
|
|
pid_t p;
|
|
|
|
socklen_t in_sa_len;
|
|
|
|
time_t t;
|
2017-06-20 19:40:00 +00:00
|
|
|
enum status status;
|
2016-09-04 21:58:20 +00:00
|
|
|
int infd;
|
2017-06-20 19:40:00 +00:00
|
|
|
char inip4[INET_ADDRSTRLEN], inip6[INET6_ADDRSTRLEN], tstmp[25];
|
2016-09-02 07:59:02 +00:00
|
|
|
|
|
|
|
while (1) {
|
2016-09-04 21:58:20 +00:00
|
|
|
/* accept incoming connections */
|
2016-09-02 07:59:02 +00:00
|
|
|
in_sa_len = sizeof(in_sa);
|
|
|
|
if ((infd = accept(insock, (struct sockaddr *)&in_sa,
|
|
|
|
&in_sa_len)) < 0) {
|
|
|
|
fprintf(stderr, "%s: accept: %s\n", argv0,
|
|
|
|
strerror(errno));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
/* fork and handle */
|
2016-09-02 07:59:02 +00:00
|
|
|
switch ((p = fork())) {
|
|
|
|
case -1:
|
2017-06-20 19:40:00 +00:00
|
|
|
fprintf(stderr, "%s: fork: %s\n", argv0,
|
2016-09-02 07:59:02 +00:00
|
|
|
strerror(errno));
|
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
close(insock);
|
|
|
|
|
2016-09-04 21:58:20 +00:00
|
|
|
/* set connection timeout */
|
2016-09-02 07:59:02 +00:00
|
|
|
tv.tv_sec = 30;
|
|
|
|
tv.tv_usec = 0;
|
|
|
|
if (setsockopt(infd, SOL_SOCKET, SO_RCVTIMEO, &tv,
|
|
|
|
sizeof(tv)) < 0 ||
|
|
|
|
setsockopt(infd, SOL_SOCKET, SO_SNDTIMEO, &tv,
|
|
|
|
sizeof(tv)) < 0) {
|
|
|
|
fprintf(stderr, "%s: setsockopt: %s\n",
|
|
|
|
argv0, strerror(errno));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-06-20 19:40:00 +00:00
|
|
|
/* handle request */
|
|
|
|
if (!(status = getrequest(infd, &r))) {
|
|
|
|
status = sendresponse(infd, &r);
|
|
|
|
}
|
2016-09-02 07:59:02 +00:00
|
|
|
|
2016-09-04 21:58:20 +00:00
|
|
|
/* write output to log */
|
2016-09-02 07:59:02 +00:00
|
|
|
t = time(NULL);
|
|
|
|
strftime(tstmp, sizeof(tstmp), "%Y-%m-%dT%H:%M:%S",
|
|
|
|
gmtime(&t));
|
|
|
|
|
|
|
|
if (in_sa.ss_family == AF_INET) {
|
|
|
|
inet_ntop(AF_INET,
|
|
|
|
&(((struct sockaddr_in *)&in_sa)->sin_addr),
|
|
|
|
inip4, sizeof(inip4));
|
2017-07-11 10:34:55 +00:00
|
|
|
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inip4,
|
|
|
|
status, r.field[REQ_HOST], r.target);
|
2016-09-02 07:59:02 +00:00
|
|
|
} else {
|
|
|
|
inet_ntop(AF_INET6,
|
|
|
|
&(((struct sockaddr_in6*)&in_sa)->sin6_addr),
|
|
|
|
inip6, sizeof(inip6));
|
2017-07-11 10:34:55 +00:00
|
|
|
printf("%s\t%s\t%d\t%s\t%s\n", tstmp, inip6,
|
|
|
|
status, r.field[REQ_HOST], r.target);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
2016-09-04 21:58:20 +00:00
|
|
|
/* clean up and finish */
|
2016-09-02 07:59:02 +00:00
|
|
|
shutdown(infd, SHUT_RD);
|
|
|
|
shutdown(infd, SHUT_WR);
|
|
|
|
close(infd);
|
2016-09-04 21:58:20 +00:00
|
|
|
_exit(0);
|
2016-09-02 07:59:02 +00:00
|
|
|
default:
|
2017-06-20 19:40:00 +00:00
|
|
|
/* close the connection in the parent */
|
2016-09-02 07:59:02 +00:00
|
|
|
close(infd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
die(const char *errstr, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
va_start(ap, errstr);
|
|
|
|
vfprintf(stderr, errstr, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
getipsock(void)
|
|
|
|
{
|
|
|
|
struct addrinfo hints, *ai, *p;
|
|
|
|
int ret, insock = 0, yes;
|
|
|
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
hints.ai_family = AF_UNSPEC;
|
|
|
|
hints.ai_socktype = SOCK_STREAM;
|
|
|
|
|
|
|
|
if ((ret = getaddrinfo(host, port, &hints, &ai))) {
|
|
|
|
die("%s: getaddrinfo: %s\n", argv0, gai_strerror(ret));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (yes = 1, p = ai; p; p = p->ai_next) {
|
|
|
|
if ((insock = socket(p->ai_family, p->ai_socktype,
|
|
|
|
p->ai_protocol)) < 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (setsockopt(insock, SOL_SOCKET, SO_REUSEADDR, &yes,
|
|
|
|
sizeof(int)) < 0) {
|
|
|
|
die("%s: setsockopt: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
if (bind(insock, p->ai_addr, p->ai_addrlen) < 0) {
|
|
|
|
close(insock);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
freeaddrinfo(ai);
|
|
|
|
if (!p) {
|
|
|
|
die("%s: failed to bind\n", argv0);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (listen(insock, SOMAXCONN) < 0) {
|
|
|
|
die("%s: listen: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
return insock;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
getusock(char *udsname)
|
|
|
|
{
|
|
|
|
struct sockaddr_un addr;
|
2017-07-04 20:44:47 +00:00
|
|
|
size_t udsnamelen;
|
2016-09-02 07:59:02 +00:00
|
|
|
int insock;
|
|
|
|
|
|
|
|
if ((insock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
|
|
|
|
die("%s: socket: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
|
|
addr.sun_family = AF_UNIX;
|
2017-07-04 20:44:47 +00:00
|
|
|
|
|
|
|
if ((udsnamelen = strlen(udsname)) > sizeof(addr.sun_path) - 1) {
|
|
|
|
die("%s: UNIX-domain socket name truncated\n", argv0);
|
|
|
|
}
|
|
|
|
memcpy(addr.sun_path, udsname, udsnamelen + 1);
|
2016-09-02 07:59:02 +00:00
|
|
|
|
|
|
|
unlink(udsname);
|
|
|
|
|
|
|
|
if (bind(insock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
|
|
|
die("%s: bind: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (listen(insock, SOMAXCONN) < 0) {
|
|
|
|
die("%s: listen: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
return insock;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
usage(void)
|
|
|
|
{
|
2016-09-04 21:09:02 +00:00
|
|
|
die("usage: %s [-v] [[[-h host] [-p port]] | [-U udsocket]] "
|
2017-06-27 21:17:44 +00:00
|
|
|
"[-d dir] [-l] [-L] [-u user] [-g group]\n", argv0);
|
2016-09-02 07:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
main(int argc, char *argv[])
|
|
|
|
{
|
|
|
|
struct passwd *pwd = NULL;
|
|
|
|
struct group *grp = NULL;
|
|
|
|
struct rlimit rlim;
|
2017-07-11 10:34:55 +00:00
|
|
|
int i, insock;
|
2016-09-02 07:59:02 +00:00
|
|
|
char *udsname = NULL;
|
|
|
|
|
|
|
|
ARGBEGIN {
|
|
|
|
case 'd':
|
|
|
|
servedir = EARGF(usage());
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
|
|
group = EARGF(usage());
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
|
|
host = EARGF(usage());
|
|
|
|
break;
|
2017-06-27 21:17:44 +00:00
|
|
|
case 'l':
|
|
|
|
listdirs = 0;
|
|
|
|
break;
|
|
|
|
case 'L':
|
|
|
|
listdirs = 1;
|
|
|
|
break;
|
2016-09-02 07:59:02 +00:00
|
|
|
case 'p':
|
|
|
|
port = EARGF(usage());
|
|
|
|
break;
|
|
|
|
case 'u':
|
|
|
|
user = EARGF(usage());
|
|
|
|
break;
|
|
|
|
case 'U':
|
|
|
|
udsname = EARGF(usage());
|
|
|
|
break;
|
|
|
|
case 'v':
|
|
|
|
fputs("quark-"VERSION"\n", stderr);
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
usage();
|
|
|
|
} ARGEND
|
|
|
|
|
2017-07-04 20:26:17 +00:00
|
|
|
if (argc) {
|
2016-09-02 07:59:02 +00:00
|
|
|
usage();
|
2017-07-04 20:26:17 +00:00
|
|
|
}
|
2016-09-02 07:59:02 +00:00
|
|
|
|
2017-07-11 10:34:55 +00:00
|
|
|
/* compile and check the supplied vhost regexes */
|
|
|
|
if (vhosts) {
|
|
|
|
for (i = 0; i < LEN(vhost); i++) {
|
2017-07-11 11:36:40 +00:00
|
|
|
if (regcomp(&vhost[i].re, vhost[i].regex,
|
2017-07-11 10:34:55 +00:00
|
|
|
REG_ICASE | REG_NOSUB)) {
|
|
|
|
die("%s: regcomp '%s': invalid regex\n", argv0,
|
|
|
|
vhost[i].regex);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-02 07:59:02 +00:00
|
|
|
/* reap children automatically */
|
|
|
|
if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
|
|
|
|
fprintf(stderr, "%s: signal: Failed to set SIG_IGN on"
|
|
|
|
"SIGCHLD\n", argv0);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* raise the process limit */
|
|
|
|
rlim.rlim_cur = rlim.rlim_max = maxnprocs;
|
|
|
|
if (setrlimit(RLIMIT_NPROC, &rlim) < 0) {
|
2016-09-04 18:16:51 +00:00
|
|
|
fprintf(stderr, "%s: setrlimit RLIMIT_NPROC: %s\n", argv0,
|
2016-09-02 07:59:02 +00:00
|
|
|
strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* validate user and group */
|
|
|
|
errno = 0;
|
|
|
|
if (user && !(pwd = getpwnam(user))) {
|
|
|
|
die("%s: invalid user %s\n", argv0, user);
|
|
|
|
}
|
|
|
|
errno = 0;
|
|
|
|
if (group && !(grp = getgrnam(group))) {
|
|
|
|
die("%s: invalid group %s\n", argv0, group);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* bind socket */
|
|
|
|
insock = udsname ? getusock(udsname) : getipsock();
|
|
|
|
|
|
|
|
/* chroot */
|
|
|
|
if (chdir(servedir) < 0) {
|
|
|
|
die("%s: chdir %s: %s\n", argv0, servedir, strerror(errno));
|
|
|
|
}
|
|
|
|
if (chroot(".") < 0) {
|
|
|
|
die("%s: chroot .: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* drop root */
|
|
|
|
if (grp && setgroups(1, &(grp->gr_gid)) < 0) {
|
|
|
|
die("%s: setgroups: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
if (grp && setgid(grp->gr_gid) < 0) {
|
|
|
|
die("%s: setgid: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
if (pwd && setuid(pwd->pw_uid) < 0) {
|
|
|
|
die("%s: setuid: %s\n", argv0, strerror(errno));
|
|
|
|
}
|
|
|
|
if (getuid() == 0) {
|
|
|
|
die("%s: won't run as root user\n", argv0);
|
|
|
|
}
|
|
|
|
if (getgid() == 0) {
|
|
|
|
die("%s: won't run as root group\n", argv0);
|
|
|
|
}
|
|
|
|
|
|
|
|
serve(insock);
|
|
|
|
close(insock);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-07-04 20:21:36 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2004 Ted Unangst and Todd Miller
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
#define INVALID 1
|
|
|
|
#define TOOSMALL 2
|
|
|
|
#define TOOLARGE 3
|
|
|
|
|
|
|
|
long long
|
|
|
|
strtonum(const char *numstr, long long minval, long long maxval,
|
|
|
|
const char **errstrp)
|
|
|
|
{
|
|
|
|
long long ll = 0;
|
|
|
|
int error = 0;
|
|
|
|
char *ep;
|
|
|
|
struct errval {
|
|
|
|
const char *errstr;
|
|
|
|
int err;
|
|
|
|
} ev[4] = {
|
|
|
|
{ NULL, 0 },
|
|
|
|
{ "invalid", EINVAL },
|
|
|
|
{ "too small", ERANGE },
|
|
|
|
{ "too large", ERANGE },
|
|
|
|
};
|
|
|
|
|
|
|
|
ev[0].err = errno;
|
|
|
|
errno = 0;
|
|
|
|
if (minval > maxval) {
|
|
|
|
error = INVALID;
|
|
|
|
} else {
|
|
|
|
ll = strtoll(numstr, &ep, 10);
|
|
|
|
if (numstr == ep || *ep != '\0')
|
|
|
|
error = INVALID;
|
|
|
|
else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval)
|
|
|
|
error = TOOSMALL;
|
|
|
|
else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval)
|
|
|
|
error = TOOLARGE;
|
|
|
|
}
|
|
|
|
if (errstrp != NULL)
|
|
|
|
*errstrp = ev[error].errstr;
|
|
|
|
errno = ev[error].err;
|
|
|
|
if (error)
|
|
|
|
ll = 0;
|
|
|
|
|
|
|
|
return (ll);
|
|
|
|
}
|