(ns ldap-nginx-plumber.main (:require [clojure.spec.alpha :as spec] [clojure.string :as str] [org.httpkit.server :as http] [clj-ldap.client :as ldap] [babashka.cli :as cli] [clojure.pprint :refer [pprint]]) (:import [java.util Base64] [java.nio.charset StandardCharsets]) (:gen-class)) (defn- port? [x] (and (nat-int? x) (<= 0 x 65535))) (def cli-spec {:spec {:port {:coerce :int :desc "Port to listen on" :alias :p :validate port? :require true} :base-dn {:coerce :string :desc "Base DN for LDAP searches" :require true} :ldap-host {:coerce :string :require true} :ldap-port {:coerce :int :validate port? :default 389} :bind-dn {:coerce :string :require true} :bind-password {:coerce :string :require true}}}) (def ^:dynamic *opts*) (defonce ldap-connection-pool (atom nil)) (defn- base64->utf8 [base64] (try (-> (.decode (Base64/getDecoder) base64) (String. StandardCharsets/UTF_8)) (catch java.lang.IllegalArgumentException _)) nil) (defn- response [status & {:as more}] (apply merge {:status status :headers {"Content-Type" "text/plain"}} more)) (defn- consultant-app [req] (printf "\n%s received request:\n" (.toString (java.util.Date.))) (pprint req) (try (if-let [[_ user pass] (some->> (get-in [req :headers] "Authorization") str/lower-case (re-matches #"basic ([a-zA-Z0-9+/=]+)") second base64->utf8 (re-matches #"([^:]+):(.*)"))] (response 200 :body "yay!") (response 401 :headers {"WWW-Authenticate" "Basic realm=\"Restricted\"" "Cache-Control" "no-cache"})) (catch Exception e (println "`consultant-app` threw an error:") (prn e) (response 500)))) (defonce consultant-server (atom nil)) (defn- stop-consultant! [] (when @consultant-server ;; Graceful shutdown: wait 100ms for existing requests to be finished. ;; :timeout is optional, when no timeout, stop immediately. (http/server-stop! @consultant-server {:timeout 100}) (reset! consultant-server nil))) (defn- start-consultant [& {:keys [port] :as opts}] (binding [*opts* opts] (if @consultant-server (throw (ex-info "Refusing to start the server whilst a previous lingers" {})) (reset! consultant-server (http/run-server #'consultant-app {:port port :legacy-return-value? false}))))) (defn- connect-to-ldap [& {:keys [ldap-host ldap-port bind-dn bind-password]}] (reset! ldap-connection-pool (or @ldap-connection-pool (ldap/connect {:host {:address ldap-host :port ldap-port} :max-connections 8 :bind-dn bind-dn :password bind-password})))) (defn- main* [& opts] (and (apply connect-to-ldap opts) (apply start-consultant opts))) (comment (let [ask (let [base-dn "dc=identify,dc=deertopia,dc=net"] (consultant-app {:port 8080 :ldap-host "192.168.68.79" :ldap-port 3890 :base-dn base-dn :bind-dn (str "uid=nginx-bind-user,ou=people," base-dn) :bind-password "secret123"}))] (ask {}))) (comment ; Start on :8080 (let [base-dn "dc=identify,dc=deertopia,dc=net"] (main* :port 8080 :ldap-host "192.168.68.79" :ldap-port 3890 :base-dn base-dn :bind-dn (str "uid=nginx-bind-user,ou=people," base-dn) :bind-password "secret123"))) (comment ; Shutdown (stop-consultant!)) (defn -main [& args] (main* (cli/parse-opts args cli-spec)))