commit 584aad56b82ff86a2b917dc00d74d97b723ca5ff Author: kento2 Date: Tue Sep 8 18:36:12 2026 +0200 initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5ec75fa --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +example.input -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c048a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build/ diff --git a/README b/README new file mode 100644 index 0000000..a8315c2 --- /dev/null +++ b/README @@ -0,0 +1,24 @@ +nobotgate +========= + +When you run a private site (that still uses a +super-serve), you can use nobotgate to require users to +provide an http bearer token. This way, bots won't flood +your site with spam. + +usage +----- + +use nobotgate(1) together with a super-server such as inetd(8), +tcpserver(1) or s6-tcpserver: + # tcpserver 0.0.0.0 80 nobotgate /etc/wwwtokens ./on-valid ./on-invalid +where /etc/wwwtokens contains: + +and ./on-valid contains: + #!/bin/sh + exec busybox httpd -i -h /var/www +and ./on-invalid contains: + #!/bin/sh + exec nobotgate-access cat usage +and usage contains: + No bearer token specified! diff --git a/check_token.c b/check_token.c new file mode 100644 index 0000000..2adc6ce --- /dev/null +++ b/check_token.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include + +#include "nobotgate.h" + +int check_token(char *tokenfile, stralloc *token) +{ + int fd; + char buf[4096]; + buffer b; + int r,found; + stralloc line; + fd = open_read(tokenfile); + if (fd < 0) + diesys(errno == ENOENT ? 100 : 111, "open_read ", tokenfile); + buffer_init_read(&b, fd, buf, sizeof(buf)); + stralloc_init(&line); + + found = 0; + for (;;) { + r = buffer_getnewline_sa(&b, &line); + if (r == 0) break; + if (r < 0) diesys(111, "failed reading tokenfile ", tokenfile); + stralloc_chomp(&line); + stralloc_0(&line); + line.s[str_chr(line.s, ' ')] = 0; + if (stralloc_equals(token, line.s)) { + found = 1; + break; + } + } + + buffer_close(&b); + stralloc_free(&line); + return found; +} diff --git a/example.input b/example.input new file mode 100644 index 0000000..9766e4f --- /dev/null +++ b/example.input @@ -0,0 +1,2 @@ +Authorization: Bearer 1234 + diff --git a/example.service/on-invalid b/example.service/on-invalid new file mode 100755 index 0000000..def9172 --- /dev/null +++ b/example.service/on-invalid @@ -0,0 +1,4 @@ +#!/bin/sh +exec \ + nobotgate-access \ + cat usage diff --git a/example.service/on-valid b/example.service/on-valid new file mode 100755 index 0000000..516e3d1 --- /dev/null +++ b/example.service/on-valid @@ -0,0 +1,3 @@ +#!/bin/sh +exec \ + busybox httpd -i -h . diff --git a/example.service/run b/example.service/run new file mode 100755 index 0000000..746063a --- /dev/null +++ b/example.service/run @@ -0,0 +1,10 @@ +#!/bin/sh +# service that runs a web server protected with nobotgate. +# if the user provided a valid token, it will exec into +# ./on-valid, if not, into ./on-invalid. +exec \ + envuidgid www \ + tcpserver -U 127.0.0.1 80 \ + ../build/nobotgate ./tokens \ + ./on-valid \ + ./on-invalid diff --git a/example.service/tokens b/example.service/tokens new file mode 100644 index 0000000..08ccab1 --- /dev/null +++ b/example.service/tokens @@ -0,0 +1,2 @@ +1234 a pretty insecure token +11aa this token is a lot more secure diff --git a/example.service/trace b/example.service/trace new file mode 100644 index 0000000..dbbc145 --- /dev/null +++ b/example.service/trace @@ -0,0 +1,91 @@ +2783 execve("/usr/bin/envuidgid", ["envuidgid", "www", "tcpserver", "-U", "127.0.0.1", "80"], 0x7ffed7055678 /* 11 vars */) = 0 +2783 brk(NULL) = 0x558000b69000 +2783 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) +2783 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 +2783 fstat(3, {st_mode=S_IFREG|0644, st_size=129031, ...}) = 0 +2783 mmap(NULL, 129031, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f13d3dfa000 +2783 close(3) = 0 +2783 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13d3df8000 +2783 openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 +2783 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300\255\2\0\0\0\0\0@\0\0\0\0\0\0\08\264\301\0\0\0\0\0\0\0\0\0@\08\0\16\0@\0G\0F\0\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0\20\3\0\0\0\0\0\0\20\3\0\0\0\0\0\0\10\0\0\0\0\0\0\0\3\0\0\0\4\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\210t\2\0\0\0\0\0\210t\2\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\5\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0"..., 832) = 832 +2783 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0\20\3\0\0\0\0\0\0\20\3\0\0\0\0\0\0\10\0\0\0\0\0\0\0\3\0\0\0\4\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\210t\2\0\0\0\0\0\210t\2\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\5\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0\235\216\25\0\0\0\0\0\235\216\25\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\20\30\0\0\0\0\0\0\20\30\0\0\0\0\0\0\20\30\0\0\0\0\0"..., 784, 64) = 784 +2783 fstat(3, {st_mode=S_IFREG|0755, st_size=12699128, ...}) = 0 +2783 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0\20\3\0\0\0\0\0\0\20\3\0\0\0\0\0\0\10\0\0\0\0\0\0\0\3\0\0\0\4\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\210t\2\0\0\0\0\0\210t\2\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\5\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0\235\216\25\0\0\0\0\0\235\216\25\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\20\30\0\0\0\0\0\0\20\30\0\0\0\0\0\0\20\30\0\0\0\0\0"..., 784, 64) = 784 +2783 mmap(NULL, 2006680, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f13d3c0e000 +2783 mmap(0x7f13d3c36000, 1413120, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f13d3c36000 +2783 mmap(0x7f13d3d8f000, 352256, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x181000) = 0x7f13d3d8f000 +2783 mmap(0x7f13d3de5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d6000) = 0x7f13d3de5000 +2783 mmap(0x7f13d3deb000, 52888, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f13d3deb000 +2783 close(3) = 0 +2783 mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f13d3c0b000 +2783 arch_prctl(ARCH_SET_FS, 0x7f13d3c0b740) = 0 +2783 set_tid_address(0x7f13d3c0ba10) = 2783 +2783 set_robust_list(0x7f13d3c0ba20, 24) = 0 +2783 rseq({cpu_id_start=0, cpu_id=RSEQ_CPU_ID_UNINITIALIZED, rseq_cs=NULL, flags=0, node_id=0, mm_cid=0, slice_ctrl={request=0, granted=0, __reserved=0}, __reserved=0}, 32, 0, 0x53053053) = 0 +2783 mprotect(0x7f13d3de5000, 16384, PROT_READ) = 0 +2783 mprotect(0x557fcb203000, 4096, PROT_READ) = 0 +2783 mprotect(0x7f13d3e55000, 8192, PROT_READ) = 0 +2783 prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0 +2783 munmap(0x7f13d3dfa000, 129031) = 0 +2783 getrandom("\x50\xa4\x5b\x1a\x33\xa9\x65\xde", 8, GRND_NONBLOCK) = 8 +2783 brk(NULL) = 0x558000b69000 +2783 brk(0x558000b8a000) = 0x558000b8a000 +2783 socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3 +2783 connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory) +2783 close(3) = 0 +2783 socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0) = 3 +2783 connect(3, {sa_family=AF_UNIX, sun_path="/var/run/nscd/socket"}, 110) = -1 ENOENT (No such file or directory) +2783 close(3) = 0 +2783 newfstatat(AT_FDCWD, "/etc/nsswitch.conf", {st_mode=S_IFREG|0644, st_size=216, ...}, 0) = 0 +2783 newfstatat(AT_FDCWD, "/", {st_mode=S_IFDIR|0755, st_size=4096, ...}, 0) = 0 +2783 openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3 +2783 fstat(3, {st_mode=S_IFREG|0644, st_size=216, ...}) = 0 +2783 read(3, "# /etc/nsswitch.conf\n#\n# See nsswitch.conf(5) for information.\n#\npasswd: \tfiles\ngroup: \t\tfiles\nshadow: \tfiles\n\nhosts: \t\tfiles mdns dns\nnetworks: \tfiles\n\nprotocols: \tfiles\nservices: \tfiles\nethers: \tfiles\nrpc: \t\tfiles\n", 4096) = 216 +2783 read(3, "", 4096) = 0 +2783 fstat(3, {st_mode=S_IFREG|0644, st_size=216, ...}) = 0 +2783 close(3) = 0 +2783 openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 3 +2783 fstat(3, {st_mode=S_IFREG|0644, st_size=5894, ...}) = 0 +2783 lseek(3, 0, SEEK_SET) = 0 +2783 read(3, "root:x:0:0:root:/root:/bedrock/cross/bin/sh\nnobody:x:99:99:Unprivileged User:/dev/null:/bedrock/cross/bin/false\nchrony:x:999:999:chrony unprivileged user:/var/lib/chrony:/bedrock/cross/bin/nologin\n_dhcpcd:x:998:998:_dhcpcd unprivileged user:/var/db/dhcpcd:"..., 4096) = 4096 +2783 read(3, "6 unprivileged user:/var/empty:/bedrock/cross/bin/nologin\nnixbld7:x:979:952:nixbld7 unprivileged user:/var/empty:/bedrock/cross/bin/nologin\nnixbld8:x:978:952:nixbld8 unprivileged user:/var/empty:/bedrock/cross/bin/nologin\nnixbld9:x:977:952:nixbld9 unprivil"..., 4096) = 1798 +2783 close(3) = 0 +2783 execve("/home/a/.local/bin/tcpserver", ["tcpserver", "-U", "127.0.0.1", "80"], 0x557fcb2047f0 /* 13 vars */) = -1 ENOENT (No such file or directory) +2783 execve("/bedrock/cross/pin/bin/tcpserver", ["tcpserver", "-U", "127.0.0.1", "80"], 0x557fcb2047f0 /* 13 vars */) = -1 ENOENT (No such file or directory) +2783 execve("/bedrock/bin/tcpserver", ["tcpserver", "-U", "127.0.0.1", "80"], 0x557fcb2047f0 /* 13 vars */) = -1 ENOENT (No such file or directory) +2783 execve("/home/a/.nix-profile/bin/tcpserver", ["tcpserver", "-U", "127.0.0.1", "80"], 0x557fcb2047f0 /* 13 vars */) = -1 ENOENT (No such file or directory) +2783 execve("/nix/var/nix/profiles/default/bin/tcpserver", ["tcpserver", "-U", "127.0.0.1", "80"], 0x557fcb2047f0 /* 13 vars */) = -1 ENOENT (No such file or directory) +2783 execve("/usr/local/bin/tcpserver", ["tcpserver", "-U", "127.0.0.1", "80"], 0x557fcb2047f0 /* 13 vars */) = -1 ENOENT (No such file or directory) +2783 execve("/usr/local/sbin/tcpserver", ["tcpserver", "-U", "127.0.0.1", "80"], 0x557fcb2047f0 /* 13 vars */) = -1 ENOENT (No such file or directory) +2783 execve("/usr/bin/tcpserver", ["tcpserver", "-U", "127.0.0.1", "80"], 0x557fcb2047f0 /* 13 vars */) = 0 +2783 brk(NULL) = 0x5605f057b000 +2783 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) +2783 openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 +2783 fstat(3, {st_mode=S_IFREG|0644, st_size=129031, ...}) = 0 +2783 mmap(NULL, 129031, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9752158000 +2783 close(3) = 0 +2783 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9752156000 +2783 openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 +2783 read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\300\255\2\0\0\0\0\0@\0\0\0\0\0\0\08\264\301\0\0\0\0\0\0\0\0\0@\08\0\16\0@\0G\0F\0\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0\20\3\0\0\0\0\0\0\20\3\0\0\0\0\0\0\10\0\0\0\0\0\0\0\3\0\0\0\4\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\210t\2\0\0\0\0\0\210t\2\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\5\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0"..., 832) = 832 +2783 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0\20\3\0\0\0\0\0\0\20\3\0\0\0\0\0\0\10\0\0\0\0\0\0\0\3\0\0\0\4\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\210t\2\0\0\0\0\0\210t\2\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\5\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0\235\216\25\0\0\0\0\0\235\216\25\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\20\30\0\0\0\0\0\0\20\30\0\0\0\0\0\0\20\30\0\0\0\0\0"..., 784, 64) = 784 +2783 fstat(3, {st_mode=S_IFREG|0755, st_size=12699128, ...}) = 0 +2783 pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0\20\3\0\0\0\0\0\0\20\3\0\0\0\0\0\0\10\0\0\0\0\0\0\0\3\0\0\0\4\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0\240q\32\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0 \0\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\210t\2\0\0\0\0\0\210t\2\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\5\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0\0\200\2\0\0\0\0\0\235\216\25\0\0\0\0\0\235\216\25\0\0\0\0\0\0\20\0\0\0\0\0\0\1\0\0\0\4\0\0\0\0\20\30\0\0\0\0\0\0\20\30\0\0\0\0\0\0\20\30\0\0\0\0\0"..., 784, 64) = 784 +2783 mmap(NULL, 2006680, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f9751f6c000 +2783 mmap(0x7f9751f94000, 1413120, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x28000) = 0x7f9751f94000 +2783 mmap(0x7f97520ed000, 352256, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x181000) = 0x7f97520ed000 +2783 mmap(0x7f9752143000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d6000) = 0x7f9752143000 +2783 mmap(0x7f9752149000, 52888, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f9752149000 +2783 close(3) = 0 +2783 mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9751f69000 +2783 arch_prctl(ARCH_SET_FS, 0x7f9751f69740) = 0 +2783 set_tid_address(0x7f9751f69a10) = 2783 +2783 set_robust_list(0x7f9751f69a20, 24) = 0 +2783 rseq({cpu_id_start=0, cpu_id=RSEQ_CPU_ID_UNINITIALIZED, rseq_cs=NULL, flags=0, node_id=0, mm_cid=0, slice_ctrl={request=0, granted=0, __reserved=0}, __reserved=0}, 32, 0, 0x53053053) = 0 +2783 mprotect(0x7f9752143000, 16384, PROT_READ) = 0 +2783 mprotect(0x5605de88f000, 4096, PROT_READ) = 0 +2783 mprotect(0x7f97521b3000, 8192, PROT_READ) = 0 +2783 prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0 +2783 munmap(0x7f9752158000, 129031) = 0 +2783 write(2>, "tcpserver: usage: tcpserver [ -461UXpPhHrRoOdDqQv ] [ -c limit ] [ -x rules.cdb ] [ -B banner ] [ -g gid ] [ -u uid ] [ -b backlog ] [ -l localname ] [ -t timeout ] [ -I interface ] host port program\n", 200) = 200 +2783 exit_group(100) = ? +2783 +++ exited with 100 +++ diff --git a/example.service/usage b/example.service/usage new file mode 100644 index 0000000..5274ad7 --- /dev/null +++ b/example.service/usage @@ -0,0 +1 @@ +curl -H 'Authorization: Bearer ' http://localhost:80 diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..dd200a5 --- /dev/null +++ b/meson.build @@ -0,0 +1,28 @@ +project( + 'nobotgate', 'c', + default_options: [ + 'c_std=c99', + 'warning_level=2', + ], +) + +cc = meson.get_compiler('c') +deps = [ + cc.find_library('libowfat'), +] + +add_project_arguments( + '-DPATH_TMP="'+get_option('path_tmp')+'"', + language: 'c', +) + +executable('nobotgate', files( + './nobotgate.c', + './parse_token.c', + './check_token.c', + './send_headers.c', +), dependencies:deps, install:true) + +executable('nobotgate-access', files( + './nobotgate-access.c', +), dependencies:deps, install:true) diff --git a/meson.options b/meson.options new file mode 100644 index 0000000..e687354 --- /dev/null +++ b/meson.options @@ -0,0 +1,6 @@ +option( + 'path_tmp', + type : 'string', + value : '/tmp', + description : 'path to tmpfs', +) diff --git a/nobotgate-access.c b/nobotgate-access.c new file mode 100644 index 0000000..9472176 --- /dev/null +++ b/nobotgate-access.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +#define dieusage() die(100, argv0, " PROG ARGS"); + +static void consume_headers(void) { + char buf[2048]; + int r; + for (;;) { + r=buffer_getline(buffer_0, buf, sizeof buf); + if (r==0) die(100,"EOF before header end"); + if (buf[0]=='\r') + break; + } +} + +int main(int argc, char **argv) +{ + char *header; + (void)argc; + errmsg_iam(*argv); + + if (argc<2) dieusage(); + + consume_headers(); + + header = "HTTP/1.0 200 OK\r\n" + "Connection: close\r\n" + "\r\n"; + if (write(1, header, str_len(header)) < 0) + diesys(111, "write to stdout"); + execvp(argv[1], argv+1); + diesys(111, "exec ", argv[1]); +} diff --git a/nobotgate.c b/nobotgate.c new file mode 100644 index 0000000..ea279fe --- /dev/null +++ b/nobotgate.c @@ -0,0 +1,57 @@ +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include + +#include "nobotgate.h" + +#define dieusage() die(100,"usage: ",argv0," tokenfile allowed-handler denied-handler") + +static stralloc token; +static char *tokenfile, *cmd_valid, *cmd_invalid; + +static void run(void) { + char tmp[] = PATH_TMP"/nobotgate.XXXXXX"; + int tmpfd; + char buf[8192]; + buffer b; + char *cmd; + tmpfd = mkstemp(tmp); + + buffer_init_write(&b,tmpfd,buf,sizeof buf); + if (!send_headers(buffer_0, &b)) + die(111,"copy stdin to temporary file"); + + lseek(tmpfd,0,SEEK_SET); + buffer_init_read(&b,tmpfd,buf,sizeof buf); + if (!parse_token(&b,&token)) { + carp("failed to parse token"); + goto err; + } + + cmd=check_token(tokenfile,&token) + ? cmd_valid + : cmd_invalid; + lseek(tmpfd,0,SEEK_SET); + dup2(tmpfd,0); + unlink(tmp); + buffer_close(&b); + stralloc_free(&token); + carp("execing ",cmd); + execlp(cmd,cmd,NULL); +err: + unlink(tmp); + exit(111); +} + +int main(int argc,char **argv) +{ + errmsg_iam(*argv); + argv++; argc--; + if (argc != 3) dieusage(); + tokenfile=argv[0]; + cmd_valid=argv[1]; + cmd_invalid=argv[2]; + run(); +} diff --git a/nobotgate.h b/nobotgate.h new file mode 100644 index 0000000..8d9adc8 --- /dev/null +++ b/nobotgate.h @@ -0,0 +1,19 @@ +#ifndef NOBOTGATE +#define NOBOTGATE + +#include +#include + +/* buffer shall stream an http request. + * it stores the found bearer token to result or + * returns 0 if none is found. + * always succeeds. */ +int parse_token(buffer *b, stralloc *result); + +/* return: 1 if the token is valid(i.e. in tokenfile), + * 0 else */ +int check_token(char *tokenfile, stralloc *token); + +int send_headers(buffer *from, buffer *to); + +#endif diff --git a/parse_token.c b/parse_token.c new file mode 100644 index 0000000..24b6fcb --- /dev/null +++ b/parse_token.c @@ -0,0 +1,34 @@ +#include +#include +#include +#include + +#define AUTH_HEADER "Authorization: Bearer " +#define AUTH_HEADER_LEN str_len(AUTH_HEADER) + +// TODO make case insensitive according to https://www.rfc-editor.org/info/rfc7230/#section-3.2 +int parse_token(buffer *b, stralloc *result) +{ + stralloc line; + int r; + stralloc_init(result); + for (;;) { + r=buffer_getnewline_sa(b,&line); + if (r==0) break; + if (r<0) { carpsys("read line"); goto err; } + + if (stralloc_starts(&line,AUTH_HEADER)) { + stralloc_chomp(&line); + if (!stralloc_copyb(result, line.s+AUTH_HEADER_LEN, + line.len-AUTH_HEADER_LEN) + ) { carpsys("out of memory"); goto err; } + break; + } + } + + stralloc_free(&line); + return 1; +err: + stralloc_free(&line); + return 0; +} diff --git a/send_headers.c b/send_headers.c new file mode 100644 index 0000000..19db070 --- /dev/null +++ b/send_headers.c @@ -0,0 +1,27 @@ +#include +#include + +#include "nobotgate.h" + +int send_headers(buffer *from, buffer *to) +{ + char buf[8192]; + int nread; + for (;;) { + nread=buffer_getline(from,buf,sizeof buf); + if (nread==0) { carp("EOF before header end"); return 0; } + if (nread<0) { carpsys("failed reading"); return 0; } + /* include \n */ + nread++; + + if (buffer_put(to,buf,nread)<0) return 0; + if (buf[0]=='\r') + break; + } + + if (buffer_flush(to)<0) { + carpsys("buffer flush"); + return 0; + } + return 1; +} diff --git a/tool/debug b/tool/debug new file mode 100755 index 0000000..05c3111 --- /dev/null +++ b/tool/debug @@ -0,0 +1,5 @@ +#!/bin/sh +set -eux +gdb \ + -ex 'set args ./example.service/tokens ./example.service/on-valid ./example.service/on-invalid < ./example.input' \ + ./build/nobotgate