diff --git a/examples/passwd-wrapper.scm b/examples/passwd-wrapper.scm new file mode 100755 index 0000000..fac5587 --- /dev/null +++ b/examples/passwd-wrapper.scm @@ -0,0 +1,236 @@ +#!/usr/local/bin/scsh \ +-lm ../scheme/packages.scm -o threads -o chat-package -o expect-package -e main -s +!# + +(define (assq/false key alist) + (let ((p (assq key alist))) + (and p (cdr p)))) + +;; *** password restrictions ***************************************** + +(define min-password-length 8) +(define max-password-length 32) +(define min-password-char-classes 2) + +(define known-char-sets + (list char-set:lower-case char-set:upper-case + char-set:digit char-set:punctuation)) + +(define all-char-sets + (cons (char-set-difference + char-set:full + (apply char-set-union known-char-sets)) + known-char-sets)) + +(define (count-char-classes pw) + (apply + (map (lambda (cs) + (if (string-fold + (lambda (c s) + (or s (char-set-contains? cs c))) + #f pw) + 1 0)) + all-char-sets))) + +(define (display-password-too-short) + (newline) + (display "Password too short (minimum length ") + (display min-password-length) + (display ")") + (newline)) + +(define (display-password-too-long) + (newline) + (display "Password too long (maximum length ") + (display max-password-length) + (display ")") + (newline)) + +(define (display-password-too-few-char-classes) + (display " +New password does not have enough character classes. +The character classes are: + - lower-case letters + - upper-case letters + - digits + - punctuation, and + - all other characters (e.g., control characters). + +Please choose a password with at least 2 character classes.") + (newline)) + +;; *** tty support *************************************************** + +;; read string without echoing it +;; optionals arguments: +;; prompt - a string to be display before reading (default none) +;; port - the port to read from (default current-input-port) +(define (read-password . args) + (let-optionals args ((prompt #f) + (port (current-input-port))) + (let* ((tty-before (tty-info port)) + (tty-sans-echo (copy-tty-info tty-before))) + (if prompt + (begin + (display prompt) + (force-output (current-output-port)))) + (set-tty-info:local-flags + tty-sans-echo (bitwise-and (tty-info:local-flags tty-sans-echo) + (bitwise-not ttyl/echo))) + (set-tty-info/now port tty-sans-echo) + (let ((password (read-line port))) + (set-tty-info/now port tty-before) + (flush-tty/both port) + password)))) + +;; *** supported machines ******************************************** + +;; TODO: cache the results + +(define (solaris-machine?) + (let ((systype (getenv "SYSTYPE"))) + (and systype (string=? systype "sun4x_58")))) + +(define (freebsd-machine?) + (let ((systype (getenv "SYSTYPE"))) + (and systype (string=? systype "i386_fbsd52")))) + +(define (linux-machine?) + (let ((systype (getenv "SYSTYPE"))) + (and systype (string=? systype "i386_linux24")))) + +(define (raise-unsupported-machine) + (display "I refuse to run on unsupported machines\n" + (current-error-port)) + (exit 10)) + +;; *** interface to yppasswd ***************************************** + +(define (yppasswd-program) + "/usr/bin/yppasswd") + +(define (yppasswd-prompts) + (map cons + '(old-prompt new-prompt retype-prompt wrong-message mismatch-message + success-message) + (cond + ((freebsd-machine?) '("Old Password:" + "New Password:" + "Retype New Password:" + "yppasswd: sorry" + "Mismatch; try again, EOF to quit." + #f)) + ((solaris-machine?) '("Enter existing login password:" + "New Password: " + "Re-enter new Password: " + "yppasswd: Sorry, wrong passwd" + "passwd(SYSTEM): They don't match." + "passwd: password successfully changed")) + ((linux-machine?) '("Please enter old password:" + "Please enter new password:" + "Please retype new password:" + "Sorry." + "Mismatch - password unchanged." + #f ;; TODO?? + ))))) + +(define (verify-yp-password old-pw) + (let ((prompts (yppasswd-prompts))) + (old-prompt (assq/false 'old prompts)) + (wrong (assq/false 'wrong-old prompts)) + ;; if prompted for new password, old one is correct + (correct (assq/false 'new prompts))) + (call/cc + (lambda (return) + (chat (spawn ,(yppasswd-program) (= 2 1)) + (chat-abort (rx ,(assq/false 'wrong-message prompts))) + (chat-monitor (lambda (event value) + (case event + ((eof timeout abort) (return #f))))) + + (look-for (assq/false 'old-prompt prompts)) + (send/cr old-pw) + (look-for (assq/false 'new-prompt prompts)) + ;; if we are prompted for the new pw, old one was correct + #t)))) + +(define (change-yp-password old-pw new-pw) + (let ((prompts (yppasswd-prompts)) + (success-message (assq/false 'success-message prompts))) + (call/cc + (lambda (return) + (chat (spawn ,(yppasswd-program (= 2 1))) + (chat-abort (rx (| ,(assq/false 'mismatch-message prompts) + ,(assq/false 'wrong-message prompts)))) + (chat-monitor (lambda (event value) + (case event + ((timeout abort) (return #f)) + ((eof) + (if success-message + (return #f) + (return #t)))))) + + (look-for (assq/false 'old-prompt prompts)) + (send old-pw) + (look-for (assq/false 'new-prompt prompts)) + (send new-pw) + (look-for (assq/false 'retype-prompt prompts)) + (send new-pw) + (if success-message + ;; if there's gonna be a success-message, wait for it. + (begin (look-for success-message) #t) + ;; else wait a bit for errors (#f) or eof (#t) + (begin (sleep 2000) #f))))))) + +;; *** all together ************************************************** + +(define (verify-old-password pw) + (verify-yp-password pw)) + +(define (change-password old-pw new-pw) + (change-yp-password old-pw new-pw)) + +(define (ask/check-old-password) + (let ((old-pw-prompt "Old password: ")) + (let lp ((old-pw #f)) + (let ((pw (read-password old-pw-prompt))) + (cond + ((verify-old-password pw) + pw) + (else + (display "Wrong password. Try again.") + (newline) + (lp (read-password old-pw-prompt)))))))) + +(define (ask-new-password) + (let ((new-pw-prompt-1 "New password: ") + (new-pw-prompt-2 "\nRetype new password: ") + (no-match "Passwords do not match. Retry.")) + (let lp ((pw #f)) + (if pw + (if (string=? pw (read-password new-pw-prompt-2)) + pw + (begin + (newline) + (display no-match) + (newline) + (lp #f))) + (let ((pw (read-password new-pw-prompt-1))) + (cond + ((< (string-length pw) min-password-length) + (display-password-too-short) + (lp #f)) + ((> (string-length pw) max-password-length) + (display-password-too-long) + (lp #f)) + ((< (count-char-classes pw) min-password-char-classes) + (display-password-too-few-char-classes) + (lp #f)) + (else + (lp pw)))))))) + +(define (main args) + (if (not (or (solaris-machine?) (freebsd-machine?))) + (raise-unsupported-machine)) + (let ((old-pw (ask/check-old-password)) + (new-pw (ask-new-password))) + (change-password old-pw new-pw)))