diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2025-02-22 17:07:12 +0100 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2025-02-22 17:07:12 +0100 |
commit | 0242504a0eecb3543f47d6d6c4ef3e38ee322534 (patch) | |
tree | 3099a836891396fc5b3e1ed532e86778de2b13c7 | |
parent | e3603be3dce9faa55c3d0785563b22b19adaf651 (diff) | |
download | cgi-dev.zip |
-rw-r--r-- | Dockerfile | 24 | ||||
-rw-r--r-- | bootstrap.sh | 285 | ||||
-rwxr-xr-x | main.c | 171 | ||||
-rw-r--r-- | site.conf | 68 |
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!" @@ -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; + } +} |