3c465b1a |
#+(version= 8 1)
(sys:defpatch "smtp" 1
"v1: add smtp support for ssl connections and STARTTLS negotiation."
:type :system
:post-loadable t)
#+(version= 8 0) ;; not current with latest sources
|
a6202327 |
(sys:defpatch "smtp" 5
|
31c28d94 |
"v1: send-letter w/attachments; send-smtp* can take streams;
|
db0eed6c |
v2: add :port argument to send-letter, send-smtp, send-smtp-auth;
|
2fe8c381 |
v3: fix incompatibility introduced in v2;
|
a6202327 |
v4: remove stray force-output of t;
v5: send-smtp-1: New external-format keyword arg."
|
aca0a611 |
:type :system
:post-loadable t)
|
3c465b1a |
#+(version= 7 0) ;; not current with latest sources
|
a6202327 |
(sys:defpatch "smtp" 5
|
31c28d94 |
"v2: send-letter w/attachments; send-smtp* can take streams;
|
db0eed6c |
v3: add :port argument to send-letter, send-smtp, send-smtp-auth;
|
a6202327 |
v4: fix incompatibility introduced in v3;
v5: rm stray force-output of t; send-smtp-1: New external-format keyword arg."
|
41ecdaf4 |
:type :system
:post-loadable t)
|
fae7eaa0 |
;; -*- mode: common-lisp; package: net.post-office -*-
|
a5c2fe04 |
;;
|
8559c73a |
;; smtp.cl
|
a5c2fe04 |
;;
|
eb76a3f2 |
;; copyright (c) 1986-2002 Franz Inc, Berkeley, CA - All rights reserved.
|
0067fbb4 |
;; copyright (c) 2002-2012 Franz Inc, Oakland, CA - All rights reserved.
|
8559c73a |
;;
;; This code is free software; you can redistribute it and/or
;; modify it under the terms of the version 2.1 of
;; the GNU Lesser General Public License as published by
;; 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.
;;
;; Version 2.1 of the GNU Lesser General Public License is in the file
;; 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
;; version) or write to the Free Software Foundation, Inc., 59 Temple Place,
;; Suite 330, Boston, MA 02111-1307 USA
;;
;;
|
3c465b1a |
;; $Id: smtp.cl,v 1.24 2008/09/16 23:22:14 layer Exp $
|
8559c73a |
;; 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
;;-
|
8dd85436 |
(defpackage :net.post-office
|
a5c2fe04 |
(:use #:lisp #:excl)
(:export
#:send-letter
|
fae7eaa0 |
#:send-smtp
|
ffc31da7 |
#:send-smtp-auth
|
fae7eaa0 |
#:test-email-address))
|
a5c2fe04 |
|
8dd85436 |
(in-package :net.post-office)
|
a5c2fe04 |
|
ffc31da7 |
(eval-when (compile load eval)
|
1a86640a |
(require :streamp)
(require :sasl)
(require :mime))
|
8559c73a |
|
a5c2fe04 |
;; the exported functions:
|
8559c73a |
;; (send-letter "mail-server" "from" "to" "message"
;; &key cc bcc subject reply-to headers)
;;
|
a5c2fe04 |
;;
;; 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
;; recipients.
|
f6cd6a72 |
;; "message" is the message to be sent. It can be a string or a stream.
|
8559c73a |
;; cc and bcc can be either be a string or a list of strings
;; naming recipients. All cc's and bcc's are sent the message
;; but the bcc's aren't included in the header created.
;; reply-to's value is a string and in cases a Reply-To header
;; to be created.
;; headers is a string or list of stings. These are raw header lines
;; added to the header build to send out.
;;
;; This builds a header and inserts the optional cc, bcc,
;; subject and reply-to lines.
|
a5c2fe04 |
;;
;; (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).
|
f6cd6a72 |
;; The messages ia list of strings or streams to be concatenated together
|
a5c2fe04 |
;; and sent as one message
;;
;;
|
fae7eaa0 |
;; (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).
;;
|
a5c2fe04 |
|
fae7eaa0 |
(defmacro response-case ((smtp-stream &optional smtp-response response-code) &rest case-clauses)
|
8559c73a |
;; 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
;;
|
a5c2fe04 |
(let ((response-class (gensym)))
|
fae7eaa0 |
`(multiple-value-bind (,response-class
,@(if* smtp-response then (list smtp-response))
,@(if* response-code then (list response-code)))
|
8559c73a |
(progn (force-output ,smtp-stream)
(wait-for-response ,smtp-stream))
;;(declare (ignorable smtp-response))
|
a5c2fe04 |
(case ,response-class
,@case-clauses))))
|
5bc10cea |
(defmacro smtp-send-recv ((smtp-stream cmd smtp-response &optional response-code) &rest case-clauses)
(let ((stream (gensym))
(sent (gensym)))
`(let ((,stream ,smtp-stream)
(,sent ,cmd))
(if* *smtp-debug*
then (format *smtp-debug* "to smtp command: ~s~%" ,sent)
(force-output *smtp-debug*))
(write-string ,sent ,stream)
(write-char #\return ,stream)
(write-char #\newline ,stream)
(force-output ,stream)
(macrolet ((smtp-transaction-error ()
(list
'error
"SMTP transaction failed. We said: ~s, and the server replied: ~s"
(quote ,sent)
(quote ,smtp-response))))
(response-case (,stream ,smtp-response ,response-code)
,@case-clauses)))))
|
a5c2fe04 |
(defvar *smtp-debug* nil)
(defun send-letter (server from to message
|
ffc31da7 |
&key cc bcc subject reply-to headers
|
f6cd6a72 |
login password attachments)
|
8559c73a |
;;
;; see documentation at the head of this file
;;
|
f6cd6a72 |
(if* (mime-part-constructed-p message)
then (if* (and (not (multipart-mixed-p message)) attachments)
then (error "~
attachments are not allowed for non-multipart/mixed messages."))
else (let ((part
(if* (streamp message)
then
(make-mime-part :file message)
elseif (stringp message)
then (make-mime-part :text message)
else (error "~
message must be a string, stream, or mime-part-constructed, not ~s" message))))
(setf message
(if* attachments
then (make-mime-part :subparts (list part))
else part))))
|
b45f6462 |
(let ((hdrs nil)
(user-headers "")
|
8559c73a |
(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))))
|
f6cd6a72 |
|
b45f6462 |
(setf hdrs
(with-output-to-string (hdrs)
(macrolet ((already-have (name)
`(mime-get-header ,name message)))
;; Give priority to headers already provided in a mime-part.
(if* (not (already-have "From"))
then (format hdrs "From: ~a~%" from))
(if* (not (already-have "To"))
then (format hdrs "To: ~a~%" (list-to-delimited-string tos ", ")))
(if* (and ccs (not (already-have "Cc")))
then (format hdrs "Cc: ~a~%" (list-to-delimited-string ccs ", ")))
(if* (and subject (not (already-have "Subject")))
then (format hdrs "Subject: ~a~%" subject))
(if* (and reply-to (not (already-have "Reply-To")))
then (format hdrs "Reply-To: ~a~%" reply-to)))))
|
a5c2fe04 |
|
8559c73a |
(if* headers
then (if* (stringp headers)
then (setq headers (list headers))
elseif (consp headers)
thenret
else (error "Unknown headers format: ~s." headers))
|
b45f6462 |
(setf user-headers
|
f6cd6a72 |
(with-output-to-string (header)
(dolist (h headers)
(format header "~a~%" h)))))
|
b45f6462 |
;; Temporarily modifies 'message', which may be user-provided.
(let ((parts-save (mime-part-parts message)))
(if* attachments
then (if (not (consp attachments))
(setf attachments (list attachments)))
|
f6cd6a72 |
|
b45f6462 |
(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 "~
|
f6cd6a72 |
Attachments must be filenames, streams, or mime-part-constructed, not ~s"
|
b45f6462 |
attachment))
(push attachment res))
(setf (mime-part-parts message) (append parts-save res))))
(with-mime-part-constructed-stream (s message)
(send-smtp-auth server from (append tos ccs bccs)
login password
hdrs
user-headers
s))
(setf (mime-part-parts message) parts-save)
t)))
|
a5c2fe04 |
|
f6cd6a72 |
(defun send-smtp (server from to &rest messages)
|
ffc31da7 |
(send-smtp-1 server from to nil nil messages))
|
a5c2fe04 |
|
ffc31da7 |
(defun send-smtp-auth (server from to login password &rest messages)
(send-smtp-1 server from to login password messages))
|
f6cd6a72 |
|
a6202327 |
(defun send-smtp-1 (server from to login password messages
&key (external-format :default))
|
a5c2fe04 |
;; send the effective concatenation of the messages via
;; smtp to the mail server
|
f6cd6a72 |
;; Each message should be a string or a stream.
|
a5c2fe04 |
;;
;; 'to' can be a single string or a list of strings.
;; each string should be in the official rfc822 format "foo@bar.com"
;;
|
fae7eaa0 |
|
ffc31da7 |
(let ((sock (connect-to-mail-server server login password)))
|
3c465b1a |
|
a5c2fe04 |
(unwind-protect
(progn
|
f6cd6a72 |
|
5bc10cea |
(smtp-send-recv (sock (format nil "MAIL from:<~a>" from) msg)
|
8559c73a |
(2 ;; cool
nil
)
|
5bc10cea |
(t (smtp-transaction-error)))
|
a5c2fe04 |
(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)
|
5bc10cea |
(smtp-send-recv (sock (format nil "RCPT to:<~a>" to) msg)
|
8559c73a |
(2 ;; cool
nil
)
|
5bc10cea |
(t (smtp-transaction-error)))))
|
a5c2fe04 |
|
5bc10cea |
(smtp-send-recv (sock "DATA" msg)
|
8559c73a |
(3 ;; cool
nil)
|
5bc10cea |
(t (smtp-transaction-error)))
|
8559c73a |
|
a5c2fe04 |
|
36ee99cb |
(let ((at-bol t)
|
f6cd6a72 |
(prev-ch nil)
ch stream)
|
a5c2fe04 |
(dolist (message messages)
|
db0eed6c |
(when message
(setf stream (if* (streamp message)
then message
|
a6202327 |
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)))))
|
a5c2fe04 |
(write-char #\return sock) (write-char #\linefeed sock)
(write-char #\. sock)
(write-char #\return sock) (write-char #\linefeed sock)
|
8559c73a |
(response-case (sock msg)
(2 nil ; (format t "Message sent to ~a~%" to)
)
|
a5c2fe04 |
|
8559c73a |
(t (error "message not sent: ~s" msg)))
|
a5c2fe04 |
|
5bc10cea |
(smtp-send-recv (sock "QUIT" msg)
|
8559c73a |
(2 ;; cool
nil)
|
5bc10cea |
(t (smtp-transaction-error))))
;; Cleanup
|
a5c2fe04 |
(close sock))))
|
ffc31da7 |
(defun connect-to-mail-server (server login password)
|
fae7eaa0 |
;; make that initial connection to the mail server
;; returning a socket connected to it and
;; signaling an error if it can't be made.
|
31c28d94 |
|
3c465b1a |
(let ((use-port 25) ;; standard SMTP port
ssl-args
ssl
starttls)
|
31c28d94 |
(if* (consp server)
|
3c465b1a |
then (if* (consp (cdr server))
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)))
|
31c28d94 |
elseif (stringp server)
then (multiple-value-bind (match whole m1 m2)
(match-re "^([^:]+):([0-9]+)$" server)
(declare (ignore whole))
(if* match
then (setf server m1)
|
3c465b1a |
(setf use-port (parse-integer m2)))))
|
fae7eaa0 |
|
31c28d94 |
(let ((ipaddr (determine-mail-server server))
(sock)
(ok))
(if* (null ipaddr)
then (error "Can't determine ip address for mail server ~s" server))
(setq sock (socket:make-socket :remote-host ipaddr
|
3c465b1a |
:remote-port use-port
|
31c28d94 |
))
|
3c465b1a |
(when ssl
(setq sock (apply #'acl-socket:make-ssl-client-stream sock ssl-args)))
|
31c28d94 |
(unwind-protect
|
3c465b1a |
(tagbody
|
31c28d94 |
(response-case (sock msg)
(2 ;; to the initial connect
nil)
(t (error "initial connect failed: ~s" msg)))
|
3c465b1a |
ehlo
|
31c28d94 |
;; 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)))))
|
3c465b1a |
(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)))))
|
fae7eaa0 |
|
31c28d94 |
;; all is good
(setq ok t))
|
fae7eaa0 |
|
31c28d94 |
;; cleanup:
(if* (null ok)
then (close sock :abort t)
(setq sock nil)))
|
fae7eaa0 |
|
31c28d94 |
;; return:
sock
)))
|
fae7eaa0 |
|
a5c2fe04 |
|
ffc31da7 |
;; 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)
|
5bc10cea |
(smtp-send-recv (sock (format nil "EHLO ~A" our-name) msg)
|
ffc31da7 |
(2 ;; ok
;; Collect the auth mechanisms.
|
3c465b1a |
(let (mechs)
(multiple-value-bind (found whole mech)
(match-re "250[- ]AUTH (.*)" msg)
(declare (ignore whole))
(if found (push mech mechs)))
(multiple-value-bind (found whole mech)
(match-re "250[- ](STARTTLS)" msg)
(declare (ignore whole))
(if found (push mech mechs)))
mechs))
|
ffc31da7 |
(t
|
5bc10cea |
(smtp-send-recv (sock (format nil "HELO ~A" our-name) msg)
|
ffc31da7 |
(2 ;; ok
nil)
|
5bc10cea |
(t (smtp-transaction-error))))))
|
ffc31da7 |
(defun smtp-authenticate (sock server mechs login password)
(let ((ctx (net.sasl:sasl-client-new "smtp" server
:user login
:pass password)))
(multiple-value-bind (res selected-mech response)
(net.sasl:sasl-client-start ctx mechs)
(if (not (eq res :continue))
(error "sasl-client-start unexpectedly returned: ~s" res))
(smtp-command sock "AUTH ~a" selected-mech)
(loop
(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))
|
5bc10cea |
(error "SMTP server indicated authentication complete before mechanisms was satisfied"))
|
ffc31da7 |
;; It's all good.
(return)) ;; break from loop
(t
|
5bc10cea |
(error "SMTP authentication failed: ~a" msg)))))
|
ffc31da7 |
;; 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)
(net.sasl:sasl-make-stream ctx sock :close-base t)
sock)))
|
fae7eaa0 |
(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))
(zerop (length address)))
then (error "mail address should be a non-empty string: ~s" address))
; split on the @ sign
(let (name hostname)
(let ((pos (position #\@ address)))
(if* (null pos)
then (setq name address
hostname "localhost")
elseif (or (eql pos 0)
(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)))))
|
ffc31da7 |
(let ((sock (ignore-errors (connect-to-mail-server hostname nil nil))))
|
fae7eaa0 |
(if* (null sock) then (return-from test-email-address nil))
(unwind-protect
(progn
|
5bc10cea |
(smtp-send-recv (sock (format nil "VRFY ~a" name) msg code)
|
fae7eaa0 |
(5
(if* (eq code 550)
then ; no such user
msg ; to remove unused warning
nil
|
ee0a4951 |
else ;; otherwise we don't know
(return-from test-email-address t)))
(t (return-from test-email-address t)))
|
5bc10cea |
(smtp-send-recv (sock (format nil "VRFY ~a" address) msg code)
|
ee0a4951 |
(5
(if* (eq code 550)
then ; no such user
msg ; to remove unused warning
nil
else t))
|
fae7eaa0 |
(t t)))
(close sock :abort t)))))
|
a5c2fe04 |
(defun wait-for-response (stream)
|
8559c73a |
;; read the response of the smtp server.
|
a5c2fe04 |
;; collect it all in a string.
;; Return two values:
;; response class
;; 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)
;; like strncmp
(dotimes (i count t)
(if* (not (eq (aref string (+ pos1 i))
(aref string (+ pos2 i))))
then (return nil)))))
(let ((res (make-array 20 :element-type 'character
:adjustable t
:fill-pointer 0)))
(if* (null (read-a-line stream res))
then ; eof encountered before end of line
(return-from wait-for-response (values -1 res)))
;; 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)))
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))))
;; complete response is in res
;; compute class and return the whole thing
(let ((class (or (and (> (length res) 0)
(digit-char-p (aref res 0)))
-1)))
|
fae7eaa0 |
(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))))))))
|
a5c2fe04 |
(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)
(force-output *smtp-debug*))
(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)
then ; premature eof
(return nil))
(if* *smtp-debug*
then (format *smtp-debug* "~c" ch)
(force-output *smtp-debug*)
)
(if* (eq last-ch #\return)
then (if* (eq ch #\linefeed)
then (vector-push-extend #\newline res)
(return t)
else (vector-push-extend last-ch res))
elseif (eq ch #\linefeed)
then ; line ends with just lf, not cr-lf
(vector-push-extend #\newline res)
(return t)
elseif (not (eq ch #\return))
then (vector-push-extend ch res))
(setq last-ch ch))))
|
8559c73a |
|
44ad8bd7 |
(eval-when (compile eval)
(defmacro ipaddrp (obj)
|
3d8d64c0 |
#+(version>= 8 0) `(socket:ipaddrp ,obj)
#-(version>= 8 0) `(and (integerp ,obj) (<= 0 ,obj #.(1- (expt 2 32)))))
|
44ad8bd7 |
)
|
fae7eaa0 |
(defun determine-mail-server (name)
;; return the ipaddress to be used to connect to the
;; the mail server.
;; name is any method for naming a machine:
|
31c28d94 |
;; ip address (binary)
|
fae7eaa0 |
;; string with dotted ip address
|
31c28d94 |
;; string naming a domain
|
fae7eaa0 |
;; we can only do the mx lookup for the third case, the rest
;; we just return the ipaddress for what we were given
;;
(let (ipaddr)
|
44ad8bd7 |
(if* (ipaddrp name)
|
fae7eaa0 |
then name
|
44ad8bd7 |
elseif (ipaddrp (setq ipaddr (socket:dotted-to-ipaddr name :errorp nil)))
|
fae7eaa0 |
then ipaddr
else ; do mx lookup if acldns is being used
(if* (or (eq socket:*dns-mode* :acldns)
|
ffc31da7 |
(and (consp socket:*dns-mode*)
(member :acldns socket:*dns-mode* :test #'eq)))
|
fae7eaa0 |
then (let ((res (socket:dns-query name :type :mx)))
|
9bcd0a7b |
(if* (and (consp res) (cadr res))
|
fae7eaa0 |
then (cadr res) ; the ip address
|
9bcd0a7b |
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)))))))
|
fae7eaa0 |
else ; just do a hostname lookup
(ignore-errors (socket:lookup-hostname name))))))
|
8559c73a |
(provide :smtp)
|