git.fiddlerwoaroof.com
smtp.lisp
275a250a
 ;; -*- mode: common-lisp; package: net.post-office -*-
 ;;
 ;; smtp.cl
 ;;
 ;; copyright (c) 1986-2002 Franz Inc, Berkeley, CA  - All rights reserved.
32ad9729
 ;; copyright (c) 2002-2012 Franz Inc, Oakland, CA - All rights reserved.
275a250a
 ;;
 ;; This code is free software; you can redistribute it and/or
 ;; modify it under the terms of the version 2.1 of
3fa30fc6
 ;; the GNU Lesser General Public License as published by
275a250a
 ;; the Free Software Foundation, as clarified by the AllegroServe
 ;; prequel found in license-allegroserve.txt.
 ;;
 ;; This code is distributed in the hope that it will be useful,
 ;; but without any warranty; without even the implied warranty of
 ;; merchantability or fitness for a particular purpose.  See the GNU
 ;; Lesser General Public License for more details.
 ;;
3fa30fc6
 ;; Version 2.1 of the GNU Lesser General Public License is in the file
275a250a
 ;; license-lgpl.txt that was distributed with this file.
 ;; If it is not present, you can access it from
 ;; http://www.gnu.org/copyleft/lesser.txt (until superseded by a newer
3fa30fc6
 ;; version) or write to the Free Software Foundation, Inc., 59 Temple Place,
