There's actually a huge botnet targeting /mona/. Something ludicrously oversized for a site with such a low traffic. 99.999999999% of visits are from spambots and crawlers.
SchemeBBS is often unattended for a long period of time, so /mona/ was shut down for some months before a quick fix was written leveraging ngx-auth-request-module
. The authentication server is written in Gerbil Scheme, it checks if the request origin is in a dnsbl-like blacklist and replies 403 if that happens to be the case.
location / {
...
auth_request /auth;
auth_request_set $auth_status $upstream_status;
}
#auth server
location /auth {
internal;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
if ($request_method = POST ) {
proxy_pass http://localhost:<port>/check?ip=$remote_addr;
}
if ($request_method = GET ) {
return 200;
}
}
Unfortunately, it makes it much harder to use a proxy for privacy-savvy users to post but real anonymous anons should really start using i2p. (an update to i2pd seems to have fixed the recurring segfaults and this home-cooked ``WAF'' is still better than routing everything through something evil like Cloudflare.
The authentication server, below. Gerbil and Gambit were a nightmare to port on FreeBSD and once again patches weren't shared upstream because having to register anywhere to post something is such a pain. Gerbil's included batteries allows to write server programs roughly as terses as their goland counterparts.
(import :std/net/httpd
:std/net/address
:std/text/json
:std/sugar
:std/iter
:std/getopt
:std/misc/alist
:std/net/request
:gerbil/gambit/threads
:std/misc/bytes)
(export main)
(define BLACKLIST "listed_ip_180.txt") ; fetch https://www.stopforumspam.com/downloads/listed_ip_180.zip
(define APIKEY "<get_your_own>")
(define banlist (make-hash-table-eq))
(define (params->alist p)
(map (lambda (x)
(let (l (string-split x #\=))
(cons (car l) (cadr l))))
(string-split p #\&)))
(define (ip->uint ip)
(u8vector->uint
(uint-list->u8vector
(map string->number (string-split ip #\.))
'little
1)))
(define (uint->ip n)
(string-join
(map number->string (u8vector->list (uint->u8vector n)))
#\.))
(define (readlines file)
(let ((infile (open-input-file file)))
(let loop ((lines '())
(next-line (read-line infile)))
(if (eof-object? next-line)
(begin (close-input-port infile)
(reverse lines))
(loop (cons next-line lines)
(read-line infile))))))
(define (is-banned? ip)
(hash-key? banlist (ip->uint ip)))
(define (in-abuseipdb? ip)
(let* ((json (http-get "http://api.abuseipdb.com/api/v2/check"
headers: `(("Key" . ,APIKEY)
("Accept" . "application/json"))
params: `(("ipAddress" . ,ip))))
(score (hash-get (hash-get (request-json json) 'data) 'abuseConfidenceScore)))
(if (> score 1)
(begin (hash-put! banlist (ip->uint ip) '()) #t)
#f)))
;;; This is basically the web server in gerbil/src/tutorial/httpd/
;;; (C) vyzo at hackzen.org
;;; insanely fast, you should use that for Scheme web applications
(define (run address)
(let (httpd (start-http-server! address mux: (make-default-http-mux default-handler)))
(http-register-handler httpd "/" root-handler)
(http-register-handler httpd "/check" check-handler)
(thread-join! httpd)))
;; /
(define (root-handler req res)
(http-response-write res 200 '(("Content-Type" . "text/plain")) "Ok"))
;; /check
(define (check-handler req res)
(let* ((params (params->alist (http-request-params req)))
(ip (cdr (assoc "ip" params))))
(if (or (is-banned? ip) (in-abuseipdb? ip))
(http-response-write res 403 '(("Content-Type" . "text/plain")) "banned")
(http-response-write res 200 '(("Content-Type" . "text/plain")) "not banned"))))
;; default
(define (default-handler req res)
(http-response-write res 404 '(("Content-Type" . "text/plain"))
"these aren't the droids you are looking for.\n"))
(def (main . args)
(for-each (lambda (x) (hash-put! banlist x '() ))
(map ip->uint (readlines BLACKLIST)))
(define gopt
(getopt (option 'address "-a" "--address"
help: "server address"
default: "127.0.0.1:<port>")))
(try
(let (opt (getopt-parse gopt args))
(run (hash-get opt 'address)))
(catch (getopt-error? exn)
(getopt-display-help exn "hellod" (current-error-port))
(exit 1))))