summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitya Selivanov <automainint@guattari.tech>2025-02-22 17:07:12 +0100
committerMitya Selivanov <automainint@guattari.tech>2025-02-22 17:07:12 +0100
commit0242504a0eecb3543f47d6d6c4ef3e38ee322534 (patch)
tree3099a836891396fc5b3e1ed532e86778de2b13c7
parente3603be3dce9faa55c3d0785563b22b19adaf651 (diff)
downloadcgi-dev.zip
Knock-knock cookie (work in progress)HEADdev
-rw-r--r--Dockerfile24
-rw-r--r--bootstrap.sh285
-rwxr-xr-xmain.c171
-rw-r--r--site.conf68
4 files changed, 207 insertions, 341 deletions
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 15f1596..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-FROM nginx:alpine
-
-RUN apk add git-daemon cgit certbot certbot-nginx fcgiwrap apache2-utils gcc musl-dev
-RUN adduser -D -h /srv/git git
-
-WORKDIR /usr/build/
-COPY bootstrap.sh .
-COPY main.c .
-COPY static ./static
-
-RUN DEBIAN_FRONTEND=noninteractive sh bootstrap.sh
-
-RUN printf "\
- git daemon --reuseaddr --base-path=/srv/git/ /srv/git/ &\n\
- fcgiwrap -f &\n\
- nginx &\n\
- wait -n\n\
- exit $?\
-" >> /usr/local/bin/entrypoint
-
-# ssh, http, https, git
-EXPOSE 22 80 443 9418
-
-ENTRYPOINT [ "entrypoint" ]
diff --git a/bootstrap.sh b/bootstrap.sh
deleted file mode 100644
index b0e9eff..0000000
--- a/bootstrap.sh
+++ /dev/null
@@ -1,285 +0,0 @@
-echo "[ 1/9] Setting up Git"
-
-if ! id git >/dev/null 2>&1; then
- useradd -m -b /srv git
- [ $? -eq 0 ] || exit $?
-else
- echo "[SKIP] Git user already created."
-fi
-
-GIT_HOME=$( grep git /etc/passwd | cut -d: -f6 )
-
-if [ "$GIT_HOME" = "" ]; then
- echo "[ERROR] No git home!"
- exit 1
-fi
-
-echo "[INFO] Git home: $GIT_HOME"
-
-if ! command -v git >/dev/null 2>&1; then
- apt-get install -y git
- [ $? -eq 0 ] || exit $?
-else
- echo "[SKIP] Git already installed."
-fi
-
-if ls /usr/lib/git-core/git-http-backend >/dev/null 2>&1; then
- GIT_HTTP_BACKEND=/usr/lib/git-core/git-http-backend
-elif ls /usr/libexec/git-core/git-http-backend >/dev/null 2>&1; then
- GIT_HTTP_BACKEND=/usr/libexec/git-core/git-http-backend
-else
- echo "[ERROR] git-http-backend not found!"
- exit 1
-fi
-
-echo "[INFO] git-http-backend: $GIT_HTTP_BACKEND"
-
-echo "[ 2/9] Setting up cgit"
-
-if [ ! command -v /usr/share/webapps/cgit/cgit.cgi >/dev/null 2>&1 ] &&
- [ ! command -v /usr/lib/cgit/cgit.cgi >/dev/null 2>&1 ]; then
- apt-get install -y cgit
- [ $? -eq 0 ] || exit $?
-else
- echo "[SKIP] cgit already installed."
-fi
-
-if command -v /usr/share/webapps/cgit/cgit.cgi >/dev/null 2>&1; then
- CGIT_CGI=/usr/share/webapps/cgit/cgit.cgi
-elif command -v /usr/lib/cgit/cgit.cgi >/dev/null 2>&1; then
- CGIT_CGI=/usr/lib/cgit/cgit.cgi
-else
- echo "[ERROR] cgit not found!"
- exit 1
-fi
-
-echo "[INFO] Found cgit: $CGIT_CGI"
-
-echo "[ 3/9] Setting up nginx"
-
-if ! command -v nginx >/dev/null 2>&1; then
- apt-get install -y nginx
- [ $? -eq 0 ] || exit $?
-else
- echo "[SKIP] nginx already installed."
-fi
-
-echo "[ 4/9] Setting up certbot"
-
-if ! command -v certbot >/dev/null 2>&1; then
- apt-get install -y python3-certbot-nginx
- [ $? -eq 0 ] || exit $?
-else
- echo "[SKIP] certbot already installed."
-fi
-
-echo "[ 5/9] Setting up fcgiwrap"
-
-if ! command -v fcgiwrap >/dev/null 2>&1; then
- apt-get install -y fcgiwrap
- [ $? -eq 0 ] || exit $?
-else
- echo "[SKIP] fcgiwrap already installed."
-fi
-
-echo "[ 6/9] Setting up htpasswd"
-
-if ! command -v htpasswd >/dev/null 2>&1; then
- apt-get install -y apache2-utils
- [ $? -eq 0 ] || exit $?
-else
- echo "[SKIP] htpasswd already installed."
-fi
-
-echo "[ 7/9] Setting up GCC"
-
-if ! command -v gcc >/dev/null 2>&1; then
- apt-get install -y gcc
- [ $? -eq 0 ] || exit $?
-else
- echo "[SKIP] GCC already installed."
-fi
-
-echo "[ 8/9] Compiling and setting up CGI program"
-
-COMPILE="\
- -Wno-old-style-declaration \
- -Wno-missing-field-initializers -Wno-missing-braces \
- -Wall -Wextra -Werror -pedantic -mshstk \
- -O3 -o main main.c"
-
-SAN=-fsanitize=address,undefined,leak
-
-if gcc $SAN $COMPILE >/dev/null 2>&1; then
- gcc $SAN $COMPILE
- [ $? -eq 0 ] || exit $?
-else
- echo "[INFO] Sanitizers are disabled."
- gcc $COMPILE
- [ $? -eq 0 ] || exit $?
-fi
-
-chown root:root main
-[ $? -eq 0 ] || exit $?
-
-mv -f main /srv/
-[ $? -eq 0 ] || exit $?
-
-mv -f static /srv/static
-[ $? -eq 0 ] || exit $?
-
-echo "[ 9/9] Configuring"
-
-if [ ! -d /etc/nginx/sites-available ]; then
- mkdir /etc/nginx/sites-available
- [ $? -eq 0 ] || exit $?
-fi
-
-if [ ! -d /etc/nginx/sites-enabled ]; then
- mkdir /etc/nginx/sites-enabled
- [ $? -eq 0 ] || exit $?
-fi
-
-if [ ! -f /etc/nginx/sites-enabled/default ]; then
- ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default
- [ $? -eq 0 ] || exit $?
-fi
-
-cat <<EOF >/etc/nginx/sites-available/default
-server {
- listen 80;
- listen [::]:80;
-
- server_name _;
-
- location ~ ^/git_write/ {
- rewrite ^/git_write/(.*) /\$1 break;
-
- auth_basic "Git";
- auth_basic_user_file $GIT_HOME.htpasswd;
-
- include fastcgi_params;
- fastcgi_param SCRIPT_FILENAME $GIT_HTTP_BACKEND;
- fastcgi_param GIT_PROJECT_ROOT $GIT_HOME;
- fastcgi_param PATH_INFO \$uri;
- fastcgi_pass unix:/var/run/fcgiwrap.socket;
- }
-
- location ~ ^/git_read/ {
- rewrite ^/git_read/(.*) /\$1 break;
-
- include fastcgi_params;
- fastcgi_param SCRIPT_FILENAME $GIT_HTTP_BACKEND;
- fastcgi_param GIT_PROJECT_ROOT $GIT_HOME;
- fastcgi_param PATH_INFO \$uri;
- fastcgi_pass unix:/var/run/fcgiwrap.socket;
- }
-
- location ~ \\.git {
- if (\$arg_service = git-receive-pack) {
- rewrite /(.*) /git_write/\$1 last;
- }
- if (\$uri ~ ^/.*/git-receive-pack\$) {
- rewrite /(.*) /git_write/\$1 last;
- }
- if (\$arg_service = git-upload-pack) {
- rewrite /(.*) /git_read/\$1 last;
- }
- if (\$uri ~ ^/.*/git-upload-pack\$) {
- rewrite /(.*) /git_read/\$1 last;
- }
- }
-
- location ^~ /git/ {
- rewrite ^/git/(.*) /\$1 break;
- include fastcgi_params;
- fastcgi_param SCRIPT_FILENAME $CGIT_CGI;
- fastcgi_param PATH_INFO \$uri;
- fastcgi_param QUERY_STRING \$args;
- fastcgi_pass unix:/var/run/fcgiwrap.socket;
- }
-
- location ~* \\.(txt|asc|htm|css|svg|jpg|png|gif|ico|woff|woff2|js|wasm|mp3)\$ {
- rewrite ^/(.*) /static/plain/\$1 break;
- include fastcgi_params;
- fastcgi_param SCRIPT_FILENAME $CGIT_CGI;
- fastcgi_param PATH_INFO \$uri;
- fastcgi_param QUERY_STRING \$args;
- fastcgi_pass unix:/var/run/fcgiwrap.socket;
- }
-
- location / {
- include /etc/nginx/fastcgi_params;
- fastcgi_param SCRIPT_FILENAME /srv/main.cgi;
- fastcgi_pass unix:/var/run/fcgiwrap.socket;
- }
-}
-EOF
-[ $? -eq 0 ] || exit $?
-
-echo "[INFO] Written /etc/nginx/sites-available/default:"
-
-cat /etc/nginx/sites-available/default
-
-cat <<EOF >/etc/nginx/mime.types
-types {
- text/plain txt;
- text/plain asc;
- text/html htm;
- text/css css;
- image/svg+xml svg;
- image/jpeg jpg;
- image/png png;
- image/gif gif;
- image/x-icon ico;
- application/font-woff woff;
- application/font-woff2 woff2;
- application/javascript js;
- application/wasm wasm;
- audio/mpeg mp3;
-}
-EOF
-[ $? -eq 0 ] || exit $?
-
-echo "[INFO] Written /etc/nginx/mime.types:"
-
-cat /etc/nginx/mime.types
-
-cat <<EOF >/etc/nginx/nginx.conf
-user git;
-worker_processes 1;
-pid /run/nginx.pid;
-include /etc/nginx/modules-enabled/*.conf;
-
-events {
- worker_connections 768;
-}
-
-http {
- sendfile on;
- tcp_nopush on;
- types_hash_max_size 2048;
- server_names_hash_bucket_size 256;
-
- include /etc/nginx/mime.types;
- default_type application/octet-stream;
-
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
- ssl_prefer_server_ciphers on;
-
- access_log /var/log/nginx/access.log;
- error_log /var/log/nginx/error.log;
-
- gzip on;
-
- include /etc/nginx/conf.d/*.conf;
- include /etc/nginx/sites-enabled/*;
-}
-EOF
-[ $? -eq 0 ] || exit $?
-
-echo "[INFO] Written /etc/nginx/nginx.conf:"
-
-cat /etc/nginx/nginx.conf
-
-echo "[INFO] All done!"
diff --git a/main.c b/main.c
index 9949195..30500e4 100755
--- a/main.c
+++ b/main.c
@@ -13,29 +13,17 @@
#/ - No JavaScript
#/ - No external dependencies
#/ - No dynamic memory management
-#/ - Simple bootstrap script
-#/ - Not a single LLM-generated line of code
#/
-#/ NOTE
+#/ NOTE: At the start of this file you can see a shell script
+#/ that will compile and install the program into `/srv/`
#/
-#/ - At the start of this file you can see a shell script
-#/ that will compile and install the program into `/srv/`
+#/ To-Do list
#/
-#/ TODO
-#/
-#/ - CGI program
-#/ - Hierarchical tags.
-#/ - Blacklist and throttling.
-#/ - Wrap other CGI programs.
-#/ - CI terminal
-#/ - Convay's Game of Life with `<meta http-equiv="refresh" content="1" >`
-#/ - Bootstrap script
-#/ - ssh
-#/ - git config
-#/ - highlight
-#/ - tor, onion service
-#/ - daemons
-#/ - ufw
+#/ - Hierarchical tags.
+#/ - Blacklist and throttling.
+#/ - Redirects for cgit and git-http-backend.
+#/ - CI terminal.
+#/ - Convay's Game of Life with `<meta http-equiv="refresh" content="1" >`
#/
#/ ================================================================
@@ -86,6 +74,8 @@ exit $? # */
#define FORM_NAME "submit"
#define FORM_ITEM_DATA "data"
+#define CGI_CGIT "/usr/lib/cgit/cgit.cgi"
+
enum {
DEFAULT_SCALE = 100,
DEFAULT_WIDTH = 42,
@@ -508,7 +498,7 @@ static Redirect redirects[] = {
}, {
.alias = "/juliaset",
.url = "/static/plain/juliaset/index.htm",
- .cgi = "/usr/lib/cgit/cgit.cgi",
+ .cgi = CGI_CGIT,
.replace_headers = (Replace_Header[]) {
{ .name = "Content-Type", .value = "text/html; charset=UTF-8", },
{ .name = "X-Content-Type-Options", .value = NULL, },
@@ -2446,29 +2436,38 @@ i32 run_cgi(c8 const *cgi, c8 *const *argv, c8 *const *envp) {
signal(SIGCHLD, SIG_IGN);
signal(SIGALRM, SIG_IGN);
+ i32 pipe_in[2];
i32 pipe_out[2];
- if (pipe(pipe_out) == -1)
+ if (pipe(pipe_in) == -1)
return 1;
+ if (pipe(pipe_out) == -1)
+ return 2;
if (fcntl(pipe_out[1], F_SETFL, O_NONBLOCK) == -1)
- return 2;
+ return 3;
pid_t id = fork();
switch (id) {
case -1:
- return 3;
+ return 4;
case 0:
+ if (dup2(pipe_in[0], STDIN_FILENO) == -1)
+ return 5;
if (dup2(pipe_out[1], STDOUT_FILENO) == -1)
- return 4;
+ return 6;
+ close(pipe_in[0]);
+ close(pipe_in[1]);
close(pipe_out[0]);
close(pipe_out[1]);
execve(cgi, argv, envp);
- return 5;
+ // No return.
+
+ return 7;
default:;
}
@@ -2486,7 +2485,7 @@ i32 run_cgi(c8 const *cgi, c8 *const *argv, c8 *const *envp) {
i64 n = read(pipe_out[0], cgi_output, sizeof cgi_output - 1);
if (n <= 0)
- return 6;
+ return 8;
cgi_output[n] = '\0';
cgi_output_size = n;
@@ -2952,6 +2951,21 @@ b8 str_eq_with_slash(c8 const *a, c8 const *b) {
return 0;
}
+b8 str_eq_prefix(c8 const *a, c8 const *b) {
+ for (; *b != '\0'; ++a, ++b)
+ if (*a != *b)
+ return 0;
+ return 1;
+}
+
+b8 str_eq_postfix(c8 const *a, c8 const *b) {
+ i64 a_len = strlen(a);
+ i64 b_len = strlen(b);
+ if (a_len >= b_len)
+ return memcmp(a + (a_len - b_len), b, b_len) == 0;
+ return 0;
+}
+
i32 check_color(i32 c) {
if (c >= 0 && c < COLORS_MAX)
return c;
@@ -3002,6 +3016,44 @@ void skip_spaces(c8 const *s, i32 *n) {
}
}
+b8 knock_knock_cookie(c8 const *cookie) {
+ c8 knock_knock[] = "knock=knock";
+
+ {
+ c8 const *val = cookie;
+ c8 const *val_end = NULL;
+
+ while (*val != '\0') {
+ while (*val == ' ' || *val == ';') ++val;
+ val_end = val;
+ while (*val_end != ';' && *val_end != '\0') ++val_end;
+
+ if (memcmp(val, knock_knock, sizeof knock_knock - 1) == 0)
+ return 1;
+ }
+ }
+
+ printf("Status: 200 OK\r\n");
+ printf("Set-Cookie: %s; Path=/; SameSite=Lax; Expires=", knock_knock);
+ print_time_gmt(6 * SECONDS_IN_MONTH);
+ printf("; HttpOnly\r\n");
+ printf("Cross-Origin-Opener-Policy: same-origin\r\n");
+ printf("Cross-Origin-Embedder-Policy: require-corp\r\n");
+ printf("Content-Type: text/html\r\n");
+ printf("\r\n");
+
+ printf("<!DOCTYPE html><html><head>");
+ printf("<meta http-equiv=\"refresh\" content=\"1\" >");
+ printf("<meta charset='utf-8'>");
+ printf("<meta name='viewport' content='width=device-width,initial-scale=1'>");
+ printf("<link rel='icon' type='image/gif' href='/favicon.gif'>");
+ printf("<title> Knock-knock </title>");
+ printf("</head><body></body>");
+ printf("\r\n");
+
+ return 0;
+}
+
void setup_theme_cookie(c8 const *cookie, c8 const *query) {
// Parse cookie values
//
@@ -3225,7 +3277,7 @@ i32 main(i32 argc, c8 **argv) {
return 0;
}
- // TODO Check blacklist and throttling
+ // TODO: Check blacklist and throttling.
if (0) {
printf("Status: 400 Forbidden\r\n");
@@ -3237,6 +3289,8 @@ i32 main(i32 argc, c8 **argv) {
return 0;
}
+ // Redirects.
+
i32 doc = DOC_404;
for (i64 i = 0; i < (i64)(sizeof redirects / sizeof *redirects); ++i)
@@ -3341,6 +3395,61 @@ i32 main(i32 argc, c8 **argv) {
}
}
+ // Knock-knock.
+
+ if (!knock_knock_cookie(http_cookie))
+ return 0;
+
+ // cgit hooks.
+
+ if (str_eq_prefix(document_uri, "/git/")) {
+ static c8 buf_path [4096] = {0};
+ static c8 buf_query [4096] = {0};
+
+ snprintf(buf_path, sizeof buf_path - 1, "PATH_INFO=%s", document_uri + 4);
+ snprintf(buf_query, sizeof buf_query - 1, "QUERY_STRING=%s", query_string);
+
+ c8 *argv[] = { CGI_CGIT, NULL };
+ c8 *envp[] = { buf_path, buf_query, NULL };
+
+ i32 s = run_cgi(CGI_CGIT, argv, envp);
+ if (s != 0) {
+ printf("Status: 500 Internal Server Error %d\r\n", s);
+ return 0;
+ }
+
+ fflush(stdout);
+ i32 written = write(STDOUT_FILENO, cgi_output, cgi_output_size);
+ (void) written;
+
+ return 0;
+ }
+
+ if (str_eq_postfix(document_uri, ".git")) {
+ static c8 buf_path [4096] = {0};
+ static c8 buf_query [4096] = {0};
+
+ snprintf(buf_path, sizeof buf_path - 1, "PATH_INFO=%s", document_uri + 4);
+ snprintf(buf_query, sizeof buf_query - 1, "QUERY_STRING=%s", query_string);
+
+ c8 *argv[] = { CGI_CGIT, NULL };
+ c8 *envp[] = { buf_path, buf_query, NULL };
+
+ i32 s = run_cgi(CGI_CGIT, argv, envp);
+ if (s != 0) {
+ printf("Status: 500 Internal Server Error %d\r\n", s);
+ return 0;
+ }
+
+ fflush(stdout);
+ i32 written = write(STDOUT_FILENO, cgi_output, cgi_output_size);
+ (void) written;
+
+ return 0;
+ }
+
+ // Normal HTML gen.
+
for (i64 i = 0; i < (i64)(sizeof locs / sizeof *locs); ++i)
if (strcmp(request_method, locs[i].method) == 0 &&
str_eq_with_slash(document_uri, locs[i].uri)) {
@@ -3368,10 +3477,8 @@ i32 main(i32 argc, c8 **argv) {
printf("<!DOCTYPE html><html><head>");
{
printf("<meta charset='utf-8'>");
- printf("<meta name='viewport' "
- "content='width=device-width,initial-scale=1'>");
- printf(
- "<link rel='icon' type='image/gif' href='/favicon.gif'>");
+ printf("<meta name='viewport' content='width=device-width,initial-scale=1'>");
+ printf("<link rel='icon' type='image/gif' href='/favicon.gif'>");
printf("<title> %s </title>", docs[doc].title);
printf("<style>");
for (i64 i = 0; i < (i64)(sizeof fonts / sizeof *fonts); ++i)
diff --git a/site.conf b/site.conf
new file mode 100644
index 0000000..e4cda3c
--- /dev/null
+++ b/site.conf
@@ -0,0 +1,68 @@
+server {
+ listen 80;
+ listen [::]:80;
+
+ server_name _;
+
+ location ~ ^/git_write/ {
+ rewrite ^/git_write/(.*) /\$1 break;
+
+ auth_basic "Git";
+ auth_basic_user_file $GIT_HOME.htpasswd;
+
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $GIT_HTTP_BACKEND;
+ fastcgi_param GIT_PROJECT_ROOT $GIT_HOME;
+ fastcgi_param PATH_INFO \$uri;
+ fastcgi_pass unix:/var/run/fcgiwrap.socket;
+ }
+
+ location ~ ^/git_read/ {
+ rewrite ^/git_read/(.*) /\$1 break;
+
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $GIT_HTTP_BACKEND;
+ fastcgi_param GIT_PROJECT_ROOT $GIT_HOME;
+ fastcgi_param PATH_INFO \$uri;
+ fastcgi_pass unix:/var/run/fcgiwrap.socket;
+ }
+
+ location ~ \\.git {
+ if (\$arg_service = git-receive-pack) {
+ rewrite /(.*) /git_write/\$1 last;
+ }
+ if (\$uri ~ ^/.*/git-receive-pack\$) {
+ rewrite /(.*) /git_write/\$1 last;
+ }
+ if (\$arg_service = git-upload-pack) {
+ rewrite /(.*) /git_read/\$1 last;
+ }
+ if (\$uri ~ ^/.*/git-upload-pack\$) {
+ rewrite /(.*) /git_read/\$1 last;
+ }
+ }
+
+ location ^~ /git/ {
+ rewrite ^/git/(.*) /\$1 break;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $CGIT_CGI;
+ fastcgi_param PATH_INFO \$uri;
+ fastcgi_param QUERY_STRING \$args;
+ fastcgi_pass unix:/var/run/fcgiwrap.socket;
+ }
+
+ location ~* \\.(txt|asc|htm|css|svg|jpg|png|gif|ico|woff|woff2|js|wasm|mp3)\$ {
+ rewrite ^/(.*) /static/plain/\$1 break;
+ include fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME $CGIT_CGI;
+ fastcgi_param PATH_INFO \$uri;
+ fastcgi_param QUERY_STRING \$args;
+ fastcgi_pass unix:/var/run/fcgiwrap.socket;
+ }
+
+ location / {
+ include /etc/nginx/fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME /srv/main.cgi;
+ fastcgi_pass unix:/var/run/fcgiwrap.socket;
+ }
+}