Browse code
Untabify smtp.lisp
Orivej Desh authored on 10/02/2012 19:09:02
Showing 1 changed files
Showing 1 changed files
... | ... |
@@ -52,7 +52,7 @@ |
52 | 52 |
;; the exported functions: |
53 | 53 |
|
54 | 54 |
;; (send-letter "mail-server" "from" "to" "message" |
55 |
-;; &key cc bcc subject reply-to headers) |
|
55 |
+;; &key cc bcc subject reply-to headers) |
|
56 | 56 |
;; |
57 | 57 |
;; |
58 | 58 |
;; sends a message to the mail server (which may be a relay server |
... | ... |
@@ -61,12 +61,12 @@ |
61 | 61 |
;; recipients. |
62 | 62 |
;; "message" is the message to be sent. It can be a string or a stream. |
63 | 63 |
;; cc and bcc can be either be a string or a list of strings |
64 |
-;; naming recipients. All cc's and bcc's are sent the message |
|
65 |
-;; but the bcc's aren't included in the header created. |
|
64 |
+;; naming recipients. All cc's and bcc's are sent the message |
|
65 |
+;; but the bcc's aren't included in the header created. |
|
66 | 66 |
;; reply-to's value is a string and in cases a Reply-To header |
67 |
-;; to be created. |
|
67 |
+;; to be created. |
|
68 | 68 |
;; headers is a string or list of stings. These are raw header lines |
69 |
-;; added to the header build to send out. |
|
69 |
+;; added to the header build to send out. |
|
70 | 70 |
;; |
71 | 71 |
;; This builds a header and inserts the optional cc, bcc, |
72 | 72 |
;; subject and reply-to lines. |
... | ... |
@@ -101,146 +101,146 @@ |
101 | 101 |
;; |
102 | 102 |
(let ((response-class (gensym))) |
103 | 103 |
`(multiple-value-bind (,response-class |
104 |
- ,@(if* smtp-response then (list smtp-response)) |
|
105 |
- ,@(if* response-code then (list response-code))) |
|
106 |
- (progn (force-output ,smtp-stream) |
|
107 |
- (wait-for-response ,smtp-stream)) |
|
104 |
+ ,@(if* smtp-response then (list smtp-response)) |
|
105 |
+ ,@(if* response-code then (list response-code))) |
|
106 |
+ (progn (force-output ,smtp-stream) |
|
107 |
+ (wait-for-response ,smtp-stream)) |
|
108 | 108 |
;;(declare (ignorable smtp-response)) |
109 | 109 |
(case ,response-class |
110 |
- ,@case-clauses)))) |
|
110 |
+ ,@case-clauses)))) |
|
111 | 111 |
|
112 | 112 |
(defmacro smtp-send-recv ((smtp-stream cmd smtp-response &optional response-code) &rest case-clauses) |
113 | 113 |
(let ((stream (gensym)) |
114 |
- (sent (gensym))) |
|
114 |
+ (sent (gensym))) |
|
115 | 115 |
`(let ((,stream ,smtp-stream) |
116 |
- (,sent ,cmd)) |
|
116 |
+ (,sent ,cmd)) |
|
117 | 117 |
(if* *smtp-debug* |
118 |
- then (format *smtp-debug* "to smtp command: ~s~%" ,sent) |
|
119 |
- (force-output *smtp-debug*)) |
|
118 |
+ then (format *smtp-debug* "to smtp command: ~s~%" ,sent) |
|
119 |
+ (force-output *smtp-debug*)) |
|
120 | 120 |
(write-string ,sent ,stream) |
121 | 121 |
(write-char #\return ,stream) |
122 | 122 |
(write-char #\newline ,stream) |
123 | 123 |
(force-output ,stream) |
124 | 124 |
(macrolet ((smtp-transaction-error () |
125 |
- (list |
|
126 |
- 'error |
|
127 |
- "SMTP transaction failed. We said: ~s, and the server replied: ~s" |
|
128 |
- (quote ,sent) |
|
129 |
- (quote ,smtp-response)))) |
|
125 |
+ (list |
|
126 |
+ 'error |
|
127 |
+ "SMTP transaction failed. We said: ~s, and the server replied: ~s" |
|
128 |
+ (quote ,sent) |
|
129 |
+ (quote ,smtp-response)))) |
|
130 | 130 |
|
131 |
- (response-case (,stream ,smtp-response ,response-code) |
|
132 |
- ,@case-clauses))))) |
|
131 |
+ (response-case (,stream ,smtp-response ,response-code) |
|
132 |
+ ,@case-clauses))))) |
|
133 | 133 |
|
134 | 134 |
(defvar *smtp-debug* nil) |
135 | 135 |
|
136 | 136 |
|
137 | 137 |
|
138 | 138 |
(defun send-letter (server from to message |
139 |
- &key cc bcc subject reply-to headers |
|
140 |
- login password attachments) |
|
139 |
+ &key cc bcc subject reply-to headers |
|
140 |
+ login password attachments) |
|
141 | 141 |
;; |
142 | 142 |
;; see documentation at the head of this file |
143 | 143 |
;; |
144 | 144 |
|
145 | 145 |
(if* (mime-part-constructed-p message) |
146 | 146 |
then (if* (and (not (multipart-mixed-p message)) attachments) |
147 |
- then (error "~ |
|
147 |
+ then (error "~ |
|
148 | 148 |
attachments are not allowed for non-multipart/mixed messages.")) |
149 | 149 |
else (let ((part |
150 |
- (if* (streamp message) |
|
151 |
- then |
|
152 |
- (make-mime-part :file message) |
|
153 |
- elseif (stringp message) |
|
154 |
- then (make-mime-part :text message) |
|
155 |
- else (error "~ |
|
150 |
+ (if* (streamp message) |
|
151 |
+ then |
|
152 |
+ (make-mime-part :file message) |
|
153 |
+ elseif (stringp message) |
|
154 |
+ then (make-mime-part :text message) |
|
155 |
+ else (error "~ |
|
156 | 156 |
message must be a string, stream, or mime-part-constructed, not ~s" message)))) |
157 | 157 |
|
158 |
- (setf message |
|
159 |
- (if* attachments |
|
160 |
- then (make-mime-part :subparts (list part)) |
|
161 |
- else part)))) |
|
158 |
+ (setf message |
|
159 |
+ (if* attachments |
|
160 |
+ then (make-mime-part :subparts (list part)) |
|
161 |
+ else part)))) |
|
162 | 162 |
|
163 | 163 |
(let ((hdrs nil) |
164 |
- (user-headers "") |
|
165 |
- (tos (if* (stringp to) |
|
166 |
- then (list to) |
|
167 |
- elseif (consp to) |
|
168 |
- then to |
|
169 |
- else (error "to should be a string or list, not ~s" to))) |
|
170 |
- (ccs |
|
171 |
- (if* (null cc) |
|
172 |
- then nil |
|
173 |
- elseif (stringp cc) |
|
174 |
- then (list cc) |
|
175 |
- elseif (consp cc) |
|
176 |
- then cc |
|
177 |
- else (error "cc should be a string or list, not ~s" cc))) |
|
178 |
- (bccs (if* (null bcc) |
|
179 |
- then nil |
|
180 |
- elseif (stringp bcc) |
|
181 |
- then (list bcc) |
|
182 |
- elseif (consp bcc) |
|
183 |
- then bcc |
|
184 |
- else (error "bcc should be a string or list, not ~s" bcc)))) |
|
164 |
+ (user-headers "") |
|
165 |
+ (tos (if* (stringp to) |
|
166 |
+ then (list to) |
|
167 |
+ elseif (consp to) |
|
168 |
+ then to |
|
169 |
+ else (error "to should be a string or list, not ~s" to))) |
|
170 |
+ (ccs |
|
171 |
+ (if* (null cc) |
|
172 |
+ then nil |
|
173 |
+ elseif (stringp cc) |
|
174 |
+ then (list cc) |
|
175 |
+ elseif (consp cc) |
|
176 |
+ then cc |
|
177 |
+ else (error "cc should be a string or list, not ~s" cc))) |
|
178 |
+ (bccs (if* (null bcc) |
|
179 |
+ then nil |
|
180 |
+ elseif (stringp bcc) |
|
181 |
+ then (list bcc) |
|
182 |
+ elseif (consp bcc) |
|
183 |
+ then bcc |
|
184 |
+ else (error "bcc should be a string or list, not ~s" bcc)))) |
|
185 | 185 |
|
186 | 186 |
(setf hdrs |
187 | 187 |
(with-output-to-string (hdrs) |
188 |
- (macrolet ((already-have (name) |
|
189 |
- `(mime-get-header ,name message))) |
|
188 |
+ (macrolet ((already-have (name) |
|
189 |
+ `(mime-get-header ,name message))) |
|
190 | 190 |
|
191 |
- ;; Give priority to headers already provided in a mime-part. |
|
192 |
- (if* (not (already-have "From")) |
|
193 |
- then (format hdrs "From: ~a~%" from)) |
|
191 |
+ ;; Give priority to headers already provided in a mime-part. |
|
192 |
+ (if* (not (already-have "From")) |
|
193 |
+ then (format hdrs "From: ~a~%" from)) |
|
194 | 194 |
|
195 |
- (if* (not (already-have "To")) |
|
196 |
- then (format hdrs "To: ~a~%" (list-to-delimited-string tos ", "))) |
|
195 |
+ (if* (not (already-have "To")) |
|
196 |
+ then (format hdrs "To: ~a~%" (list-to-delimited-string tos ", "))) |
|
197 | 197 |
|
198 |
- (if* (and ccs (not (already-have "Cc"))) |
|
199 |
- then (format hdrs "Cc: ~a~%" (list-to-delimited-string ccs ", "))) |
|
198 |
+ (if* (and ccs (not (already-have "Cc"))) |
|
199 |
+ then (format hdrs "Cc: ~a~%" (list-to-delimited-string ccs ", "))) |
|
200 | 200 |
|
201 |
- (if* (and subject (not (already-have "Subject"))) |
|
202 |
- then (format hdrs "Subject: ~a~%" subject)) |
|
201 |
+ (if* (and subject (not (already-have "Subject"))) |
|
202 |
+ then (format hdrs "Subject: ~a~%" subject)) |
|
203 | 203 |
|
204 |
- (if* (and reply-to (not (already-have "Reply-To"))) |
|
205 |
- then (format hdrs "Reply-To: ~a~%" reply-to))))) |
|
204 |
+ (if* (and reply-to (not (already-have "Reply-To"))) |
|
205 |
+ then (format hdrs "Reply-To: ~a~%" reply-to))))) |
|
206 | 206 |
|
207 | 207 |
(if* headers |
208 | 208 |
then (if* (stringp headers) |
209 |
- then (setq headers (list headers)) |
|
210 |
- elseif (consp headers) |
|
211 |
- thenret |
|
212 |
- else (error "Unknown headers format: ~s." headers)) |
|
213 |
- (setf user-headers |
|
214 |
- (with-output-to-string (header) |
|
215 |
- (dolist (h headers) |
|
216 |
- (format header "~a~%" h))))) |
|
209 |
+ then (setq headers (list headers)) |
|
210 |
+ elseif (consp headers) |
|
211 |
+ thenret |
|
212 |
+ else (error "Unknown headers format: ~s." headers)) |
|
213 |
+ (setf user-headers |
|
214 |
+ (with-output-to-string (header) |
|
215 |
+ (dolist (h headers) |
|
216 |
+ (format header "~a~%" h))))) |
|
217 | 217 |
|
218 | 218 |
;; Temporarily modifies 'message', which may be user-provided. |
219 | 219 |
(let ((parts-save (mime-part-parts message))) |
220 | 220 |
(if* attachments |
221 |
- then (if (not (consp attachments)) |
|
222 |
- (setf attachments (list attachments))) |
|
223 |
- |
|
224 |
- (let (res) |
|
225 |
- (dolist (attachment attachments) |
|
226 |
- (if* (mime-part-constructed-p attachment) |
|
227 |
- thenret |
|
228 |
- elseif (or (streamp attachment) (stringp attachment) |
|
229 |
- (pathnamep attachment)) |
|
230 |
- then (setf attachment (make-mime-part :file attachment)) |
|
231 |
- else (error "~ |
|
221 |
+ then (if (not (consp attachments)) |
|
222 |
+ (setf attachments (list attachments))) |
|
223 |
+ |
|
224 |
+ (let (res) |
|
225 |
+ (dolist (attachment attachments) |
|
226 |
+ (if* (mime-part-constructed-p attachment) |
|
227 |
+ thenret |
|
228 |
+ elseif (or (streamp attachment) (stringp attachment) |
|
229 |
+ (pathnamep attachment)) |
|
230 |
+ then (setf attachment (make-mime-part :file attachment)) |
|
231 |
+ else (error "~ |
|
232 | 232 |
Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
233 |
- attachment)) |
|
234 |
- (push attachment res)) |
|
233 |
+ attachment)) |
|
234 |
+ (push attachment res)) |
|
235 | 235 |
|
236 |
- (setf (mime-part-parts message) (append parts-save res)))) |
|
236 |
+ (setf (mime-part-parts message) (append parts-save res)))) |
|
237 | 237 |
|
238 | 238 |
(with-mime-part-constructed-stream (s message) |
239 |
- (send-smtp-auth server from (append tos ccs bccs) |
|
240 |
- login password |
|
241 |
- hdrs |
|
242 |
- user-headers |
|
243 |
- s)) |
|
239 |
+ (send-smtp-auth server from (append tos ccs bccs) |
|
240 |
+ login password |
|
241 |
+ hdrs |
|
242 |
+ user-headers |
|
243 |
+ s)) |
|
244 | 244 |
|
245 | 245 |
(setf (mime-part-parts message) parts-save) |
246 | 246 |
t))) |
... | ... |
@@ -253,7 +253,7 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
253 | 253 |
(send-smtp-1 server from to login password messages)) |
254 | 254 |
|
255 | 255 |
(defun send-smtp-1 (server from to login password messages |
256 |
- &key (external-format :default)) |
|
256 |
+ &key (external-format :default)) |
|
257 | 257 |
;; send the effective concatenation of the messages via |
258 | 258 |
;; smtp to the mail server |
259 | 259 |
;; Each message should be a string or a stream. |
... | ... |
@@ -265,73 +265,73 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
265 | 265 |
(let ((sock (connect-to-mail-server server login password))) |
266 | 266 |
|
267 | 267 |
(unwind-protect |
268 |
- (progn |
|
269 |
- |
|
270 |
- (smtp-send-recv (sock (format nil "MAIL from:<~a>" from) msg) |
|
271 |
- (2 ;; cool |
|
272 |
- nil |
|
273 |
- ) |
|
274 |
- (t (smtp-transaction-error))) |
|
275 |
- |
|
276 |
- (let ((tos (if* (stringp to) |
|
277 |
- then (list to) |
|
278 |
- elseif (consp to) |
|
279 |
- then to |
|
280 |
- else (error "to should be a string or list, not ~s" |
|
281 |
- to)))) |
|
282 |
- (dolist (to tos) |
|
283 |
- (smtp-send-recv (sock (format nil "RCPT to:<~a>" to) msg) |
|
284 |
- (2 ;; cool |
|
285 |
- nil |
|
286 |
- ) |
|
287 |
- (t (smtp-transaction-error))))) |
|
288 |
- |
|
289 |
- (smtp-send-recv (sock "DATA" msg) |
|
290 |
- (3 ;; cool |
|
291 |
- nil) |
|
292 |
- (t (smtp-transaction-error))) |
|
293 |
- |
|
294 |
- |
|
295 |
- |
|
296 |
- (let ((at-bol t) |
|
297 |
- (prev-ch nil) |
|
298 |
- ch stream) |
|
299 |
- (dolist (message messages) |
|
300 |
- (when message |
|
301 |
- (setf stream (if* (streamp message) |
|
302 |
- then message |
|
303 |
- else (make-buffer-input-stream |
|
304 |
- (string-to-octets |
|
305 |
- message |
|
306 |
- :null-terminate nil |
|
307 |
- :external-format external-format)))) |
|
308 |
- |
|
309 |
- (while (setf ch (read-byte stream nil)) |
|
310 |
- (if* (and at-bol (eq ch #.(char-code #\.))) |
|
311 |
- then ;; to prevent . from being interpreted as eol |
|
312 |
- (write-char #\. sock)) |
|
313 |
- (if* (eq ch #.(char-code #\newline)) |
|
314 |
- then (setq at-bol t) |
|
315 |
- (if* (not (eq prev-ch #.(char-code #\return))) |
|
316 |
- then (write-char #\return sock)) |
|
317 |
- else (setq at-bol nil)) |
|
318 |
- (write-byte ch sock) |
|
319 |
- (setq prev-ch ch))))) |
|
320 |
- |
|
321 |
- (write-char #\return sock) (write-char #\linefeed sock) |
|
322 |
- (write-char #\. sock) |
|
323 |
- (write-char #\return sock) (write-char #\linefeed sock) |
|
324 |
- |
|
325 |
- (response-case (sock msg) |
|
326 |
- (2 nil ; (format t "Message sent to ~a~%" to) |
|
327 |
- ) |
|
328 |
- |
|
329 |
- (t (error "message not sent: ~s" msg))) |
|
330 |
- |
|
331 |
- (smtp-send-recv (sock "QUIT" msg) |
|
332 |
- (2 ;; cool |
|
333 |
- nil) |
|
334 |
- (t (smtp-transaction-error)))) |
|
268 |
+ (progn |
|
269 |
+ |
|
270 |
+ (smtp-send-recv (sock (format nil "MAIL from:<~a>" from) msg) |
|
271 |
+ (2 ;; cool |
|
272 |
+ nil |
|
273 |
+ ) |
|
274 |
+ (t (smtp-transaction-error))) |
|
275 |
+ |
|
276 |
+ (let ((tos (if* (stringp to) |
|
277 |
+ then (list to) |
|
278 |
+ elseif (consp to) |
|
279 |
+ then to |
|
280 |
+ else (error "to should be a string or list, not ~s" |
|
281 |
+ to)))) |
|
282 |
+ (dolist (to tos) |
|
283 |
+ (smtp-send-recv (sock (format nil "RCPT to:<~a>" to) msg) |
|
284 |
+ (2 ;; cool |
|
285 |
+ nil |
|
286 |
+ ) |
|
287 |
+ (t (smtp-transaction-error))))) |
|
288 |
+ |
|
289 |
+ (smtp-send-recv (sock "DATA" msg) |
|
290 |
+ (3 ;; cool |
|
291 |
+ nil) |
|
292 |
+ (t (smtp-transaction-error))) |
|
293 |
+ |
|
294 |
+ |
|
295 |
+ |
|
296 |
+ (let ((at-bol t) |
|
297 |
+ (prev-ch nil) |
|
298 |
+ ch stream) |
|
299 |
+ (dolist (message messages) |
|
300 |
+ (when message |
|
301 |
+ (setf stream (if* (streamp message) |
|
302 |
+ then message |
|
303 |
+ else (make-buffer-input-stream |
|
304 |
+ (string-to-octets |
|
305 |
+ message |
|
306 |
+ :null-terminate nil |
|
307 |
+ :external-format external-format)))) |
|
308 |
+ |
|
309 |
+ (while (setf ch (read-byte stream nil)) |
|
310 |
+ (if* (and at-bol (eq ch #.(char-code #\.))) |
|
311 |
+ then ;; to prevent . from being interpreted as eol |
|
312 |
+ (write-char #\. sock)) |
|
313 |
+ (if* (eq ch #.(char-code #\newline)) |
|
314 |
+ then (setq at-bol t) |
|
315 |
+ (if* (not (eq prev-ch #.(char-code #\return))) |
|
316 |
+ then (write-char #\return sock)) |
|
317 |
+ else (setq at-bol nil)) |
|
318 |
+ (write-byte ch sock) |
|
319 |
+ (setq prev-ch ch))))) |
|
320 |
+ |
|
321 |
+ (write-char #\return sock) (write-char #\linefeed sock) |
|
322 |
+ (write-char #\. sock) |
|
323 |
+ (write-char #\return sock) (write-char #\linefeed sock) |
|
324 |
+ |
|
325 |
+ (response-case (sock msg) |
|
326 |
+ (2 nil ; (format t "Message sent to ~a~%" to) |
|
327 |
+ ) |
|
328 |
+ |
|
329 |
+ (t (error "message not sent: ~s" msg))) |
|
330 |
+ |
|
331 |
+ (smtp-send-recv (sock "QUIT" msg) |
|
332 |
+ (2 ;; cool |
|
333 |
+ nil) |
|
334 |
+ (t (smtp-transaction-error)))) |
|
335 | 335 |
;; Cleanup |
336 | 336 |
(close sock)))) |
337 | 337 |
|
... | ... |
@@ -341,80 +341,80 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
341 | 341 |
;; signaling an error if it can't be made. |
342 | 342 |
|
343 | 343 |
(let ((use-port 25) ;; standard SMTP port |
344 |
- ssl-args |
|
345 |
- ssl |
|
346 |
- starttls) |
|
344 |
+ ssl-args |
|
345 |
+ ssl |
|
346 |
+ starttls) |
|
347 | 347 |
(if* (consp server) |
348 | 348 |
then (if* (consp (cdr server)) |
349 |
- then ;; long form |
|
350 |
- (setq ssl-args (cdr server)) |
|
351 |
- (setf server (car server)) |
|
352 |
- (setq ssl (getf ssl-args :ssl)) |
|
353 |
- (remf ssl-args :ssl) |
|
354 |
- (setq use-port (or (getf ssl-args :port) |
|
355 |
- (if ssl 465 use-port))) |
|
356 |
- (remf ssl-args :port) |
|
357 |
- (setq starttls (getf ssl-args :starttls)) |
|
358 |
- (remf ssl-args :starttls) |
|
359 |
- else ;; short form |
|
360 |
- (setf use-port (cdr server)) |
|
361 |
- (setf server (car server))) |
|
349 |
+ then ;; long form |
|
350 |
+ (setq ssl-args (cdr server)) |
|
351 |
+ (setf server (car server)) |
|
352 |
+ (setq ssl (getf ssl-args :ssl)) |
|
353 |
+ (remf ssl-args :ssl) |
|
354 |
+ (setq use-port (or (getf ssl-args :port) |
|
355 |
+ (if ssl 465 use-port))) |
|
356 |
+ (remf ssl-args :port) |
|
357 |
+ (setq starttls (getf ssl-args :starttls)) |
|
358 |
+ (remf ssl-args :starttls) |
|
359 |
+ else ;; short form |
|
360 |
+ (setf use-port (cdr server)) |
|
361 |
+ (setf server (car server))) |
|
362 | 362 |
elseif (stringp server) |
363 | 363 |
then (multiple-value-bind (match whole m1 m2) |
364 |
- (match-re "^([^:]+):([0-9]+)$" server) |
|
365 |
- (declare (ignore whole)) |
|
366 |
- (if* match |
|
367 |
- then (setf server m1) |
|
368 |
- (setf use-port (parse-integer m2))))) |
|
364 |
+ (match-re "^([^:]+):([0-9]+)$" server) |
|
365 |
+ (declare (ignore whole)) |
|
366 |
+ (if* match |
|
367 |
+ then (setf server m1) |
|
368 |
+ (setf use-port (parse-integer m2))))) |
|
369 | 369 |
|
370 | 370 |
(let ((ipaddr (determine-mail-server server)) |
371 |
- (sock) |
|
372 |
- (ok)) |
|
371 |
+ (sock) |
|
372 |
+ (ok)) |
|
373 | 373 |
|
374 | 374 |
(if* (null ipaddr) |
375 |
- then (error "Can't determine ip address for mail server ~s" server)) |
|
375 |
+ then (error "Can't determine ip address for mail server ~s" server)) |
|
376 | 376 |
|
377 | 377 |
(setq sock (socket:make-socket :remote-host ipaddr |
378 |
- :remote-port use-port |
|
379 |
- )) |
|
378 |
+ :remote-port use-port |
|
379 |
+ )) |
|
380 | 380 |
(when ssl |
381 |
- (setq sock (apply #'acl-socket:make-ssl-client-stream sock ssl-args))) |
|
381 |
+ (setq sock (apply #'acl-socket:make-ssl-client-stream sock ssl-args))) |
|
382 | 382 |
|
383 | 383 |
(unwind-protect |
384 |
- (tagbody |
|
385 |
- (response-case (sock msg) |
|
386 |
- (2 ;; to the initial connect |
|
387 |
- nil) |
|
388 |
- (t (error "initial connect failed: ~s" msg))) |
|
389 |
- ehlo |
|
390 |
- ;; now that we're connected we can compute our hostname |
|
391 |
- (let ((hostname (socket:ipaddr-to-hostname |
|
392 |
- (socket:local-host sock)))) |
|
393 |
- (if* (null hostname) |
|
394 |
- then (setq hostname |
|
395 |
- (format nil "[~a]" (socket:ipaddr-to-dotted |
|
396 |
- (socket:local-host sock))))) |
|
397 |
- (let ((mechs (smtp-ehlo sock hostname)) |
|
398 |
- auth-mechs) |
|
399 |
- (if* (and mechs starttls (member "STARTTLS" mechs :test #'string=)) |
|
400 |
- then (smtp-send-recv (sock (format nil "STARTTLS") msg) |
|
401 |
- (2 ;; ok |
|
402 |
- (setq sock (acl-socket:make-ssl-client-stream sock :method :tlsv1))) |
|
403 |
- (t (smtp-transaction-error))) |
|
404 |
- (go ehlo) |
|
405 |
- elseif (and mechs login password |
|
406 |
- (setq auth-mechs (car (member "LOGIN" mechs |
|
407 |
- :test #'(lambda (x y) (search x y)))))) |
|
408 |
- then (setf sock |
|
409 |
- (smtp-authenticate sock server auth-mechs login password))))) |
|
410 |
- |
|
411 |
- ;; all is good |
|
412 |
- (setq ok t)) |
|
413 |
- |
|
414 |
- ;; cleanup: |
|
415 |
- (if* (null ok) |
|
416 |
- then (close sock :abort t) |
|
417 |
- (setq sock nil))) |
|
384 |
+ (tagbody |
|
385 |
+ (response-case (sock msg) |
|
386 |
+ (2 ;; to the initial connect |
|
387 |
+ nil) |
|
388 |
+ (t (error "initial connect failed: ~s" msg))) |
|
389 |
+ ehlo |
|
390 |
+ ;; now that we're connected we can compute our hostname |
|
391 |
+ (let ((hostname (socket:ipaddr-to-hostname |
|
392 |
+ (socket:local-host sock)))) |
|
393 |
+ (if* (null hostname) |
|
394 |
+ then (setq hostname |
|
395 |
+ (format nil "[~a]" (socket:ipaddr-to-dotted |
|
396 |
+ (socket:local-host sock))))) |
|
397 |
+ (let ((mechs (smtp-ehlo sock hostname)) |
|
398 |
+ auth-mechs) |
|
399 |
+ (if* (and mechs starttls (member "STARTTLS" mechs :test #'string=)) |
|
400 |
+ then (smtp-send-recv (sock (format nil "STARTTLS") msg) |
|
401 |
+ (2 ;; ok |
|
402 |
+ (setq sock (acl-socket:make-ssl-client-stream sock :method :tlsv1))) |
|
403 |
+ (t (smtp-transaction-error))) |
|
404 |
+ (go ehlo) |
|
405 |
+ elseif (and mechs login password |
|
406 |
+ (setq auth-mechs (car (member "LOGIN" mechs |
|
407 |
+ :test #'(lambda (x y) (search x y)))))) |
|
408 |
+ then (setf sock |
|
409 |
+ (smtp-authenticate sock server auth-mechs login password))))) |
|
410 |
+ |
|
411 |
+ ;; all is good |
|
412 |
+ (setq ok t)) |
|
413 |
+ |
|
414 |
+ ;; cleanup: |
|
415 |
+ (if* (null ok) |
|
416 |
+ then (close sock :abort t) |
|
417 |
+ (setq sock nil))) |
|
418 | 418 |
|
419 | 419 |
;; return: |
420 | 420 |
sock |
... | ... |
@@ -430,52 +430,52 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
430 | 430 |
;; Collect the auth mechanisms. |
431 | 431 |
(let (mechs) |
432 | 432 |
(multiple-value-bind (found whole mech) |
433 |
- (match-re "250[- ]AUTH (.*)" msg) |
|
434 |
- (declare (ignore whole)) |
|
435 |
- (if found (push mech mechs))) |
|
433 |
+ (match-re "250[- ]AUTH (.*)" msg) |
|
434 |
+ (declare (ignore whole)) |
|
435 |
+ (if found (push mech mechs))) |
|
436 | 436 |
(multiple-value-bind (found whole mech) |
437 |
- (match-re "250[- ](STARTTLS)" msg) |
|
438 |
- (declare (ignore whole)) |
|
439 |
- (if found (push mech mechs))) |
|
437 |
+ (match-re "250[- ](STARTTLS)" msg) |
|
438 |
+ (declare (ignore whole)) |
|
439 |
+ (if found (push mech mechs))) |
|
440 | 440 |
mechs)) |
441 | 441 |
(t |
442 | 442 |
(smtp-send-recv (sock (format nil "HELO ~A" our-name) msg) |
443 | 443 |
(2 ;; ok |
444 |
- nil) |
|
444 |
+ nil) |
|
445 | 445 |
(t (smtp-transaction-error)))))) |
446 | 446 |
|
447 | 447 |
(defun smtp-authenticate (sock server mechs login password) |
448 | 448 |
(let ((ctx (net.sasl:sasl-client-new "smtp" server |
449 |
- :user login |
|
450 |
- :pass password))) |
|
449 |
+ :user login |
|
450 |
+ :pass password))) |
|
451 | 451 |
(multiple-value-bind (res selected-mech response) |
452 |
- (net.sasl:sasl-client-start ctx mechs) |
|
452 |
+ (net.sasl:sasl-client-start ctx mechs) |
|
453 | 453 |
(if (not (eq res :continue)) |
454 |
- (error "sasl-client-start unexpectedly returned: ~s" res)) |
|
454 |
+ (error "sasl-client-start unexpectedly returned: ~s" res)) |
|
455 | 455 |
(smtp-command sock "AUTH ~a" selected-mech) |
456 | 456 |
(loop |
457 |
- (response-case (sock msg) |
|
458 |
- (3 ;; need more interaction |
|
459 |
- (multiple-value-setq (res response) |
|
460 |
- (net.sasl:sasl-step |
|
461 |
- ctx |
|
462 |
- (base64-string-to-usb8-array (subseq msg 4)))) |
|
463 |
- (smtp-command sock "~a" |
|
464 |
- (usb8-array-to-base64-string response nil))) |
|
465 |
- (2 ;; server is satisfied. |
|
466 |
- ;; Make sure the auth process really completed |
|
467 |
- (if (not (net.sasl:sasl-conn-auth-complete-p ctx)) |
|
468 |
- (error "SMTP server indicated authentication complete before mechanisms was satisfied")) |
|
469 |
- ;; It's all good. |
|
470 |
- (return)) ;; break from loop |
|
471 |
- (t |
|
472 |
- (error "SMTP authentication failed: ~a" msg))))) |
|
457 |
+ (response-case (sock msg) |
|
458 |
+ (3 ;; need more interaction |
|
459 |
+ (multiple-value-setq (res response) |
|
460 |
+ (net.sasl:sasl-step |
|
461 |
+ ctx |
|
462 |
+ (base64-string-to-usb8-array (subseq msg 4)))) |
|
463 |
+ (smtp-command sock "~a" |
|
464 |
+ (usb8-array-to-base64-string response nil))) |
|
465 |
+ (2 ;; server is satisfied. |
|
466 |
+ ;; Make sure the auth process really completed |
|
467 |
+ (if (not (net.sasl:sasl-conn-auth-complete-p ctx)) |
|
468 |
+ (error "SMTP server indicated authentication complete before mechanisms was satisfied")) |
|
469 |
+ ;; It's all good. |
|
470 |
+ (return)) ;; break from loop |
|
471 |
+ (t |
|
472 |
+ (error "SMTP authentication failed: ~a" msg))))) |
|
473 | 473 |
|
474 | 474 |
;; Reach here if authentication completed. |
475 | 475 |
;; If a security layer was negotiated, return an encapsulated sock, |
476 | 476 |
;; otherwise just return the original sock. |
477 | 477 |
(if (net.sasl:sasl-conn-security-layer-p ctx) |
478 |
- (net.sasl:sasl-make-stream ctx sock :close-base t) |
|
478 |
+ (net.sasl:sasl-make-stream ctx sock :close-base t) |
|
479 | 479 |
sock))) |
480 | 480 |
|
481 | 481 |
|
... | ... |
@@ -485,45 +485,45 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
485 | 485 |
;; return nil if the address is bogus |
486 | 486 |
;; return t if the address may or may not be bogus |
487 | 487 |
(if* (or (not (stringp address)) |
488 |
- (zerop (length address))) |
|
488 |
+ (zerop (length address))) |
|
489 | 489 |
then (error "mail address should be a non-empty string: ~s" address)) |
490 | 490 |
|
491 | 491 |
; split on the @ sign |
492 | 492 |
(let (name hostname) |
493 | 493 |
(let ((pos (position #\@ address))) |
494 | 494 |
(if* (null pos) |
495 |
- then (setq name address |
|
496 |
- hostname "localhost") |
|
495 |
+ then (setq name address |
|
496 |
+ hostname "localhost") |
|
497 | 497 |
elseif (or (eql pos 0) |
498 |
- (eql pos (1- (length address)))) |
|
499 |
- then ; @ at beginning or end, bogus since we don't do route addrs |
|
500 |
- (return-from test-email-address nil) |
|
501 |
- else (setq name (subseq address 0 pos) |
|
502 |
- hostname (subseq address (1+ pos))))) |
|
498 |
+ (eql pos (1- (length address)))) |
|
499 |
+ then ; @ at beginning or end, bogus since we don't do route addrs |
|
500 |
+ (return-from test-email-address nil) |
|
501 |
+ else (setq name (subseq address 0 pos) |
|
502 |
+ hostname (subseq address (1+ pos))))) |
|
503 | 503 |
|
504 | 504 |
(let ((sock (ignore-errors (connect-to-mail-server hostname nil nil)))) |
505 | 505 |
(if* (null sock) then (return-from test-email-address nil)) |
506 | 506 |
|
507 | 507 |
(unwind-protect |
508 |
- (progn |
|
509 |
- (smtp-send-recv (sock (format nil "VRFY ~a" name) msg code) |
|
510 |
- (5 |
|
511 |
- (if* (eq code 550) |
|
512 |
- then ; no such user |
|
513 |
- msg ; to remove unused warning |
|
514 |
- nil |
|
515 |
- else ;; otherwise we don't know |
|
516 |
- (return-from test-email-address t))) |
|
517 |
- (t (return-from test-email-address t))) |
|
518 |
- (smtp-send-recv (sock (format nil "VRFY ~a" address) msg code) |
|
519 |
- (5 |
|
520 |
- (if* (eq code 550) |
|
521 |
- then ; no such user |
|
522 |
- msg ; to remove unused warning |
|
523 |
- nil |
|
524 |
- else t)) |
|
525 |
- (t t))) |
|
526 |
- (close sock :abort t))))) |
|
508 |
+ (progn |
|
509 |
+ (smtp-send-recv (sock (format nil "VRFY ~a" name) msg code) |
|
510 |
+ (5 |
|
511 |
+ (if* (eq code 550) |
|
512 |
+ then ; no such user |
|
513 |
+ msg ; to remove unused warning |
|
514 |
+ nil |
|
515 |
+ else ;; otherwise we don't know |
|
516 |
+ (return-from test-email-address t))) |
|
517 |
+ (t (return-from test-email-address t))) |
|
518 |
+ (smtp-send-recv (sock (format nil "VRFY ~a" address) msg code) |
|
519 |
+ (5 |
|
520 |
+ (if* (eq code 550) |
|
521 |
+ then ; no such user |
|
522 |
+ msg ; to remove unused warning |
|
523 |
+ nil |
|
524 |
+ else t)) |
|
525 |
+ (t t))) |
|
526 |
+ (close sock :abort t))))) |
|
527 | 527 |
|
528 | 528 |
|
529 | 529 |
|
... | ... |
@@ -543,7 +543,7 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
543 | 543 |
;; read the response of the smtp server. |
544 | 544 |
;; collect it all in a string. |
545 | 545 |
;; Return two values: |
546 |
- ;; response class |
|
546 |
+ ;; response class |
|
547 | 547 |
;; whole string |
548 | 548 |
;; The string should begin with a decimal digit, and that is converted |
549 | 549 |
;; into a number which is returned as the response class. |
... | ... |
@@ -551,18 +551,18 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
551 | 551 |
;; response class is -1. |
552 | 552 |
;; |
553 | 553 |
(flet ((match-chars (string pos1 pos2 count) |
554 |
- ;; like strncmp |
|
555 |
- (dotimes (i count t) |
|
556 |
- (if* (not (eq (aref string (+ pos1 i)) |
|
557 |
- (aref string (+ pos2 i)))) |
|
558 |
- then (return nil))))) |
|
554 |
+ ;; like strncmp |
|
555 |
+ (dotimes (i count t) |
|
556 |
+ (if* (not (eq (aref string (+ pos1 i)) |
|
557 |
+ (aref string (+ pos2 i)))) |
|
558 |
+ then (return nil))))) |
|
559 | 559 |
|
560 | 560 |
(let ((res (make-array 20 :element-type 'character |
561 |
- :adjustable t |
|
562 |
- :fill-pointer 0))) |
|
561 |
+ :adjustable t |
|
562 |
+ :fill-pointer 0))) |
|
563 | 563 |
(if* (null (read-a-line stream res)) |
564 |
- then ; eof encountered before end of line |
|
565 |
- (return-from wait-for-response (values -1 res))) |
|
564 |
+ then ; eof encountered before end of line |
|
565 |
+ (return-from wait-for-response (values -1 res))) |
|
566 | 566 |
|
567 | 567 |
;; a multi-line response begins with line containing |
568 | 568 |
;; a hyphen in the 4th column: |
... | ... |
@@ -574,40 +574,40 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
574 | 574 |
;; |
575 | 575 |
|
576 | 576 |
(if* (and (>= (length res) 4) (eq #\- (aref res 3))) |
577 |
- then ;; multi line response |
|
578 |
- (let ((old-length (length res)) |
|
579 |
- (new-length nil)) |
|
580 |
- (loop |
|
581 |
- (if* (null (read-a-line stream res)) |
|
582 |
- then ; eof encountered before end of line |
|
583 |
- (return-from wait-for-response (values -1 res))) |
|
584 |
- (setq new-length (length res)) |
|
585 |
- ;; see if this is the last line |
|
586 |
- (if* (and (>= (- new-length old-length) 4) |
|
587 |
- (eq (aref res (+ old-length 3)) #\space) |
|
588 |
- (match-chars res 0 old-length 3)) |
|
589 |
- then (return)) |
|
590 |
- |
|
591 |
- (setq old-length new-length)))) |
|
577 |
+ then ;; multi line response |
|
578 |
+ (let ((old-length (length res)) |
|
579 |
+ (new-length nil)) |
|
580 |
+ (loop |
|
581 |
+ (if* (null (read-a-line stream res)) |
|
582 |
+ then ; eof encountered before end of line |
|
583 |
+ (return-from wait-for-response (values -1 res))) |
|
584 |
+ (setq new-length (length res)) |
|
585 |
+ ;; see if this is the last line |
|
586 |
+ (if* (and (>= (- new-length old-length) 4) |
|
587 |
+ (eq (aref res (+ old-length 3)) #\space) |
|
588 |
+ (match-chars res 0 old-length 3)) |
|
589 |
+ then (return)) |
|
590 |
+ |
|
591 |
+ (setq old-length new-length)))) |
|
592 | 592 |
|
593 | 593 |
;; complete response is in res |
594 | 594 |
;; compute class and return the whole thing |
595 | 595 |
(let ((class (or (and (> (length res) 0) |
596 |
- (digit-char-p (aref res 0))) |
|
597 |
- -1))) |
|
598 |
- (values class res |
|
599 |
- (if* (>= (length res) 3) |
|
600 |
- then ; compute the whole response value |
|
601 |
- (+ (* (or (digit-char-p (aref res 0)) 0) 100) |
|
602 |
- (* (or (digit-char-p (aref res 1)) 0) 10) |
|
603 |
- (or (digit-char-p (aref res 2)) 0)))))))) |
|
596 |
+ (digit-char-p (aref res 0))) |
|
597 |
+ -1))) |
|
598 |
+ (values class res |
|
599 |
+ (if* (>= (length res) 3) |
|
600 |
+ then ; compute the whole response value |
|
601 |
+ (+ (* (or (digit-char-p (aref res 0)) 0) 100) |
|
602 |
+ (* (or (digit-char-p (aref res 1)) 0) 10) |
|
603 |
+ (or (digit-char-p (aref res 2)) 0)))))))) |
|
604 | 604 |
|
605 | 605 |
(defun smtp-command (stream &rest format-args) |
606 | 606 |
;; send a command to the smtp server |
607 | 607 |
(let ((command (apply #'format nil format-args))) |
608 | 608 |
(if* *smtp-debug* |
609 | 609 |
then (format *smtp-debug* "to smtp command: ~s~%" command) |
610 |
- (force-output *smtp-debug*)) |
|
610 |
+ (force-output *smtp-debug*)) |
|
611 | 611 |
(write-string command stream) |
612 | 612 |
(write-char #\return stream) |
613 | 613 |
(write-char #\newline stream) |
... | ... |
@@ -622,25 +622,25 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
622 | 622 |
(loop |
623 | 623 |
(setq ch (read-char stream nil nil)) |
624 | 624 |
(if* (null ch) |
625 |
- then ; premature eof |
|
626 |
- (return nil)) |
|
625 |
+ then ; premature eof |
|
626 |
+ (return nil)) |
|
627 | 627 |
|
628 | 628 |
(if* *smtp-debug* |
629 |
- then (format *smtp-debug* "~c" ch) |
|
630 |
- (force-output *smtp-debug*) |
|
631 |
- ) |
|
629 |
+ then (format *smtp-debug* "~c" ch) |
|
630 |
+ (force-output *smtp-debug*) |
|
631 |
+ ) |
|
632 | 632 |
|
633 | 633 |
(if* (eq last-ch #\return) |
634 |
- then (if* (eq ch #\linefeed) |
|
635 |
- then (vector-push-extend #\newline res) |
|
636 |
- (return t) |
|
637 |
- else (vector-push-extend last-ch res)) |
|
634 |
+ then (if* (eq ch #\linefeed) |
|
635 |
+ then (vector-push-extend #\newline res) |
|
636 |
+ (return t) |
|
637 |
+ else (vector-push-extend last-ch res)) |
|
638 | 638 |
elseif (eq ch #\linefeed) |
639 |
- then ; line ends with just lf, not cr-lf |
|
640 |
- (vector-push-extend #\newline res) |
|
641 |
- (return t) |
|
639 |
+ then ; line ends with just lf, not cr-lf |
|
640 |
+ (vector-push-extend #\newline res) |
|
641 |
+ (return t) |
|
642 | 642 |
elseif (not (eq ch #\return)) |
643 |
- then (vector-push-extend ch res)) |
|
643 |
+ then (vector-push-extend ch res)) |
|
644 | 644 |
|
645 | 645 |
(setq last-ch ch)))) |
646 | 646 |
|
... | ... |
@@ -667,24 +667,24 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
667 | 667 |
elseif (ipaddrp (setq ipaddr (socket:dotted-to-ipaddr name :errorp nil))) |
668 | 668 |
then ipaddr |
669 | 669 |
else ; do mx lookup if acldns is being used |
670 |
- (if* (or (eq socket:*dns-mode* :acldns) |
|
671 |
- (and (consp socket:*dns-mode*) |
|
672 |
- (member :acldns socket:*dns-mode* :test #'eq))) |
|
673 |
- then (let ((res (socket:dns-query name :type :mx))) |
|
674 |
- (if* (and (consp res) (cadr res)) |
|
675 |
- then (cadr res) ; the ip address |
|
676 |
- else (dolist (suffix socket::*domain-search-list* |
|
677 |
- (socket:dns-lookup-hostname name)) |
|
678 |
- (declare (special socket:*domain-search-list*)) |
|
679 |
- (let ((name |
|
680 |
- (concatenate 'string name "." suffix))) |
|
681 |
- (setq res (socket:dns-query name :type :mx)) |
|
682 |
- (if* (and res (cadr res)) |
|
683 |
- then (return (cadr res))))))) |
|
684 |
- |
|
685 |
- |
|
686 |
- else ; just do a hostname lookup |
|
687 |
- (ignore-errors (socket:lookup-hostname name)))))) |
|
670 |
+ (if* (or (eq socket:*dns-mode* :acldns) |
|
671 |
+ (and (consp socket:*dns-mode*) |
|
672 |
+ (member :acldns socket:*dns-mode* :test #'eq))) |
|
673 |
+ then (let ((res (socket:dns-query name :type :mx))) |
|
674 |
+ (if* (and (consp res) (cadr res)) |
|
675 |
+ then (cadr res) ; the ip address |
|
676 |
+ else (dolist (suffix socket::*domain-search-list* |
|
677 |
+ (socket:dns-lookup-hostname name)) |
|
678 |
+ (declare (special socket:*domain-search-list*)) |
|
679 |
+ (let ((name |
|
680 |
+ (concatenate 'string name "." suffix))) |
|
681 |
+ (setq res (socket:dns-query name :type :mx)) |
|
682 |
+ (if* (and res (cadr res)) |
|
683 |
+ then (return (cadr res))))))) |
|
684 |
+ |
|
685 |
+ |
|
686 |
+ else ; just do a hostname lookup |
|
687 |
+ (ignore-errors (socket:lookup-hostname name)))))) |
|
688 | 688 |
|
689 | 689 |
|
690 | 690 |
|