diff
filebin.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
readme.md | 100 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 240 insertions(+), 5 deletions(-)
diff --git a/filebin.c b/filebin.c
index 04b03f8..1c02b1f 100644
--- a/filebin.c
+++ b/filebin.c
@@ -94,6 +94,7 @@ static int parse_size_gb(const char *, off_t *);
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 pass_read(const char *, char *, size_t);
static int set_nb(int, int);
static off_t cap_bytes(void);
@@ -110,13 +111,14 @@ 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_file(int, const char *, 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 mk_passpath(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);
@@ -279,6 +281,12 @@ mk_metapath(const char *name, char *out, size_t outsz)
snprintf(out, outsz, "f/%s.ttl", name);
}
+static void
+mk_passpath(const char *name, char *out, size_t outsz)
+{
+ snprintf(out, outsz, "f/%s.pw", name);
+}
+
static int
parse_ttl(const char *val, long *out)
{
@@ -408,6 +416,32 @@ meta_read(const char *name, time_t *exp)
return 1;
}
+static int
+pass_read(const char *name, char *out, size_t outsz)
+{
+ char path[512];
+ FILE *fp;
+ size_t n;
+
+ if (outsz == 0)
+ return 0;
+ out[0] = '\0';
+ mk_passpath(name, path, sizeof(path));
+ fp = fopen(path, "rb");
+ if (fp == NULL)
+ return 0;
+ if (fgets(out, (int)outsz, fp) == NULL) {
+ fclose(fp);
+ out[0] = '\0';
+ return 0;
+ }
+ fclose(fp);
+ n = strlen(out);
+ while (n > 0 && (out[n - 1] == '\n' || out[n - 1] == '\r'))
+ out[--n] = '\0';
+ return 1;
+}
+
static off_t
cap_bytes(void)
{
@@ -428,7 +462,8 @@ used_bytes(void)
if (d == NULL)
return 0;
while ((e = readdir(d)) != NULL) {
- if (e->d_name[0] == '.' || has_suffix(e->d_name, ".ttl") != 0)
+ if (e->d_name[0] == '.' || has_suffix(e->d_name, ".ttl") != 0 ||
+ has_suffix(e->d_name, ".pw") != 0)
continue;
snprintf(path, sizeof(path), "f/%s", e->d_name);
if (stat(path, &st) != 0 || S_ISREG(st.st_mode) == 0)
@@ -445,6 +480,7 @@ cleanup_expired(void)
{
char base[256];
char mpath[512];
+ char ppath[512];
char path[512];
struct dirent *e;
struct stat st;
@@ -475,6 +511,18 @@ cleanup_expired(void)
unlink(mpath);
continue;
}
+ if (has_suffix(e->d_name, ".pw") != 0) {
+ n = strlen(e->d_name);
+ if (n <= 3 || n - 3 >= sizeof(base))
+ continue;
+ snprintf(ppath, sizeof(ppath), "f/%s", e->d_name);
+ memcpy(base, e->d_name, n - 3);
+ base[n - 3] = '\0';
+ snprintf(path, sizeof(path), "f/%s", base);
+ if (stat(path, &st) != 0)
+ unlink(ppath);
+ continue;
+ }
if (meta_read(e->d_name, &exp) == 0)
exp = st.st_mtime + (time_t)g_default_ttl_sec;
if (now < exp)
@@ -483,6 +531,8 @@ cleanup_expired(void)
unlink(path);
mk_metapath(e->d_name, mpath, sizeof(mpath));
unlink(mpath);
+ mk_passpath(e->d_name, ppath, sizeof(ppath));
+ unlink(ppath);
}
closedir(d);
}
@@ -689,23 +739,74 @@ find_bytes(const char *hay, size_t hlen, const char *needle, size_t nlen)
}
static void
-serve_file(int fd, const char *path)
+serve_file(int fd, const char *path, const char *req)
{
+ char phead[128];
+ char pmeta[128];
+ char pquery[128];
+ char reqpw[128];
char full[512];
char hdr[256];
+ char name[256];
+ char *q;
+ char *pp;
+ size_t i;
+ size_t nlen;
FILE *fp;
long sz;
int n;
+ phead[0] = '\0';
+ pmeta[0] = '\0';
+ pquery[0] = '\0';
+ reqpw[0] = '\0';
+ if (hdr_get(req, "X-Filebin-Password", phead, sizeof(phead)) != 0) {
+ nlen = strlen(phead);
+ while (nlen > 0 &&
+ (phead[nlen - 1] == ' ' || phead[nlen - 1] == '\t'))
+ phead[--nlen] = '\0';
+ }
if (strstr(path, "..") != NULL) {
sendtxt(fd, "400 Bad Request", "bad path\n");
return;
}
- if (has_suffix(path, ".ttl") != 0) {
+ if (has_suffix(path, ".ttl") != 0 || has_suffix(path, ".pw") != 0) {
sendtxt(fd, "404 Not Found", "not found\n");
return;
}
+ q = strchr(path, '?');
+ nlen = q == NULL ? strlen(path) : (size_t)(q - path);
+ if (nlen <= 3 || nlen - 3 >= sizeof(name)) {
+ sendtxt(fd, "400 Bad Request", "bad path\n");
+ return;
+ }
+ memcpy(name, path + 3, nlen - 3);
+ name[nlen - 3] = '\0';
+ if (q != NULL) {
+ pp = strstr(q + 1, "pw=");
+ if (pp != NULL) {
+ pp += 3;
+ i = 0;
+ while (pp[i] != '\0' && pp[i] != '&' && i + 1 < sizeof(pquery)) {
+ pquery[i] = pp[i];
+ i++;
+ }
+ pquery[i] = '\0';
+ }
+ }
+ if (pass_read(name, pmeta, sizeof(pmeta)) != 0) {
+ if (phead[0] != '\0')
+ snprintf(reqpw, sizeof(reqpw), "%s", phead);
+ else if (pquery[0] != '\0')
+ snprintf(reqpw, sizeof(reqpw), "%s", pquery);
+ if (reqpw[0] == '\0' || strcmp(reqpw, pmeta) != 0) {
+ sendtxt(fd, "401 Unauthorized", "password required\n");
+ return;
+ }
+ }
snprintf(full, sizeof(full), ".%s", path);
+ if (q != NULL)
+ full[(size_t)(q - path) + 1] = '\0';
fp = fopen(full, "rb");
if (fp == NULL) {
sendtxt(fd, "404 Not Found", "not found\n");
@@ -821,6 +922,8 @@ serve_upload(int fd, char *req, size_t req_len)
char path[128];
char rnd[16];
char ttl_hdr[64];
+ char pass_hdr[128];
+ char pass_val[128];
char *dec_body;
char *body;
char *bpos;
@@ -842,10 +945,12 @@ serve_upload(int fd, char *req, size_t req_len)
size_t part_hdr_len;
size_t part_rem;
size_t pbody_len;
+ size_t pass_len;
time_t now;
long body_len;
long ttl_sec;
int have_file;
+ int have_pass;
int is_chunked;
int i;
int urfd;
@@ -854,10 +959,12 @@ serve_upload(int fd, char *req, size_t req_len)
ttl_sec = g_default_ttl_sec;
dec_body = NULL;
+ pass_val[0] = '\0';
filename[0] = '\0';
file_body = NULL;
file_bytes = 0;
have_file = 0;
+ have_pass = 0;
hdr_end = strstr(req, "\r\n\r\n");
if (hdr_end == NULL) {
sendtxt(fd, "400 Bad Request", "bad request\n");
@@ -879,6 +986,16 @@ serve_upload(int fd, char *req, size_t req_len)
return;
}
}
+ if (hdr_get(req, "X-Filebin-Password", pass_hdr, sizeof(pass_hdr)) != 0) {
+ pass_len = strlen(pass_hdr);
+ while (pass_len > 0 &&
+ (pass_hdr[pass_len - 1] == ' ' || pass_hdr[pass_len - 1] == '\t'))
+ pass_hdr[--pass_len] = '\0';
+ if (pass_hdr[0] != '\0') {
+ snprintf(pass_val, sizeof(pass_val), "%s", pass_hdr);
+ have_pass = 1;
+ }
+ }
body = hdr_end + 4;
if (is_chunked != 0) {
size_t raw_len;
@@ -1019,6 +1136,14 @@ serve_upload(int fd, char *req, size_t req_len)
sendtxt(fd, "400 Bad Request", "bad ttl (use 1m/1h/1d)\n");
return;
}
+ } else if (strcmp(field, "password") == 0) {
+ field_len = (size_t)(pend - pbody);
+ if (field_len >= sizeof(pass_val))
+ field_len = sizeof(pass_val) - 1;
+ memcpy(pass_val, pbody, field_len);
+ pass_val[field_len] = '\0';
+ if (pass_val[0] != '\0')
+ have_pass = 1;
} else if (filename[0] != '\0' || strcmp(field, "file") == 0) {
file_body = pbody;
file_bytes = (size_t)(pend - pbody);
@@ -1071,6 +1196,16 @@ serve_upload(int fd, char *req, size_t req_len)
}
fwrite(file_body, 1, file_bytes, fp);
fclose(fp);
+ if (have_pass != 0) {
+ char pwpath[128];
+
+ mk_passpath(final, pwpath, sizeof(pwpath));
+ fp = fopen(pwpath, "wb");
+ if (fp != NULL) {
+ fprintf(fp, "%s\n", pass_val);
+ fclose(fp);
+ }
+ }
now = time(NULL);
meta_write(final, now + (time_t)ttl_sec);
if (isimg_ext(ct) != 0) {
@@ -1207,7 +1342,7 @@ serve_req(int fd, char *req, size_t req_len)
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);
+ serve_file(fd, path, req);
else if (strcmp(method, "POST") == 0 && strcmp(path, "/upload") == 0)
serve_upload(fd, req, req_len);
else
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..7e0f7c2
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,100 @@
+# filebin
+
+a tiny http file drop in c.
+
+## what it does
+
+filebin serves a small upload page at `/` and accepts `post /upload` requests.
+you can fetch files at `get /f/<name>`, set ttl by form field or header value.
+expired files are removed by a background cleanup loop that runs on a timer.
+uploads can be password protected and require a password to fetch the file.
+
+## build and run
+
+```sh
+make
+./filebin -p 8080 -m 16 -n 128 -s 100 -t 3d
+```
+
+then open `http://127.0.0.1:8080/`.
+
+## flags
+
+- `-p <port>` set port
+- `-m <mb>` max upload request size in mb
+- `-n <count>` max open clients
+- `-s <gb>` storage cap in gb
+- `-t <ttl>` default ttl (like `1h`, `1d`, `3d`)
+- `-d` run in daemon mode
+- `-u <user:group>` drop privileges
+- `-c <dir>` chroot
+
+## limits
+
+- defaults are `-m 16`, `-n 128`, `-s 100`, `-t 3d`
+
+## openrc example
+
+`/etc/init.d/filebin`:
+
+```sh
+#!/sbin/openrc-run
+
+name="filebin"
+description=""
+command="/usr/local/bin/filebin"
+command_args="-p 8080 -m 64 -n 256 -s 200 -t 1d -u filebin:filebin -c /var/lib/filebin"
+command_background="yes"
+pidfile="/run/${RC_SVCNAME}.pid"
+
+depend() {
+ need net
+}
+```
+
+then:
+
+```sh
+chmod +x /etc/init.d/filebin
+rc-update add filebin default
+rc-service filebin start
+```
+
+## curl examples
+
+```sh
+curl -F "file=@somefile.txt" -F "ttl=1h" http://127.0.0.1:8080/upload
+```
+
+### paste text via curl
+
+```sh
+printf 'hello world\nthis is a paste\n' | \
+curl -F 'file=@-;filename=paste.txt' -F 'ttl=1h' http://127.0.0.1:8080/upload
+```
+
+### password protected upload
+
+set password on upload with form field:
+
+```sh
+curl -F "file=@somefile.txt" -F "password=secret123" http://127.0.0.1:8080/upload
+```
+
+or set password with header:
+
+```sh
+curl -H "x-filebin-password: secret123" -F "file=@somefile.txt" http://127.0.0.1:8080/upload
+```
+
+fetch a protected file with header:
+
+```sh
+curl -H "x-filebin-password: secret123" http://127.0.0.1:8080/f/<name>
+```
+
+or query string:
+
+```sh
+curl "http://127.0.0.1:8080/f/<name>?pw=secret123"
+```