275a250a
 ;; Suite 330, Boston, MA  02111-1307  USA
 ;;
 ;;
 ;; $Id: smtp.cl,v 1.24 2008/09/16 23:22:14 layer Exp $
 
 ;; Description:
 ;;   send mail to an smtp server.  See rfc821 for the spec.
 
 ;;- This code in this file obeys the Lisp Coding Standard found in
 ;;- http://www.franz.com/~jkf/coding_standards.html
 ;;-
 
 
 (defpackage :net.post-office
86f4d8b2
   (:use #:cl #:excl)
3fa30fc6
   (:export
275a250a
    #:send-letter
    #:send-smtp
    #:send-smtp-auth
    #:test-email-address))
 
 (in-package :net.post-office)
 
 (eval-when (compile load eval)
   (require :streamp)
   (require :sasl)
   (require :mime))
 
 ;; the exported functions:
 
3fa30fc6
 ;; (send-letter "mail-server" "from" "to" "message"
f3fc8f1b
 ;;              &key cc bcc subject reply-to headers)
3fa30fc6
 ;;
 ;;
275a250a
 ;;    sends a message to the mail server (which may be a relay server
 ;;    or the final destination).  "from" is the address to be given
 ;;    as the sender.  "to" can be a string or a list of strings naming
3fa30fc6
 ;;    recipients.
275a250a
 ;;    "message" is the message to be sent.  It can be a string or a stream.
 ;;    cc and bcc can be either be a string or a  list of strings
f3fc8f1b
 ;;      naming recipients.  All cc's and bcc's are sent the message
 ;;      but the bcc's aren't included in the header created.
275a250a
 ;;    reply-to's value is a string and in cases a Reply-To header
f3fc8f1b
 ;;       to be created.
275a250a
 ;;    headers is a string or list of stings. These are raw header lines
f3fc8f1b
 ;;      added to the header build to send out.
275a250a
 ;;
3fa30fc6
 ;;    This builds a header and inserts the optional cc, bcc,
275a250a
 ;;    subject and reply-to  lines.
 ;;
 ;; (send-smtp "mail-server" "from" "to" &rest messages)
 ;;    this is like send-letter except that it doesn't build a header.
 ;;    the messages should contain a header (and if not then sendmail
 ;;    notices this and builds one -- other MTAs may not be that smart).
 ;;    The messages ia  list of strings or streams to be concatenated together
 ;;    and sent as one message
 ;;
 ;;
 ;;  (test-email-address "user@machine.com")
 ;;    return t is this could be a valid email address on the machine
 ;;    named.  Do this by contacting the mail server and using the VRFY
 ;;    command from smtp.  Since some mail servers don't implement VRFY
 ;;    we return t if VRFY doesn't work.
 ;;    nil means that this address is bad (or we can't make contact with
 ;;    the mail server, which could of course be a transient problem).
 ;;
 
 
 
 
 
 (defmacro response-case ((smtp-stream &optional smtp-response response-code) &rest case-clauses)
   ;; get a response from the smtp server and dispatch in a 'case' like
   ;; fashion to a clause based on the first digit of the return
   ;; code of the response.
   ;; smtp-response, if given, will be bound to string that is
   ;;  the actual response
3fa30fc6
   ;;
275a250a
   (let ((response-class (gensym)))
3fa30fc6
     `(multiple-value-bind (,response-class
f3fc8f1b
                            ,@(if* smtp-response then (list smtp-response))
                            ,@(if* response-code then (list response-code)))
          (progn (force-output ,smtp-stream)
                 (wait-for-response ,smtp-stream))
275a250a
        ;;(declare (ignorable smtp-response))
        (case ,response-class
f3fc8f1b
          ,@case-clauses))))
275a250a
 
 (defmacro smtp-send-recv ((smtp-stream cmd smtp-response &optional response-code) &rest case-clauses)
   (let ((stream (gensym))
f3fc8f1b
         (sent (gensym)))
275a250a
     `(let ((,stream ,smtp-stream)
f3fc8f1b
            (,sent ,cmd))
275a250a
        (if* *smtp-debug*
f3fc8f1b
           then (format *smtp-debug* "to smtp command: ~s~%" ,sent)
                (force-output *smtp-debug*))
275a250a
        (write-string ,sent ,stream)
        (write-char #\return ,stream)
        (write-char #\newline ,stream)
        (force-output ,stream)
        (macrolet ((smtp-transaction-error ()
f3fc8f1b
                     (list
                      'error
                      "SMTP transaction failed.  We said: ~s, and the server replied: ~s"
                      (quote ,sent)
                      (quote ,smtp-response))))
3fa30fc6
 
f3fc8f1b
          (response-case (,stream ,smtp-response ,response-code)
            ,@case-clauses)))))
275a250a
 
 (defvar *smtp-debug* nil)
 
 
 
 (defun send-letter (server from to message
f3fc8f1b
                     &key cc bcc subject reply-to headers
                          login password attachments)
275a250a
   ;;
   ;; see documentation at the head of this file
   ;;
3fa30fc6
 
275a250a
   (if* (mime-part-constructed-p message)
      then (if* (and (not (multipart-mixed-p message)) attachments)
f3fc8f1b
              then (error "~
275a250a
 attachments are not allowed for non-multipart/mixed messages."))
      else (let ((part
f3fc8f1b
                  (if* (streamp message)
                     then
                          (make-mime-part :file message)
                   elseif (stringp message)
                     then (make-mime-part :text message)
                     else (error "~
275a250a
 message must be a string, stream, or mime-part-constructed, not ~s" message))))
3fa30fc6
 
f3fc8f1b
             (setf message
               (if* attachments
                  then (make-mime-part :subparts (list part))
                  else part))))
3fa30fc6
 
275a250a
   (let ((hdrs nil)
f3fc8f1b
         (user-headers "")
         (tos (if* (stringp to)
                 then (list to)
               elseif (consp to)
                 then to
                 else (error "to should be a string or list, not ~s" to)))
         (ccs
          (if* (null cc)
             then nil
           elseif (stringp cc)
             then (list cc)
           elseif (consp cc)
             then cc
             else (error "cc should be a string or list, not ~s" cc)))
         (bccs (if* (null bcc)
                  then nil
                elseif (stringp bcc)
                  then (list bcc)
                elseif (consp bcc)
                  then bcc
                  else (error "bcc should be a string or list, not ~s" bcc))))
3fa30fc6
 
275a250a
     (setf hdrs
       (with-output-to-string (hdrs)
f3fc8f1b
         (macrolet ((already-have (name)
                      `(mime-get-header ,name message)))
3fa30fc6
 
f3fc8f1b
           ;; Give priority to headers already provided in a mime-part.
           (if* (not (already-have "From"))
              then (format hdrs "From: ~a~%" from))
3fa30fc6
 
f3fc8f1b
           (if* (not (already-have "To"))
              then (format hdrs "To: ~a~%" (list-to-delimited-string tos ", ")))
3fa30fc6
 
f3fc8f1b
           (if* (and ccs (not (already-have "Cc")))
              then (format hdrs "Cc: ~a~%" (list-to-delimited-string ccs ", ")))
3fa30fc6
 
f3fc8f1b
           (if* (and subject (not (already-have "Subject")))
              then (format hdrs "Subject: ~a~%" subject))
3fa30fc6
 
f3fc8f1b
           (if* (and reply-to (not (already-have "Reply-To")))
              then (format hdrs "Reply-To: ~a~%" reply-to)))))
3fa30fc6
 
275a250a
     (if* headers
        then (if* (stringp headers)
f3fc8f1b
                then (setq headers (list headers))
              elseif (consp headers)
                thenret
                else (error "Unknown headers format: ~s." headers))
             (setf user-headers
               (with-output-to-string (header)
                 (dolist (h headers)
                   (format header "~a~%" h)))))
275a250a
 
     ;; Temporarily modifies 'message', which may be user-provided.
     (let ((parts-save (mime-part-parts message)))
       (if* attachments
f3fc8f1b
          then (if (not (consp attachments))
                   (setf attachments (list attachments)))
 
               (let (res)
                 (dolist (attachment attachments)
                   (if* (mime-part-constructed-p attachment)
                      thenret
                    elseif (or (streamp attachment) (stringp attachment)
                               (pathnamep attachment))
                      then (setf attachment (make-mime-part :file attachment))
                      else (error "~
275a250a
 Attachments must be filenames, streams, or mime-part-constructed, not ~s"
f3fc8f1b
                                  attachment))
                   (push attachment res))
3fa30fc6
 
f3fc8f1b
                 (setf (mime-part-parts message) (append parts-save res))))
3fa30fc6
 
275a250a
       (with-mime-part-constructed-stream (s message)
f3fc8f1b
         (send-smtp-auth server from (append tos ccs bccs)
                         login password
                         hdrs
                         user-headers
                         s))
3fa30fc6
 
275a250a
       (setf (mime-part-parts message) parts-save)
       t)))
3fa30fc6
 
 
275a250a
 (defun send-smtp (server from to &rest messages)
   (send-smtp-1 server from to nil nil messages))
3fa30fc6
 
275a250a
 (defun send-smtp-auth (server from to login password &rest messages)
   (send-smtp-1 server from to login password messages))
 
 (defun send-smtp-1 (server from to login password messages
f3fc8f1b
                     &key (external-format :default))
275a250a
   ;; send the effective concatenation of the messages via
   ;; smtp to the mail server
   ;; Each message should be a string or a stream.
   ;;
   ;; 'to' can be a single string or a list of strings.
   ;; each string should be in the official rfc822 format  "foo@bar.com"
   ;;
 
   (let ((sock (connect-to-mail-server server login password)))
 
     (unwind-protect
f3fc8f1b
         (progn
 
           (smtp-send-recv (sock (format nil "MAIL from:<~a>" from) msg)
             (2 ;; cool
              nil
              )
             (t (smtp-transaction-error)))
 
           (let ((tos (if* (stringp to)
                         then (list to)
                       elseif (consp to)
                         then to
                         else (error "to should be a string or list, not ~s"
                                     to))))
             (dolist (to tos)
               (smtp-send-recv (sock (format nil "RCPT to:<~a>" to) msg)
                 (2 ;; cool
                  nil
                  )
                 (t (smtp-transaction-error)))))
 
           (smtp-send-recv (sock "DATA" msg)
             (3 ;; cool
              nil)
             (t (smtp-transaction-error)))
 
 
 
           (let ((at-bol t)
                 (prev-ch nil)
                 ch stream)
             (dolist (message messages)
               (when message
                 (setf stream (if* (streamp message)
                                 then message
                                 else (make-buffer-input-stream
                                       (string-to-octets
                                        message
                                        :null-terminate nil
                                        :external-format external-format))))
 
                 (while (setf ch (read-byte stream nil))
                   (if* (and at-bol (eq ch #.(char-code #\.)))
                      then ;; to prevent . from being interpreted as eol
                           (write-char #\. sock))
                   (if* (eq ch #.(char-code #\newline))
                      then (setq at-bol t)
                           (if* (not (eq prev-ch #.(char-code #\return)))
                              then (write-char #\return sock))
                      else (setq at-bol nil))
                   (write-byte ch sock)
                   (setq prev-ch ch)))))
 
           (write-char #\return sock) (write-char #\linefeed sock)
           (write-char #\. sock)
           (write-char #\return sock) (write-char #\linefeed sock)
 
           (response-case (sock msg)
             (2 nil ; (format t "Message sent to ~a~%" to)
                )
 
             (t (error "message not sent: ~s" msg)))
 
           (smtp-send-recv (sock "QUIT" msg)
             (2 ;; cool
              nil)
             (t (smtp-transaction-error))))
275a250a
       ;; Cleanup
       (close sock))))
 
 (defun connect-to-mail-server (server login password)
   ;; make that initial connection to the mail server
3fa30fc6
   ;; returning a socket connected to it and
275a250a
   ;; signaling an error if it can't be made.
3fa30fc6
 
275a250a
   (let ((use-port 25) ;; standard SMTP port
f3fc8f1b
         ssl-args
         ssl
         starttls)
275a250a
     (if* (consp server)
        then (if* (consp (cdr server))
f3fc8f1b
                then ;; long form
                     (setq ssl-args (cdr server))
                     (setf server (car server))
                     (setq ssl (getf ssl-args :ssl))
                     (remf ssl-args :ssl)
                     (setq use-port (or (getf ssl-args :port)
                                        (if ssl 465 use-port)))
                     (remf ssl-args :port)
                     (setq starttls (getf ssl-args :starttls))
                     (remf ssl-args :starttls)
                else ;; short form
                     (setf use-port (cdr server))
                     (setf server (car server)))
275a250a
      elseif (stringp server)
        then (multiple-value-bind (match whole m1 m2)
f3fc8f1b
                 (match-re "^([^:]+):([0-9]+)$" server)
               (declare (ignore whole))
               (if* match
                  then (setf server m1)
                       (setf use-port (parse-integer m2)))))
3fa30fc6
 
275a250a
     (let ((ipaddr (determine-mail-server server))
f3fc8f1b
           (sock)
           (ok))
3fa30fc6
 
275a250a
       (if* (null ipaddr)
f3fc8f1b
          then (error "Can't determine ip address for mail server ~s" server))
3fa30fc6
 
275a250a
       (setq sock (socket:make-socket :remote-host ipaddr
f3fc8f1b
                                      :remote-port use-port
                                      ))
275a250a
       (when ssl
f3fc8f1b
         (setq sock (apply #'acl-socket:make-ssl-client-stream sock ssl-args)))
3fa30fc6
 
275a250a
       (unwind-protect
f3fc8f1b
           (tagbody
             (response-case (sock msg)
                            (2 ;; to the initial connect
                             nil)
                            (t (error "initial connect failed: ~s" msg)))
             ehlo
             ;; now that we're connected we can compute our hostname
             (let ((hostname (socket:ipaddr-to-hostname
                              (socket:local-host sock))))
               (if* (null hostname)
                  then (setq hostname
                         (format nil "[~a]" (socket:ipaddr-to-dotted
                                             (socket:local-host sock)))))
               (let ((mechs (smtp-ehlo sock hostname))
                     auth-mechs)
                 (if* (and mechs starttls (member "STARTTLS" mechs :test #'string=))
                    then (smtp-send-recv (sock (format nil "STARTTLS") msg)
                                         (2 ;; ok
                                          (setq sock (acl-socket:make-ssl-client-stream sock :method :tlsv1)))
                                         (t (smtp-transaction-error)))
                         (go ehlo)
                  elseif (and mechs login password
                              (setq auth-mechs (car (member "LOGIN" mechs
                                                       :test #'(lambda (x y) (search x y))))))
                    then (setf sock
                           (smtp-authenticate sock server auth-mechs login password)))))
 
             ;; all is good
             (setq ok t))
 
         ;; cleanup:
         (if* (null ok)
            then (close sock :abort t)
                 (setq sock nil)))
3fa30fc6
 
275a250a
       ;; return:
       sock
       )))
3fa30fc6
 
275a250a
 
 ;; Returns string with mechanisms, or nil if none.
 ;; This may need to be expanded in the future as we support
 ;; more of the features that EHLO responds with.
 (defun smtp-ehlo (sock our-name)
   (smtp-send-recv (sock (format nil "EHLO ~A" our-name) msg)
     (2 ;; ok
      ;; Collect the auth mechanisms.
      (let (mechs)
        (multiple-value-bind (found whole mech)
f3fc8f1b
            (match-re "250[- ]AUTH (.*)" msg)
          (declare (ignore whole))
          (if found (push mech mechs)))
275a250a
        (multiple-value-bind (found whole mech)
f3fc8f1b
            (match-re "250[- ](STARTTLS)" msg)
          (declare (ignore whole))
          (if found (push mech mechs)))
275a250a
        mechs))
     (t
      (smtp-send-recv (sock (format nil "HELO ~A" our-name) msg)
        (2 ;; ok
f3fc8f1b
         nil)
275a250a
        (t (smtp-transaction-error))))))
 
 (defun smtp-authenticate (sock server mechs login password)
   (let ((ctx (net.sasl:sasl-client-new "smtp" server
f3fc8f1b
                                        :user login
                                        :pass password)))
275a250a
     (multiple-value-bind (res selected-mech response)
f3fc8f1b
         (net.sasl:sasl-client-start ctx mechs)
275a250a
       (if (not (eq res :continue))
f3fc8f1b
           (error "sasl-client-start unexpectedly returned: ~s" res))
275a250a
       (smtp-command sock "AUTH ~a" selected-mech)
       (loop
f3fc8f1b
         (response-case (sock msg)
           (3  ;; need more interaction
            (multiple-value-setq (res response)
              (net.sasl:sasl-step
               ctx
               (base64-string-to-usb8-array (subseq msg 4))))
            (smtp-command sock "~a"
                          (usb8-array-to-base64-string response nil)))
           (2 ;; server is satisfied.
            ;; Make sure the auth process really completed
            (if (not (net.sasl:sasl-conn-auth-complete-p ctx))
                (error "SMTP server indicated authentication complete before mechanisms was satisfied"))
            ;; It's all good.
            (return)) ;; break from loop
           (t
            (error "SMTP authentication failed: ~a" msg)))))
3fa30fc6
 
275a250a
     ;; Reach here if authentication completed.
     ;; If a security layer was negotiated, return an encapsulated sock,
     ;; otherwise just return the original sock.
     (if (net.sasl:sasl-conn-security-layer-p ctx)
f3fc8f1b
         (net.sasl:sasl-make-stream ctx sock :close-base t)
275a250a
       sock)))
 
3fa30fc6
 
 
275a250a
 (defun test-email-address (address)
   ;; test to see if we can determine if the address is valid
   ;; return nil if the address is bogus
   ;; return t if the address may or may not be bogus
   (if* (or (not (stringp address))
f3fc8f1b
            (zerop (length address)))
275a250a
      then (error "mail address should be a non-empty string: ~s" address))
3fa30fc6
 
275a250a
   ; split on the @ sign
   (let (name hostname)
     (let ((pos (position #\@ address)))
       (if* (null pos)
f3fc8f1b
          then (setq name address
                     hostname "localhost")
275a250a
        elseif (or (eql pos 0)
f3fc8f1b
                   (eql pos (1- (length address))))
          then ; @ at beginning or end, bogus since we don't do route addrs
               (return-from test-email-address nil)
          else (setq name (subseq address 0 pos)
                     hostname (subseq address (1+ pos)))))
3fa30fc6
 
275a250a
     (let ((sock (ignore-errors (connect-to-mail-server hostname nil nil))))
       (if* (null sock) then (return-from test-email-address nil))
3fa30fc6
 
275a250a
       (unwind-protect
f3fc8f1b
           (progn
             (smtp-send-recv (sock (format nil "VRFY ~a" name) msg code)
               (5
                (if* (eq code 550)
                   then ; no such user
                        msg ; to remove unused warning
                        nil
                   else ;; otherwise we don't know
                        (return-from test-email-address t)))
               (t (return-from test-email-address t)))
             (smtp-send-recv (sock (format nil "VRFY ~a" address) msg code)
               (5
                (if* (eq code 550)
                   then ; no such user
                        msg ; to remove unused warning
                        nil
                   else t))
               (t t)))
         (close sock :abort t)))))
275a250a
 
 
 
 
 
3fa30fc6
 
 
 
 
 
 
 
 
 
 
275a250a
 (defun wait-for-response (stream)
   ;; read the response of the smtp server.
   ;; collect it all in a string.
   ;; Return two values:
f3fc8f1b
   ;;    response class
275a250a
   ;;    whole string
   ;; The string should begin with a decimal digit, and that is converted
   ;; into a number which is returned as the response class.
   ;; If the string doesn't begin with a decimal digit then the
   ;; response class is -1.
   ;;
   (flet ((match-chars (string pos1 pos2 count)
f3fc8f1b
            ;; like strncmp
            (dotimes (i count t)
              (if* (not (eq (aref string (+ pos1 i))
                            (aref string (+ pos2 i))))
                 then (return nil)))))
275a250a
 
     (let ((res (make-array 20 :element-type 'character
f3fc8f1b
                            :adjustable t
                            :fill-pointer 0)))
275a250a
       (if* (null (read-a-line stream res))
f3fc8f1b
          then ; eof encountered before end of line
               (return-from wait-for-response (values -1 res)))
275a250a
 
       ;; a multi-line response begins with line containing
       ;; a hyphen in the 4th column:
       ;; xyz-  some text
       ;;
       ;;  and ends with a line containing the same reply code but no
       ;;  hyphen.
       ;; xyz  some text
       ;;
 
       (if* (and (>= (length res) 4) (eq #\- (aref res 3)))
f3fc8f1b
          then ;; multi line response
               (let ((old-length (length res))
                     (new-length nil))
                 (loop
                   (if* (null (read-a-line stream res))
                      then ; eof encountered before end of line
                           (return-from wait-for-response (values -1 res)))
                   (setq new-length (length res))
                   ;; see if this is the last line
                   (if* (and (>= (- new-length old-length) 4)
                             (eq (aref res (+ old-length 3)) #\space)
                             (match-chars res 0 old-length 3))
                      then (return))
 
                   (setq old-length new-length))))
275a250a
 
       ;; complete response is in res
       ;; compute class and return the whole thing
       (let ((class (or (and (> (length res) 0)
f3fc8f1b
                             (digit-char-p (aref res 0)))
                        -1)))
         (values class res
                 (if* (>= (length res) 3)
                    then ; compute the whole response value
                         (+ (* (or (digit-char-p (aref res 0)) 0) 100)
                            (* (or (digit-char-p (aref res 1)) 0) 10)
                            (or (digit-char-p (aref res 2)) 0))))))))
275a250a
 
 (defun smtp-command (stream &rest format-args)
   ;; send a command to the smtp server
   (let ((command (apply #'format nil format-args)))
     (if* *smtp-debug*
        then (format *smtp-debug* "to smtp command: ~s~%" command)
f3fc8f1b
             (force-output *smtp-debug*))
275a250a
     (write-string command stream)
     (write-char #\return stream)
     (write-char #\newline stream)
     (force-output stream)))
 
 (defun read-a-line (stream res)
   ;; read from stream and put the result in the adjust able array res
   ;; if line ends in cr-lf, only put a newline in res.
   ;; If we get an eof before the line finishes, return nil,
   ;; else return t if all is ok
   (let (ch last-ch)
     (loop
       (setq ch (read-char stream nil nil))
       (if* (null ch)
f3fc8f1b
          then ; premature eof
               (return nil))
275a250a
 
       (if* *smtp-debug*
f3fc8f1b
          then (format *smtp-debug* "~c" ch)
               (force-output *smtp-debug*)
               )
275a250a
 
       (if* (eq last-ch #\return)
f3fc8f1b
          then (if* (eq ch #\linefeed)
                  then (vector-push-extend #\newline res)
                       (return t)
                  else (vector-push-extend last-ch res))
275a250a
        elseif (eq ch #\linefeed)
f3fc8f1b
          then ; line ends with just lf, not cr-lf
               (vector-push-extend #\newline res)
               (return t)
275a250a
        elseif (not (eq ch #\return))
f3fc8f1b
          then (vector-push-extend ch res))
275a250a
 
       (setq last-ch ch))))
 
 (eval-when (compile eval)
   (defmacro ipaddrp (obj)
86f4d8b2
     ;; #+(version>= 8 0) `(socket:ipaddrp ,obj)
     ;; #-(version>= 8 0)
     `(and (integerp ,obj) (<= 0 ,obj #.(1- (expt 2 32)))))
275a250a
   )
 
 (defun determine-mail-server (name)
3fa30fc6
   ;; return the ipaddress to be used to connect to the
275a250a
   ;; the mail server.
   ;; name is any method for naming a machine:
   ;;   ip address (binary)
   ;;   string with dotted ip address
   ;;   string naming a domain
3fa30fc6
   ;; we can only do the mx lookup for the third case, the rest
275a250a
   ;; we just return the ipaddress for what we were given
   ;;
   (let (ipaddr)
     (if* (ipaddrp name)
        then name
      elseif (ipaddrp (setq ipaddr (socket:dotted-to-ipaddr name :errorp nil)))
        then ipaddr
        else ; do mx lookup if acldns is being used
f3fc8f1b
             (if* (or (eq socket:*dns-mode* :acldns)
                      (and (consp socket:*dns-mode*)
                           (member :acldns socket:*dns-mode* :test #'eq)))
                then (let ((res (socket:dns-query name :type :mx)))
                       (if* (and (consp res) (cadr res))
                          then (cadr res) ; the ip address
                          else (dolist (suffix socket::*domain-search-list*
                                         (socket:dns-lookup-hostname name))
                                 (declare (special socket:*domain-search-list*))
                                 (let ((name
                                        (concatenate 'string name "." suffix)))
                                   (setq res (socket:dns-query name :type :mx))
                                   (if* (and res (cadr res))
                                      then (return (cadr res)))))))
 
 
                else ; just do a hostname lookup
                     (ignore-errors (socket:lookup-hostname name))))))
3fa30fc6
 
 
 
275a250a
 (provide :smtp)