;;; -*- Mode:LISP; Package:user; Readtable:CL; Base:10 -*- ;;; ;;; utility for determining mail-routing for unix machines ;;; ;;; -dg 3/3/86 ;;; (defconst *local-mail-machines* `(,(si:parse-host "angel") ,(si:parse-host "cap") ,(si:parse-host "fred") ,(si:parse-host "lad"))) (defconst *valid-uucp-hosts* '(:|mitccc| :|ayesha|)) (defconst *outside-addresses-always-valid* t) (defconst *default-aliases-file-path* "/usr/lib/aliases") (defconst *default-passwd-file-path* "/etc/passwd") (defconst *host-mail-routing-alist* nil) (defconst *parse-address-check* t) (defun parse-random-address (string) (let ((current-start 0) uucp-addresses uucp-address-encountered outside-addresses outside-address-encountered local-address local-address-encountered name) (do ((loc (string-search-char #\! string current-start) (string-search-char #\! string current-start))) ((null loc)) (setq uucp-address-encountered t) (push (intern (nsubstring string current-start loc) pkg-keyword-package) uucp-addresses) (setq current-start (add1 loc))) (do ((loc (string-search-char #\% string current-start) (string-search-char #\% string current-start)) (first-time t nil)) ((null loc)) (setq outside-address-encountered t) (if first-time (setq name (intern (nsubstring string current-start loc) pkg-keyword-package)) (push (intern (nsubstring string current-start loc) pkg-keyword-package) outside-addresses)) (setq current-start (add1 loc))) (do ((loc (string-search-char #\@ string current-start) (string-search-char #\@ string current-start)) (first-time t nil)) ((null loc)) (setq local-address-encountered t) (if first-time (if outside-address-encountered (push (intern (nsubstring string current-start loc) pkg-keyword-package) outside-addresses) (setq name (intern (nsubstring string current-start loc) pkg-keyword-package))) (push (intern (nsubstring string current-start loc) pkg-keyword-package) outside-addresses)) (setq current-start (add1 loc))) (when local-address-encountered (setq local-address (nsubstring string current-start))) (when (null name) (setq name (nsubstring string current-start))) (let ((return-value (list :address :name (intern name pkg-keyword-package)))) (when local-address (rplacd (last return-value) (list :local (si:parse-host local-address)))) (when uucp-addresses (rplacd (last return-value) (list :uucp uucp-addresses))) (when outside-addresses (rplacd (last return-value) (list :outside (reverse outside-addresses)))) (when *parse-address-check* (let ((regenerated-address (generate-random-address return-value local-address))) (unless (string-equal string regenerated-address) (ferror nil "Regenerated address string does not match original...")))) return-value))) (defun generate-random-address (plist local-host-specified) (let ((string "")) (dolist (host (get plist :uucp)) (setq string (string-append host "!" string))) (setq string (string-append string (get plist :name))) (dolist (outside (get plist :outside)) (setq string (string-append string "%" outside))) (when local-host-specified (setq string (string-append string "@" local-host-specified))) string)) (defun parse-passwd-entry (line output-to) (let* ((name-end (string-search ":" line)) (name (nsubstring line 0 name-end)) (password-end (string-search ":" line (add1 name-end))) (password (nsubstring line (add1 name-end) password-end)) (account-end (string-search ":" line (add1 password-end))) (account (read-from-string line t nil :start (add1 password-end) :end account-end)) (group-end (string-search ":" line (add1 account-end))) (group (read-from-string line t nil :start (add1 account-end) :end group-end)) (full-name-end (string-search ":" line (add1 group-end))) (full-name (nsubstring line (add1 group-end) full-name-end)) (homedir-end (string-search ":" line (add1 full-name-end))) (homedir (nsubstring line (add1 full-name-end) homedir-end)) ;; for grunts (default-shell-path (nsubstring line (add1 homedir-end)))) password account group default-shell-path (when output-to (format output-to "~&~A {~A - [~d,~d]} ==> ~A" full-name name group account homedir)) (list (intern name pkg-keyword-package) :full-name full-name :homedir homedir ))) (defun parse-passwd-file (host &key (file-path *default-passwd-file-path*) output-to &aux return-list) (with-open-file (stream (fs:parse-pathname file-path host) :direction :input) (do-forever (multiple-value-bind (line eof?) (send stream :line-in) (when eof? (return-from parse-passwd-file (reverse return-list))) (if (char-equal (aref line 0) #\#) ;;; just in case there's a comment (when output-to (format output-to line)) (push (parse-passwd-entry line output-to) return-list)))))) (defun parse-alias-entry (string) (case (aref string 0) ((#\| #\") `(:program ,string)) (#\/ `(:path ,string)) (t (parse-random-address string)))) (defun parse-alias-from-stream (stream output-to) (do (line eof? finished?) (finished? finished?) (multiple-value-setq (line eof?) (send stream :line-in)) (cond (eof? (setq finished? :eof)) ((zerop (string-length line))) ((char-equal #\# (aref line 0)) (when output-to (format output-to "~&~A" line))) ((not (string-search-char #\: line)) (ferror nil "Alias first line invalid: \"~A\"" line)) (t (do* ((alias-name-end (string-search-char #\: line)) (alias-name (nsubstring line 0 alias-name-end)) (rest-of-line (string-trim '(#\ #\tab) (nsubstring line (add1 alias-name-end)))) (this-entry-begin 0) (this-entry-end-normal (string-search-char #\, rest-of-line this-entry-begin) (string-search-char #\, rest-of-line this-entry-begin)) this-entry collected-entries done?) (done? (setq finished? (cons (intern alias-name pkg-keyword-package) (reverse collected-entries)))) (if (not this-entry-end-normal) (let ((string (string-trim '(#\ #\tab) (nsubstring rest-of-line this-entry-begin)))) (if (zerop (string-length string)) (multiple-value-bind (next-line next-line-eof?) (send stream :line-in) (cond (next-line-eof? (ferror nil "End of file in the middle of alias ~S" alias-name)) ((string-search-char #\# next-line) (ferror nil "Comment between lines of alias ~S" alias-name)) ((zerop (length next-line)) (ferror nil "Empty line between line of alias ~s" alias-name)) ((not (memq (aref next-line 0) '(#\ #\tab))) (ferror nil "Continuation of alias ~S does not begin with whitespace." alias-name))) (setq rest-of-line (string-trim '(#\ #\tab) next-line) this-entry-begin 0)) (push (parse-alias-entry (nsubstring string 0 (or (string-search-char #\# string) (length string)))) collected-entries) (setq done? t))) (setq this-entry (string-trim '(#\ #\tab) (nsubstring rest-of-line this-entry-begin this-entry-end-normal))) (push (parse-alias-entry this-entry) collected-entries) (setq this-entry-begin (add1 this-entry-end-normal)))))))) (defun parse-aliases-file (host &key (file-path *default-aliases-file-path*) output-to &aux return-list) (with-open-file (stream (fs:parse-pathname file-path host) :direction :input) (do ((entry (parse-alias-from-stream stream output-to) (parse-alias-from-stream stream output-to))) ((eq entry :eof) (reverse return-list)) (push entry return-list)))) (defun get-user-forwarding-list (passwd-list host &rest return-list) (dolist (user passwd-list (reverse return-list)) (let ((pathname (fs:parse-pathname (string-append (get user :homedir) "/.forward") host))) (when (condition-case () (probef pathname) (fs:file-request-failure) ;probably access denied ) (with-open-file (forward-file pathname :direction :input) (do (line eof? result) (result (push result return-list)) (multiple-value-setq (line eof?) (send forward-file :line-in)) (setq line (string-trim '(#\ #\tab) line)) (cond (eof? (ferror nil "End of file processing .forward file for \"~A@~A\"." (car user) host)) ((zerop (length line))) ((eq (aref line 0) #\#)) (t (setq result (parse-random-address line)))))))))) (defun get-host-mail-routing-list (host) (let* ((true-host (si:parse-host host)) (passwd-plist (parse-passwd-file true-host)) (aliases-list (parse-aliases-file true-host)) (user-forwarding-list (get-user-forwarding-list passwd-plist true-host))) (dolist (user-forward user-forwarding-list) (let ((place (assq (get user-forward :name) passwd-plist))) (if (null place) (ferror nil "Found .forward file but no entry in passwd list for user ~A on host ~A." (car user-forward) host) (putprop place (cdr user-forward) :user-forwarding-entry)))) (dolist (alias aliases-list) (let ((place (assq (car alias) passwd-plist))) (if place (cond ((get place :user-forward-entry) (tv:beep) (format t "~&There is a .forward file and an alias entry for ~ user ~A on host ~A." (car alias) host)) ((get place :alias) (tv:beep) (format t "~&There are two alias entries for user ~A on host ~A" (car alias) host)) (t (putprop place (cdr alias) :alias))) (push (list (car alias) :alias (cdr alias)) passwd-plist)))) (sortcar passwd-plist 'string-lessp))) (defun setup-host-mail-routing-alist (&aux return-list) (dolist (host *local-mail-machines* (setq *host-mail-routing-alist* (reverse return-list))) (format t "~&Processing mail routing information for host: ~A" (si:parse-host host)) (push (cons (si:parse-host host) (get-host-mail-routing-list host)) return-list))) (defun uucp-addresses-valid (address) (or (null (get address :uucp)) (dolist (host (get address :uucp) t) (unless (memq host *valid-uucp-hosts*) (return-from uucp-addresses-valid nil))))) (defun local-address-valid (address) (or (null (get address :local)) (memq (get address :local) *local-mail-machines*))) (defun outside-address-valid (ignore) (or *outside-addresses-always-valid* (ferror nil "Can't check the validity of outside addresses yet."))) (defun check-address-validity (address name host) (ecase (car address) (:address (let ((valid? t)) (unless (local-address-valid address) (setq valid? nil) (format t "~&Host ~A (in \"~A\" definition on ~A) is not a local unix mail machine." (get address :local) name host)) (unless (uucp-addresses-valid address) (setq valid? nil) (format t "~&UUCP host ~A (in \"~A\" definition on ~A) is not a known uucp host." (get address :uucp) name host)) valid?)) (:path (condition-case (condition) (let ((found (probef (fs:parse-pathname (cadr address) host)))) (if found t (format t "~&File \"~A\" does not exist for alias \"~A\" on ~A" (cadr address) name host) nil)) (fs:file-request-failure (format t "~&File request failure checking path \"~A\" for alias \"~A\" on ~A" (cadr address) name host) nil))) (:program t))) (defun find-invalid-addresses (routing-alist &aux bad-list) (dolist (host-list routing-alist (reverse bad-list)) (dolist (dest (cdr host-list)) (dolist (address (get dest :alias)) (unless (check-address-validity address (car dest) (car host-list)) (push (cons (car dest) address) bad-list)))))) (defun get-alias-distribution (alias host) (assq (intern alias pkg-keyword-package) (cdr (assq (si:parse-host host) *host-mail-routing-alist*))))