fix: cap yubi candidates, pass password via stdin, explicit dir permissions
ober
4207ac84b52f6020e0995f55e7f9c10dc35381ec
--- a/recorder.ss +++ b/recorder.ss @@ -105,10 +105,18 @@ (def (ensure-jsh-logs-dir!) (let ((dir (string-append (or (getenv "HOME" #f) "/tmp") "/.jsh/logs"))) (unless (file-exists? dir) - (let ((parent (string-append (or (getenv "HOME" #f) "/tmp") "/.jsh"))) - (unless (file-exists? parent) - (with-catch (lambda (e) #f) (lambda () (mkdir parent))))) - (with-catch (lambda (e) #f) (lambda () (mkdir dir)))) + (let ((parent (string-append (or (getenv "HOME" #f) "/tmp") "/.jsh")) + ;; Recordings are sensitive: create ~/.jsh and ~/.jsh/logs with + ;; mode 0700 regardless of the user's umask. Temporarily set + ;; umask 077 so mkdir yields 0700 at creation, then restore it. + (saved-mask (ffi-umask #o077))) + (dynamic-wind + (lambda () #f) + (lambda () + (unless (file-exists? parent) + (with-catch (lambda (e) #f) (lambda () (mkdir parent)))) + (with-catch (lambda (e) #f) (lambda () (mkdir dir)))) + (lambda () (ffi-umask saved-mask))))) dir)) ;; Generate a default recording filename (encrypted .cast.enc). --- a/yubi.ss +++ b/yubi.ss @@ -3,6 +3,7 @@ (let* ([account-query "gitlab.tgsre.net"] [attempt-timeout-seconds 6] + [max-candidates 10000] [touch-markers '("touch" "press" "contact")] [setup-error-markers '("command not found" @@ -32,16 +33,17 @@ [else (loop (cdr chars) (cons (make-string 1 (car chars)) parts))]))) - (define (ykman-command candidate) + (define (ykman-command) (string-append "exec sh -c " (shell-quote (string-append + "IFS= read -r yubi_pass || exit 1; " "if ! command -v ykman >/dev/null 2>&1; then " "echo 'ykman: command not found' >&2; exit 127; fi; " "tmp=$(mktemp \"${TMPDIR:-/tmp}/yubi.XXXXXX\") || exit 1; " "trap 'rm -f \"$tmp\"' EXIT HUP INT TERM; " - "ykman oath accounts code -p \"$2\" \"$3\" >\"$tmp\" 2>&1 & " + "ykman oath accounts code -p \"$yubi_pass\" \"$2\" >\"$tmp\" 2>&1 & " "child=$!; " "elapsed=0; " "while kill -0 \"$child\" 2>/dev/null; do " @@ -59,18 +61,20 @@ " sh " (shell-quote (number->string attempt-timeout-seconds)) " " - (shell-quote candidate) - " " (shell-quote account-query))) (define (try-candidate candidate) (call-with-values (lambda () (open-process-ports - (ykman-command candidate) + (ykman-command) (buffer-mode block) (make-transcoder (utf-8-codec)))) (lambda (to-in from-out from-err pid) + ;; Pass the candidate password via stdin (not argv) so it never + ;; appears in the process table. + (display candidate to-in) + (newline to-in) (close-port to-in) (let* ([out (get-string-all from-out)] [err (get-string-all from-err)]) @@ -115,13 +119,13 @@ (let* ([positions (alphabetic-positions password)] [n (length positions)]) (let loop ([k 0] [acc '()]) - (if (> k n) + (if (or (> k n) (>= (length acc) max-candidates)) (reverse acc) - (loop (+ k 1) - (append - (map (lambda (combo) (flip-positions password combo)) - (combinations-of positions k)) - acc)))))) + (let* ([new (map (lambda (combo) (flip-positions password combo)) + (combinations-of positions k))] + [room (- max-candidates (length acc))] + [batch (if (> (length new) room) (take new room) new)]) + (loop (+ k 1) (append batch acc))))))) (define (read-secret prompt) (display prompt)