;;; -*- Mode:LISP; Package:MAIL; Base:10; Readtable:T; Lowercase:T -*- ;;; (c) 1986 Lisp Machine Incorporated ;;; Internet/SMTP interface to the Mailer (si:assure-system-patch-loaded "System Revision Level" 3 170) (si:assure-system-patch-loaded "Mailer" 2 2) (eval-when (compile load eval) ;;; I wouldn't do this if there were shorter nicknames... ;;; Have to do this because of basic problems in the QFASL/symbol package interactions (import (mapcar #'(lambda (name) (find-symbol name "TCP-APPLICATION")) '("TO" "FROM" "SMTP-REPLY" "SMTP-MAILER" "*SMTP-MAILER*" "SMTP-RCPT-VERIFIER" "SMTP-REPLY-OK" "SMTP-SERVER-STATE"))) ) ;;; Delivery (defun smtp-delivery-method (addresses plist text report-stream) (deliver-to-sorted-hosts "Internet SMTP" #'smtp-deliver-single-host addresses plist text report-stream)) (define-delivery-method smtp-mail (address) (foreign-network-address-p address :internet) (addresses plist text report-stream) (smtp-delivery-method addresses plist text report-stream)) (add-direct-mail-connected-network :internet) (defvar *smtp-deliver-debug-p* nil) (defun smtp-deliver-single-host (host recipients plist text report-stream &aux failed) (with-open-stream (u (ftp::make-smtp-user *SMTP-deliver-debug-p*)) (send u :connect (send host :name)) (unless (= 250 (nth-value 1 (send u :command "MAIL FROM:<%s>" (getf plist :from)))) (ferror "Cant accept mail from: ~S because ~A" (car (getf plist :from)) (send u :last-reply))) (dolist (r recipients) (unless (= 250 (nth-value 1 (send u :command "RCPT TO:%s" (foreign-mailer-address r)))) (push r failed) (format report-stream "Can't send to ~A: ~A" (foreign-original-address r) (send u :last-reply)))) (unless (= 354 (nth-value 1 (send u :command "DATA"))) (format report-stream "Can't send the mail text data because ~A" (send u :last-reply)) (return-from smtp-deliver-single-host recipients)) (send u :funcall-on-data-stream #'(lambda (stream) (write-string text stream))) (if (= 250 (nth-value 1 (send u :end-message))) failed (format report-stream "Some problem in ending message data: ~A" (send u :last-reply)) recipients))) ;;; SMTP server interface. (defun (:property :mailer smtp-rcpt-verifier) (original-string address-string stream) (declare (ignore original-string)) (multiple-value-bind (errorp result) (mailer-parse-address address-string) (if errorp (smtp-reply stream 501 "Bad address: ~A" result) (if (and (stringp result) (not (address-existent-p result))) (smtp-reply stream 550 "Unknown local address") (push result (smtp-server-state 'to)) (smtp-reply-ok stream))))) (defun smtp-warn-function (type stream &rest format-args) (apply #'smtp-reply stream (case type (:disk-full 452) (:random-error 451) (:append-error 550) (otherwise 451)) format-args)) (defun (:property :mailer smtp-mailer) (from-host data stream &aux qfile final-ok) (let ((*warn-function* 'smtp-warn-function)) (ignoring-errors (using-resource (text text-buffer (+ 100 (* (length data) 50.))) (macrolet ((line-out (line) `(string-nconc text ,line #\Newline))) (line-out (explicit-reception-line from-host "SMTP")) (dolist (l data) (line-out l)) ; ah, the luxuries of a large address space... (let ((result (write-queue-file (setq qfile (qfile-name)) (expand-addresses (smtp-server-state 'to)) `(:failures 0 :source-network-type :internet :source-protocol :smtp :from ,(second (smtp-server-state 'from)) :source-host ,from-host) text stream))) (cond ((errorp result) (setq final-ok ()) (if (probef qfile) (deletef qfile))) (t (setq final-ok :queued-ok) ;; ``Error'' replies are handled by WRITE-QUEUE-FILE (smtp-reply-ok stream)))))))) (when (and (eq final-ok :queued-ok) (not *delay-delivery*)) (mail-deliver-qfile qfile))) (defun smtp-use-mailer () (setq *smtp-mailer* :mailer)) (defun smtp-use-default () (setq *smtp-mailer* :simple))