diff
Makefile | 10 +
filebin.c | 1329 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 1339 insertions(+)
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0648d1b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+CC = cc
+CFLAGS = -std=c89 -Wall -Wextra -O2
+
+all: filebin
+
+filebin: filebin.c
+ $(CC) $(CFLAGS) filebin.c -o filebin
+
+clean:
+ rm -f filebin
diff --git a/filebin.c b/filebin.c
new file mode 100644
index 0000000..f0a45d3
--- /dev/null
+++ b/filebin.c
@@ -0,0 +1,1329 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <limits.h>
+#include <poll.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#define PORT 8080
+#define MAX_REQ (16 * 1024 * 1024)
+#define ENABLE_INDEX 0
+#define DEFAULT_TTL_SEC (3 * 24 * 60 * 60)
+#define MAX_TTL_SEC (3 * 24 * 60 * 60)
+#define CLEANUP_INTERVAL_SEC 60
+#define CLIENT_IDLE_TIMEOUT_SEC 30
+#define INITIAL_REQ_CAP 8192
+#define MAX_CLIENTS 128
+#define STORAGE_CAP_GB 100
+
+#if defined(__OpenBSD__) || defined(__linux__) || defined(__FreeBSD__) || \
+ defined(__NetBSD__) || defined(__DragonFly__)
+#define HAVE_CHROOT 1
+#endif
+
+#ifdef HAVE_CHROOT
+#define OPTSTR "dc:p:u:"
+#else
+#define OPTSTR "dp:u:"
+#endif
+
+#ifdef HAVE_CHROOT
+int chroot(const char *);
+#endif
+
+struct client {
+ char *req;
+ size_t off;
+ size_t cap;
+ time_t last_active;
+ int fd;
+};
+
+static const char *html_index =
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "Connection: close\r\n\r\n"
+ "<!doctype html><meta charset=utf-8><title>filebin</title>"
+ "<h1>filebin</h1><div>"
+ "<form method=post action=/upload enctype=multipart/form-data>"
+ "<input type=file name=file required> "
+ "<select name=ttl>"
+ "<option value='1m'>1m</option>"
+ "<option value='10m'>10m</option>"
+ "<option value='30m'>30m</option>"
+ "<option value='1h' selected>1h</option>"
+ "<option value='3h'>3h</option>"
+ "<option value='6h'>6h</option>"
+ "<option value='1d'>1d</option>"
+ "<option value='3d'>3d</option>"
+ "</select> "
+ "<button>Upload</button></form></div>";
+
+static int has_suffix(const char *, const char *);
+static int grow_reqbuf(struct client *);
+static int hdr_get(const char *, const char *, char *, size_t);
+static int eqi_n(const char *, const char *, size_t);
+static int isimg_ext(const char *);
+static int req_done(const char *, size_t);
+static int mk_fdir(void);
+static int parse_uint(const char *, unsigned int *);
+static int parse_ttl(const char *, long *);
+static int parse_ug(const char *, uid_t *, gid_t *);
+static int meta_read(const char *, time_t *);
+static int set_nb(int, int);
+
+static off_t cap_bytes(void);
+static off_t used_bytes(void);
+
+static char *find_bytes(const char *, size_t, const char *, size_t);
+
+static long parse_clen(const char *, const char *);
+
+static void cleanup_expired(void);
+static void client_close(struct client *);
+static void serve_req(int, char *, size_t);
+static void die(const char *);
+static void diex(const char *, const char *);
+static void serve_file(int, const char *);
+static void serve_list(int);
+static void serve_upload(int, char *, size_t);
+static void warnp(const char *);
+static void do_chroot(const char *);
+static void daemonize_self(void);
+static void mk_metapath(const char *, char *, size_t);
+static void obsd_sandbox(void);
+static void clean_ext(const char *, char *, size_t);
+static void sendall(int, const void *, size_t);
+static void sendtxt(int, const char *, const char *);
+static void usage(void);
+static void drop_privs(int, uid_t, gid_t);
+static void meta_write(const char *, time_t);
+
+static const char *ext_mime(const char *);
+
+static void
+sendall(int fd, const void *buf, size_t len)
+{
+ const char *p;
+ struct pollfd pfd;
+ ssize_t n;
+ int pr;
+
+ p = buf;
+ while (len > 0) {
+ n = send(fd, p, len, 0);
+ if (n > 0) {
+ p += n;
+ len -= (size_t)n;
+ continue;
+ }
+ if (n == -1 && errno == EINTR)
+ continue;
+ if (n == -1 && (errno == EAGAIN || errno == EWOULDBLOCK)) {
+ pfd.fd = fd;
+ pfd.events = POLLOUT;
+ pfd.revents = 0;
+ pr = poll(&pfd, 1, 1000);
+ if (pr > 0)
+ continue;
+ }
+ return;
+ }
+}
+
+static void
+sendtxt(int fd, const char *code, const char *body)
+{
+ char hdr[256];
+ int n;
+
+ n = snprintf(hdr, sizeof(hdr),
+ "HTTP/1.1 %s\r\n"
+ "Content-Type: text/plain; charset=utf-8\r\n"
+ "Content-Length: %zu\r\n"
+ "Connection: close\r\n\r\n",
+ code, strlen(body));
+ sendall(fd, hdr, (size_t)n);
+ sendall(fd, body, strlen(body));
+}
+
+static const char *
+ext_mime(const char *path)
+{
+ const char *dot;
+
+ dot = strrchr(path, '.');
+ if (dot == NULL)
+ return "application/octet-stream";
+ dot++;
+ if (strcmp(dot, "txt") == 0)
+ return "text/plain";
+ if (strcmp(dot, "html") == 0 || strcmp(dot, "htm") == 0)
+ return "text/html";
+ if (strcmp(dot, "jpg") == 0 || strcmp(dot, "jpeg") == 0)
+ return "image/jpeg";
+ if (strcmp(dot, "png") == 0)
+ return "image/png";
+ if (strcmp(dot, "gif") == 0)
+ return "image/gif";
+ if (strcmp(dot, "webp") == 0)
+ return "image/webp";
+ if (strcmp(dot, "svg") == 0)
+ return "image/svg+xml";
+ if (strcmp(dot, "bmp") == 0)
+ return "image/bmp";
+ if (strcmp(dot, "ico") == 0)
+ return "image/x-icon";
+ if (strcmp(dot, "avif") == 0)
+ return "image/avif";
+ if (strcmp(dot, "mp3") == 0)
+ return "audio/mpeg";
+ if (strcmp(dot, "mp4") == 0)
+ return "video/mp4";
+ if (strcmp(dot, "flac") == 0)
+ return "audio/flac";
+ if (strcmp(dot, "pdf") == 0)
+ return "application/pdf";
+ return "application/octet-stream";
+}
+
+static int
+isimg_ext(const char *ext)
+{
+ if (strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0)
+ return 1;
+ if (strcmp(ext, "png") == 0 || strcmp(ext, "gif") == 0)
+ return 1;
+ if (strcmp(ext, "webp") == 0 || strcmp(ext, "svg") == 0)
+ return 1;
+ if (strcmp(ext, "bmp") == 0 || strcmp(ext, "ico") == 0)
+ return 1;
+ if (strcmp(ext, "avif") == 0)
+ return 1;
+ return 0;
+}
+
+static void
+clean_ext(const char *name, char *ext, size_t extsz)
+{
+ const char *dot;
+ size_t i;
+
+ i = 0;
+ dot = strrchr(name, '.');
+ if (dot == NULL || dot[1] == '\0') {
+ ext[0] = '\0';
+ return;
+ }
+ dot++;
+ while (*dot != '\0' && i + 1 < extsz) {
+ if (isalnum((unsigned char)*dot) != 0)
+ ext[i++] = (char)tolower((unsigned char)*dot);
+ dot++;
+ }
+ ext[i] = '\0';
+}
+
+static int
+mk_fdir(void)
+{
+ struct stat st;
+
+ if (stat("f", &st) == 0 && S_ISDIR(st.st_mode) != 0)
+ return 0;
+ return mkdir("f", 0755);
+}
+
+static int
+has_suffix(const char *s, const char *suffix)
+{
+ size_t ls;
+ size_t lf;
+
+ ls = strlen(s);
+ lf = strlen(suffix);
+ if (lf > ls)
+ return 0;
+ return strcmp(s + ls - lf, suffix) == 0;
+}
+
+static void
+mk_metapath(const char *name, char *out, size_t outsz)
+{
+ snprintf(out, outsz, "f/%s.ttl", name);
+}
+
+static int
+parse_ttl(const char *val, long *out)
+{
+ char *end;
+ long n;
+ long mult;
+
+ while (*val == ' ' || *val == '\t')
+ val++;
+ if (isdigit((unsigned char)*val) == 0)
+ return 0;
+ n = strtol(val, &end, 10);
+ if (n <= 0)
+ return 0;
+ while (*end == ' ' || *end == '\t')
+ end++;
+ if (*end == 'm' || *end == 'M')
+ mult = 60;
+ else if (*end == 'h' || *end == 'H')
+ mult = 60 * 60;
+ else if (*end == 'd' || *end == 'D')
+ mult = 24 * 60 * 60;
+ else
+ return 0;
+ end++;
+ while (*end == ' ' || *end == '\t')
+ end++;
+ if (*end != '\0' || n > LONG_MAX / mult)
+ return 0;
+ *out = n * mult;
+ if (*out > MAX_TTL_SEC)
+ *out = MAX_TTL_SEC;
+ return 1;
+}
+
+static int
+parse_uint(const char *s, unsigned int *out)
+{
+ char *end;
+ unsigned long v;
+
+ errno = 0;
+ v = strtoul(s, &end, 10);
+ if (s[0] == '\0' || *end != '\0' || errno != 0 || v > UINT_MAX)
+ return 0;
+ *out = (unsigned int)v;
+ return 1;
+}
+
+static int
+parse_ug(const char *spec, uid_t *uid, gid_t *gid)
+{
+ char copy[256];
+ char *group;
+ char *user;
+ struct group *gr;
+ struct passwd *pw;
+ unsigned int id;
+ size_t n;
+
+ n = strlen(spec);
+ if (n >= sizeof(copy))
+ return 0;
+ memcpy(copy, spec, n + 1);
+ user = copy;
+ group = strchr(copy, ':');
+ if (group == NULL || group == user || group[1] == '\0')
+ return 0;
+ *group++ = '\0';
+
+ pw = getpwnam(user);
+ if (pw != NULL)
+ *uid = pw->pw_uid;
+ else {
+ if (parse_uint(user, &id) == 0)
+ return 0;
+ *uid = (uid_t)id;
+ }
+
+ gr = getgrnam(group);
+ if (gr != NULL)
+ *gid = gr->gr_gid;
+ else {
+ if (parse_uint(group, &id) == 0)
+ return 0;
+ *gid = (gid_t)id;
+ }
+
+ return 1;
+}
+
+static void
+meta_write(const char *name, time_t exp)
+{
+ char path[512];
+ FILE *fp;
+
+ mk_metapath(name, path, sizeof(path));
+ fp = fopen(path, "wb");
+ if (fp == NULL)
+ return;
+ fprintf(fp, "%ld\n", (long)exp);
+ fclose(fp);
+}
+
+static int
+meta_read(const char *name, time_t *exp)
+{
+ char path[512];
+ char buf[64];
+ FILE *fp;
+ long v;
+
+ mk_metapath(name, path, sizeof(path));
+ fp = fopen(path, "rb");
+ if (fp == NULL)
+ return 0;
+ if (fgets(buf, sizeof(buf), fp) == NULL) {
+ fclose(fp);
+ return 0;
+ }
+ fclose(fp);
+ v = atol(buf);
+ if (v <= 0)
+ return 0;
+ *exp = (time_t)v;
+ return 1;
+}
+
+static off_t
+cap_bytes(void)
+{
+ off_t cap;
+
+ cap = (off_t)STORAGE_CAP_GB;
+ cap *= 1024;
+ cap *= 1024;
+ cap *= 1024;
+ return cap;
+}
+
+static off_t
+used_bytes(void)
+{
+ char path[512];
+ struct dirent *e;
+ struct stat st;
+ DIR *d;
+ off_t total;
+
+ total = 0;
+ d = opendir("f");
+ if (d == NULL)
+ return 0;
+ while ((e = readdir(d)) != NULL) {
+ if (e->d_name[0] == '.' || has_suffix(e->d_name, ".ttl") != 0)
+ continue;
+ snprintf(path, sizeof(path), "f/%s", e->d_name);
+ if (stat(path, &st) != 0 || S_ISREG(st.st_mode) == 0)
+ continue;
+ if (st.st_size > 0)
+ total += st.st_size;
+ }
+ closedir(d);
+ return total;
+}
+
+static void
+cleanup_expired(void)
+{
+ char base[256];
+ char mpath[512];
+ char path[512];
+ struct dirent *e;
+ struct stat st;
+ time_t exp;
+ time_t now;
+ size_t n;
+ DIR *d;
+
+ d = opendir("f");
+ if (d == NULL)
+ return;
+ now = time(NULL);
+ while ((e = readdir(d)) != NULL) {
+ if (e->d_name[0] == '.')
+ continue;
+ snprintf(path, sizeof(path), "f/%s", e->d_name);
+ if (stat(path, &st) != 0 || S_ISREG(st.st_mode) == 0)
+ continue;
+ if (has_suffix(e->d_name, ".ttl") != 0) {
+ n = strlen(e->d_name);
+ if (n <= 4 || n - 4 >= sizeof(base))
+ continue;
+ snprintf(mpath, sizeof(mpath), "f/%s", e->d_name);
+ memcpy(base, e->d_name, n - 4);
+ base[n - 4] = '\0';
+ snprintf(path, sizeof(path), "f/%s", base);
+ if (stat(path, &st) != 0)
+ unlink(mpath);
+ continue;
+ }
+ if (meta_read(e->d_name, &exp) == 0)
+ exp = st.st_mtime + DEFAULT_TTL_SEC;
+ if (now < exp)
+ continue;
+ snprintf(path, sizeof(path), "f/%s", e->d_name);
+ unlink(path);
+ mk_metapath(e->d_name, mpath, sizeof(mpath));
+ unlink(mpath);
+ }
+ closedir(d);
+}
+
+static int
+eqi_n(const char *a, const char *b, size_t n)
+{
+ size_t i;
+
+ for (i = 0; i < n; i++) {
+ if (tolower((unsigned char)a[i]) != tolower((unsigned char)b[i]))
+ return 0;
+ }
+ return 1;
+}
+
+static long
+parse_clen(const char *hdr, const char *hdr_end)
+{
+ const char *line;
+ const char *line_end;
+ const char *v;
+
+ line = hdr;
+ while (line < hdr_end && *line != '\0') {
+ line_end = strstr(line, "\r\n");
+ if (line_end == NULL || line_end > hdr_end || line_end == line)
+ break;
+ if ((size_t)(line_end - line) > 15 &&
+ eqi_n(line, "Content-Length", 14) != 0 &&
+ line[14] == ':') {
+ v = line + 15;
+ while (v < line_end && (*v == ' ' || *v == '\t'))
+ v++;
+ return atol(v);
+ }
+ line = line_end + 2;
+ }
+ return -1;
+}
+
+static char *
+find_bytes(const char *hay, size_t hlen, const char *needle, size_t nlen)
+{
+ size_t i;
+
+ if (nlen == 0 || hlen < nlen)
+ return NULL;
+ for (i = 0; i + nlen <= hlen; i++) {
+ if (memcmp(hay + i, needle, nlen) == 0)
+ return (char *)(hay + i);
+ }
+ return NULL;
+}
+
+static void
+serve_file(int fd, const char *path)
+{
+ char full[512];
+ char hdr[256];
+ FILE *fp;
+ long sz;
+ int n;
+
+ if (strstr(path, "..") != NULL) {
+ sendtxt(fd, "400 Bad Request", "bad path\n");
+ return;
+ }
+ if (has_suffix(path, ".ttl") != 0) {
+ sendtxt(fd, "404 Not Found", "not found\n");
+ return;
+ }
+ snprintf(full, sizeof(full), ".%s", path);
+ fp = fopen(full, "rb");
+ if (fp == NULL) {
+ sendtxt(fd, "404 Not Found", "not found\n");
+ return;
+ }
+ fseek(fp, 0, SEEK_END);
+ sz = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ if (sz < 0) {
+ fclose(fp);
+ sendtxt(fd, "500 Internal Server Error", "io error\n");
+ return;
+ }
+ n = snprintf(hdr, sizeof(hdr),
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: %s\r\n"
+ "Content-Length: %ld\r\n"
+ "Connection: close\r\n\r\n",
+ ext_mime(full), sz);
+ sendall(fd, hdr, (size_t)n);
+ while (1) {
+ char buf[8192];
+ size_t r;
+
+ r = fread(buf, 1, sizeof(buf), fp);
+ if (r == 0)
+ break;
+ sendall(fd, buf, r);
+ }
+ fclose(fp);
+}
+
+static void
+serve_list(int fd)
+{
+#if ENABLE_INDEX
+ char out[65536];
+ struct dirent *e;
+ size_t off;
+ DIR *d;
+
+ off = 0;
+ off += (size_t)snprintf(out + off, sizeof(out) - off,
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "Connection: close\r\n\r\n"
+ "<h1>f/</h1><ul>");
+ d = opendir("f");
+ if (d != NULL) {
+ while ((e = readdir(d)) != NULL) {
+ if (e->d_name[0] == '.' || off + 256 >= sizeof(out))
+ continue;
+ off += (size_t)snprintf(out + off, sizeof(out) - off,
+ "<li><a href='/f/%s'>%s</a></li>",
+ e->d_name, e->d_name);
+ }
+ closedir(d);
+ }
+ off += (size_t)snprintf(out + off, sizeof(out) - off, "</ul>");
+ sendall(fd, out, off);
+#else
+ sendtxt(fd, "403 Forbidden", "index listing is off\n");
+#endif
+}
+
+static int
+hdr_get(const char *hdr, const char *name, char *out, size_t outsz)
+{
+ const char *line;
+ const char *line_end;
+ const char *v;
+ size_t len;
+ size_t nl;
+
+ nl = strlen(name);
+ if (outsz == 0)
+ return 0;
+ out[0] = '\0';
+ line = hdr;
+ while (*line != '\0') {
+ line_end = strstr(line, "\r\n");
+ if (line_end == NULL || line_end == line)
+ break;
+ if ((size_t)(line_end - line) > nl &&
+ eqi_n(line, name, nl) != 0 && line[nl] == ':') {
+ v = line + nl + 1;
+ while (v < line_end && (*v == ' ' || *v == '\t'))
+ v++;
+ len = (size_t)(line_end - v);
+ if (len >= outsz)
+ len = outsz - 1;
+ memcpy(out, v, len);
+ out[len] = '\0';
+ return 1;
+ }
+ line = line_end + 2;
+ }
+ return 0;
+}
+
+static void
+serve_upload(int fd, char *req, size_t req_len)
+{
+ char boundary[256];
+ char bmark[300];
+ char bterm[304];
+ char cd[512];
+ char cl[64];
+ char ct[512];
+ char field[64];
+ char filename[256];
+ char final[64];
+ char path[128];
+ char rnd[16];
+ char ttl_hdr[64];
+ char *body;
+ char *bpos;
+ char *curr;
+ char *fn;
+ char *hdr_end;
+ char *part;
+ char *part_hdr_end;
+ char *pend;
+ char *file_body;
+ char *pbody;
+ const char *alpha;
+ off_t cap_sz;
+ off_t used_sz;
+ size_t bmark_len;
+ size_t bterm_len;
+ size_t file_bytes;
+ size_t field_len;
+ size_t part_hdr_len;
+ size_t part_rem;
+ size_t pbody_len;
+ time_t now;
+ long body_len;
+ long ttl_sec;
+ int have_file;
+ int i;
+ int urfd;
+ unsigned char r[12];
+ FILE *fp;
+
+ ttl_sec = DEFAULT_TTL_SEC;
+ filename[0] = '\0';
+ file_body = NULL;
+ file_bytes = 0;
+ have_file = 0;
+ hdr_end = strstr(req, "\r\n\r\n");
+ if (hdr_end == NULL) {
+ sendtxt(fd, "400 Bad Request", "bad request\n");
+ return;
+ }
+ *hdr_end = '\0';
+ if (hdr_get(req, "Content-Type", ct, sizeof(ct)) == 0 ||
+ hdr_get(req, "Content-Length", cl, sizeof(cl)) == 0) {
+ sendtxt(fd, "400 Bad Request", "missing headers\n");
+ return;
+ }
+ if (hdr_get(req, "X-Filebin-TTL", ttl_hdr, sizeof(ttl_hdr)) != 0) {
+ if (parse_ttl(ttl_hdr, &ttl_sec) == 0) {
+ sendtxt(fd, "400 Bad Request", "bad ttl (use 1m/1h/1d)\n");
+ return;
+ }
+ }
+ body_len = atol(cl);
+ if (body_len <= 0 || body_len > MAX_REQ) {
+ sendtxt(fd, "413 Payload Too Large", "bad size\n");
+ return;
+ }
+ body = hdr_end + 4;
+ if ((size_t)(body - req) + (size_t)body_len > req_len) {
+ sendtxt(fd, "400 Bad Request", "truncated body\n");
+ return;
+ }
+ bpos = strstr(ct, "boundary=");
+ if (bpos == NULL) {
+ sendtxt(fd, "400 Bad Request", "no boundary\n");
+ return;
+ }
+ bpos += 9;
+ i = 0;
+ if (*bpos == '"') {
+ bpos++;
+ while (bpos[i] != '\0' && bpos[i] != '"' &&
+ i + 1 < (int)sizeof(boundary)) {
+ boundary[i] = bpos[i];
+ i++;
+ }
+ } else {
+ while (bpos[i] != '\0' && bpos[i] != ';' && bpos[i] != ' ' &&
+ bpos[i] != '\t' && i + 1 < (int)sizeof(boundary)) {
+ boundary[i] = bpos[i];
+ i++;
+ }
+ }
+ boundary[i] = '\0';
+ if (boundary[0] == '\0') {
+ sendtxt(fd, "400 Bad Request", "bad boundary\n");
+ return;
+ }
+ snprintf(bmark, sizeof(bmark), "--%s", boundary);
+ snprintf(bterm, sizeof(bterm), "\r\n--%s", boundary);
+ bmark_len = strlen(bmark);
+ bterm_len = strlen(bterm);
+ if ((size_t)body_len < bmark_len ||
+ memcmp(body, bmark, bmark_len) != 0) {
+ sendtxt(fd, "400 Bad Request", "bad multipart\n");
+ return;
+ }
+ curr = body;
+ while (curr < body + body_len) {
+ part = curr + bmark_len;
+ part_rem = (size_t)(body + body_len - part);
+ if (part_rem >= 2 && part[0] == '-' && part[1] == '-')
+ break;
+ if (part_rem < 2 || part[0] != '\r' || part[1] != '\n') {
+ sendtxt(fd, "400 Bad Request", "bad part\n");
+ return;
+ }
+ part += 2;
+ part_rem = (size_t)(body + body_len - part);
+ part_hdr_end = find_bytes(part, part_rem, "\r\n\r\n", 4);
+ if (part_hdr_end == NULL) {
+ sendtxt(fd, "400 Bad Request", "bad part\n");
+ return;
+ }
+ part_hdr_len = (size_t)(part_hdr_end - part);
+ if (part_hdr_len >= sizeof(ct)) {
+ sendtxt(fd, "400 Bad Request", "part headers too large\n");
+ return;
+ }
+ memcpy(ct, part, part_hdr_len);
+ ct[part_hdr_len] = '\0';
+ if (hdr_get(ct, "Content-Disposition", cd, sizeof(cd)) == 0) {
+ sendtxt(fd, "400 Bad Request", "no disposition\n");
+ return;
+ }
+ field[0] = '\0';
+ fn = strstr(cd, "name=");
+ if (fn != NULL) {
+ fn += 5;
+ if (*fn == '"') {
+ fn++;
+ i = 0;
+ while (*fn != '\0' && *fn != '"' &&
+ i + 1 < (int)sizeof(field)) {
+ field[i++] = *fn++;
+ }
+ field[i] = '\0';
+ }
+ }
+ fn = strstr(cd, "filename=");
+ filename[0] = '\0';
+ if (fn != NULL) {
+ fn += 9;
+ if (*fn == '"') {
+ fn++;
+ i = 0;
+ while (*fn != '\0' && *fn != '"' &&
+ i + 1 < (int)sizeof(filename)) {
+ filename[i++] = *fn++;
+ }
+ filename[i] = '\0';
+ }
+ }
+ pbody = part_hdr_end + 4;
+ pbody_len = (size_t)(body + body_len - pbody);
+ pend = find_bytes(pbody, pbody_len, bterm, bterm_len);
+ if (pend == NULL) {
+ sendtxt(fd, "400 Bad Request", "part end missing\n");
+ return;
+ }
+ if (strcmp(field, "ttl") == 0) {
+ field_len = (size_t)(pend - pbody);
+ if (field_len >= sizeof(ttl_hdr))
+ field_len = sizeof(ttl_hdr) - 1;
+ memcpy(ttl_hdr, pbody, field_len);
+ ttl_hdr[field_len] = '\0';
+ if (parse_ttl(ttl_hdr, &ttl_sec) == 0) {
+ sendtxt(fd, "400 Bad Request", "bad ttl (use 1m/1h/1d)\n");
+ return;
+ }
+ } else if (filename[0] != '\0' || strcmp(field, "file") == 0) {
+ file_body = pbody;
+ file_bytes = (size_t)(pend - pbody);
+ have_file = 1;
+ }
+ curr = pend + 2;
+ if ((size_t)(body + body_len - curr) < bmark_len ||
+ memcmp(curr, bmark, bmark_len) != 0) {
+ sendtxt(fd, "400 Bad Request", "bad multipart\n");
+ return;
+ }
+ }
+ if (have_file == 0) {
+ sendtxt(fd, "400 Bad Request", "missing file\n");
+ return;
+ }
+ used_sz = used_bytes();
+ cap_sz = cap_bytes();
+ if ((off_t)file_bytes > cap_sz - used_sz) {
+ sendtxt(fd, "507 Insufficient Storage", "storage cap reached\n");
+ return;
+ }
+ alpha = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ urfd = open("/dev/urandom", O_RDONLY);
+ if (urfd < 0 || read(urfd, r, sizeof(r)) != (ssize_t)sizeof(r)) {
+ if (urfd >= 0)
+ close(urfd);
+ srand((unsigned int)(time(NULL) ^ getpid()));
+ for (i = 0; i < 12; i++)
+ r[i] = (unsigned char)(rand() & 0xff);
+ } else
+ close(urfd);
+ for (i = 0; i < 12; i++)
+ rnd[i] = alpha[r[i] % 62];
+ rnd[12] = '\0';
+ clean_ext(filename, ct, sizeof(ct));
+ if (ct[0] == '\0')
+ snprintf(final, sizeof(final), "%s.bin", rnd);
+ else
+ snprintf(final, sizeof(final), "%s.%s", rnd, ct);
+ snprintf(path, sizeof(path), "f/%s", final);
+ fp = fopen(path, "wb");
+ if (fp == NULL) {
+ sendtxt(fd, "500 Internal Server Error", "cannot write\n");
+ return;
+ }
+ fwrite(file_body, 1, file_bytes, fp);
+ fclose(fp);
+ now = time(NULL);
+ meta_write(final, now + (time_t)ttl_sec);
+ if (isimg_ext(ct) != 0) {
+ char resp[2048];
+ int n;
+
+ n = snprintf(resp, sizeof(resp),
+ "HTTP/1.1 201 Created\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "Connection: close\r\n\r\n"
+ "<!doctype html><meta charset=utf-8><title>uploaded</title>"
+ "<p>uploaded: <a href='/f/%s'>/f/%s</a></p>"
+ "<p><img src='/f/%s' style='max-width:100%%;height:auto' "
+ "alt='upload'></p>",
+ final, final, final);
+ sendall(fd, resp, (size_t)n);
+ return;
+ }
+ {
+ char resp[512];
+ int n;
+
+ n = snprintf(resp, sizeof(resp),
+ "HTTP/1.1 201 Created\r\n"
+ "Content-Type: text/plain; charset=utf-8\r\n"
+ "Connection: close\r\n\r\n"
+ "/f/%s\n",
+ final);
+ sendall(fd, resp, (size_t)n);
+ }
+}
+
+static int
+set_nb(int fd, int on)
+{
+ int fl;
+
+ fl = fcntl(fd, F_GETFL, 0);
+ if (fl < 0)
+ return -1;
+ if (on != 0)
+ fl |= O_NONBLOCK;
+ else
+ fl &= ~O_NONBLOCK;
+ return fcntl(fd, F_SETFL, fl);
+}
+
+static void
+client_close(struct client *cl)
+{
+ if (cl->fd >= 0)
+ close(cl->fd);
+ free(cl->req);
+ cl->req = NULL;
+ cl->off = 0;
+ cl->cap = 0;
+ cl->last_active = 0;
+ cl->fd = -1;
+}
+
+static int
+grow_reqbuf(struct client *cl)
+{
+ char *p;
+ size_t newcap;
+
+ if (cl->off < cl->cap)
+ return 1;
+ if (cl->cap >= MAX_REQ)
+ return 0;
+ newcap = cl->cap == 0 ? INITIAL_REQ_CAP : cl->cap * 2;
+ if (newcap > MAX_REQ)
+ newcap = MAX_REQ;
+ p = realloc(cl->req, newcap + 1);
+ if (p == NULL)
+ return 0;
+ cl->req = p;
+ cl->cap = newcap;
+ return 1;
+}
+
+static int
+req_done(const char *req, size_t off)
+{
+ char *hdr_end;
+ long need;
+ size_t have;
+
+ hdr_end = strstr((char *)req, "\r\n\r\n");
+ if (hdr_end == NULL)
+ return 0;
+ need = parse_clen(req, hdr_end);
+ have = off - (size_t)(hdr_end + 4 - req);
+ if (need < 0)
+ return 1;
+ if (have >= (size_t)need)
+ return 1;
+ return 0;
+}
+
+static void
+serve_req(int fd, char *req, size_t req_len)
+{
+ char method[16];
+ char path[1024];
+
+ (void)req_len;
+ method[0] = '\0';
+ path[0] = '\0';
+ sscanf(req, "%15s %1023s", method, path);
+ if (strcmp(method, "GET") == 0 && strcmp(path, "/") == 0)
+ sendall(fd, html_index, strlen(html_index));
+ else if (strcmp(method, "GET") == 0 && strcmp(path, "/f") == 0)
+ serve_list(fd);
+ else if (strcmp(method, "GET") == 0 && strncmp(path, "/f/", 3) == 0)
+ serve_file(fd, path);
+ else if (strcmp(method, "POST") == 0 && strcmp(path, "/upload") == 0)
+ serve_upload(fd, req, req_len);
+ else
+ sendtxt(fd, "404 Not Found", "not found\n");
+}
+
+static void
+usage(void)
+{
+#ifdef HAVE_CHROOT
+ fprintf(stderr,
+ "usage: filebin [-d] [-c chrootdir] [-p port] [-u user:group]\n");
+#else
+ fprintf(stderr, "usage: filebin [-d] [-p port] [-u user:group]\n");
+#endif
+ exit(1);
+}
+
+static void
+die(const char *msg)
+{
+ perror(msg);
+ exit(1);
+}
+
+static void
+diex(const char *msg, const char *arg)
+{
+ fprintf(stderr, "%s: %s\n", msg, arg);
+ exit(1);
+}
+
+static void
+warnp(const char *msg)
+{
+ perror(msg);
+}
+
+static void
+do_chroot(const char *dir)
+{
+#ifdef HAVE_CHROOT
+ if (dir == NULL)
+ return;
+ if (chroot(dir) != 0)
+ die("chroot");
+ if (chdir("/") != 0)
+ die("chdir");
+#else
+ if (dir != NULL)
+ diex("chroot unsupported on this platform", dir);
+#endif
+}
+
+static void
+drop_privs(int enabled, uid_t uid, gid_t gid)
+{
+ if (enabled == 0)
+ return;
+ if (setgid(gid) != 0)
+ die("setgid");
+ if (setuid(uid) != 0)
+ die("setuid");
+}
+
+static void
+daemonize_self(void)
+{
+ int devnull;
+ pid_t pid;
+
+ pid = fork();
+ if (pid < 0)
+ die("fork");
+ if (pid > 0)
+ _exit(0);
+ if (setsid() < 0)
+ die("setsid");
+ signal(SIGHUP, SIG_IGN);
+ pid = fork();
+ if (pid < 0)
+ die("fork");
+ if (pid > 0)
+ _exit(0);
+ devnull = open("/dev/null", O_RDWR);
+ if (devnull < 0)
+ die("open /dev/null");
+ if (dup2(devnull, STDIN_FILENO) < 0)
+ die("dup2 stdin");
+ if (dup2(devnull, STDOUT_FILENO) < 0)
+ die("dup2 stdout");
+ if (dup2(devnull, STDERR_FILENO) < 0)
+ die("dup2 stderr");
+ if (devnull > STDERR_FILENO)
+ close(devnull);
+}
+
+static void
+obsd_sandbox(void)
+{
+#ifdef __OpenBSD__
+ if (unveil("f", "rwc") != 0)
+ die("unveil f");
+ if (unveil("/dev/urandom", "r") != 0)
+ die("unveil urandom");
+ if (unveil(NULL, NULL) != 0)
+ die("unveil lock");
+ if (pledge("stdio inet rpath wpath cpath", NULL) != 0)
+ die("pledge");
+#endif
+}
+
+int
+main(int argc, char *argv[])
+{
+#ifdef HAVE_CHROOT
+ const char *chroot_dir;
+#endif
+ char *end;
+ struct client clients[MAX_CLIENTS];
+ struct pollfd pfds[MAX_CLIENTS + 1];
+ struct sockaddr_in sin;
+ time_t last_cleanup;
+ uid_t run_uid;
+ gid_t run_gid;
+ int map[MAX_CLIENTS + 1];
+ int c;
+ int ch;
+ int do_daemon;
+ int do_drop;
+ int i;
+ int nfds;
+ int one;
+ int port;
+ int pr;
+ int s;
+
+ port = PORT;
+ do_daemon = 0;
+ do_drop = 0;
+ run_uid = 0;
+ run_gid = 0;
+#ifdef HAVE_CHROOT
+ chroot_dir = NULL;
+#endif
+ while ((ch = getopt(argc, argv, OPTSTR)) != -1) {
+ switch (ch) {
+ case 'd':
+ do_daemon = 1;
+ break;
+#ifdef HAVE_CHROOT
+ case 'c':
+ chroot_dir = optarg;
+ break;
+#endif
+ case 'p':
+ errno = 0;
+ port = (int)strtol(optarg, &end, 10);
+ if (optarg[0] == '\0' || *end != '\0' || errno != 0 ||
+ port <= 0 || port > 65535)
+ usage();
+ break;
+ case 'u':
+ if (parse_ug(optarg, &run_uid, &run_gid) == 0)
+ diex("invalid user:group", optarg);
+ do_drop = 1;
+ break;
+ default:
+ usage();
+ }
+ }
+ argc -= optind;
+ argv += optind;
+ if (argc != 0)
+ usage();
+
+ s = socket(AF_INET, SOCK_STREAM, 0);
+ if (s < 0)
+ die("socket");
+ one = 1;
+ if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != 0)
+ die("setsockopt");
+ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(INADDR_ANY);
+ sin.sin_port = htons((unsigned short)port);
+ if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0)
+ die("bind");
+ if (listen(s, 16) != 0)
+ die("listen");
+#ifdef HAVE_CHROOT
+ do_chroot(chroot_dir);
+#else
+ do_chroot(NULL);
+#endif
+ if (mk_fdir() != 0)
+ die("mkdir f");
+ drop_privs(do_drop, run_uid, run_gid);
+ obsd_sandbox();
+ if (set_nb(s, 1) != 0)
+ die("set_nb");
+ if (do_daemon != 0)
+ daemonize_self();
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ clients[i].req = NULL;
+ clients[i].off = 0;
+ clients[i].cap = 0;
+ clients[i].last_active = 0;
+ clients[i].fd = -1;
+ }
+ last_cleanup = 0;
+ printf("filebin listening on :%d\n", port);
+ fflush(stdout);
+
+ while (1) {
+ time_t now;
+
+ now = time(NULL);
+ if (last_cleanup == 0 || now - last_cleanup >= CLEANUP_INTERVAL_SEC) {
+ cleanup_expired();
+ last_cleanup = now;
+ }
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if (clients[i].fd >= 0 &&
+ now - clients[i].last_active >= CLIENT_IDLE_TIMEOUT_SEC)
+ client_close(&clients[i]);
+ }
+
+ pfds[0].fd = s;
+ pfds[0].events = POLLIN;
+ pfds[0].revents = 0;
+ map[0] = -1;
+ nfds = 1;
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if (clients[i].fd < 0)
+ continue;
+ pfds[nfds].fd = clients[i].fd;
+ pfds[nfds].events = POLLIN;
+ pfds[nfds].revents = 0;
+ map[nfds] = i;
+ nfds++;
+ }
+ pr = poll(pfds, (nfds_t)nfds, 1000);
+ if (pr < 0) {
+ if (errno == EINTR)
+ continue;
+ warnp("poll");
+ continue;
+ }
+ if ((pfds[0].revents & POLLIN) != 0) {
+ while (1) {
+ c = accept(s, NULL, NULL);
+ if (c < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ break;
+ warnp("accept");
+ break;
+ }
+ if (set_nb(c, 1) != 0) {
+ close(c);
+ continue;
+ }
+ for (i = 0; i < MAX_CLIENTS; i++) {
+ if (clients[i].fd < 0)
+ break;
+ }
+ if (i == MAX_CLIENTS) {
+ close(c);
+ continue;
+ }
+ clients[i].req = malloc(INITIAL_REQ_CAP + 1);
+ if (clients[i].req == NULL) {
+ close(c);
+ continue;
+ }
+ clients[i].req[0] = '\0';
+ clients[i].off = 0;
+ clients[i].cap = INITIAL_REQ_CAP;
+ clients[i].last_active = now;
+ clients[i].fd = c;
+ }
+ }
+ for (i = 1; i < nfds; i++) {
+ struct client *cl;
+ ssize_t nr;
+
+ if ((pfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) != 0) {
+ client_close(&clients[map[i]]);
+ continue;
+ }
+ if ((pfds[i].revents & POLLIN) == 0)
+ continue;
+ cl = &clients[map[i]];
+ while (1) {
+ if (grow_reqbuf(cl) == 0) {
+ sendtxt(cl->fd, "413 Payload Too Large", "bad size\n");
+ client_close(cl);
+ break;
+ }
+ nr = recv(cl->fd, cl->req + cl->off, cl->cap - cl->off, 0);
+ if (nr > 0) {
+ cl->off += (size_t)nr;
+ cl->req[cl->off] = '\0';
+ cl->last_active = now;
+ if (req_done(cl->req, cl->off) != 0) {
+ serve_req(cl->fd, cl->req, cl->off);
+ client_close(cl);
+ break;
+ }
+ if (cl->off >= MAX_REQ) {
+ sendtxt(cl->fd, "413 Payload Too Large", "bad size\n");
+ client_close(cl);
+ break;
+ }
+ continue;
+ }
+ if (nr == 0) {
+ client_close(cl);
+ break;
+ }
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ break;
+ client_close(cl);
+ break;
+ }
+ }
+ }
+}