git.strcat.st

/strcat/filebin.git/ - summarytreelogarchive

subject
add password protected uploads & readme
commit
e5373b0d9b52017f6bfdedb875bdf4c02b5ed3f0
date
2026-05-28T18:52:34Z
message
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"
+```