Browse code
2007-04-12 Ahmon Dancy <dancy@dancy>
layer authored on 12/04/2007 23:58:15
Showing 2 changed files
Showing 2 changed files
... | ... |
@@ -43,7 +43,7 @@ v5: rm stray force-output of t; send-smtp-1: New external-format keyword arg." |
43 | 43 |
;; Suite 330, Boston, MA 02111-1307 USA |
44 | 44 |
;; |
45 | 45 |
;; |
46 |
-;; $Id: smtp.cl,v 1.21 2006/11/17 00:32:07 layer Exp $ |
|
46 |
+;; $Id: smtp.cl,v 1.22 2007/04/12 23:58:15 layer Exp $ |
|
47 | 47 |
|
48 | 48 |
;; Description: |
49 | 49 |
;; send mail to an smtp server. See rfc821 for the spec. |
... | ... |
@@ -128,6 +128,28 @@ v5: rm stray force-output of t; send-smtp-1: New external-format keyword arg." |
128 | 128 |
(case ,response-class |
129 | 129 |
,@case-clauses)))) |
130 | 130 |
|
131 |
+(defmacro smtp-send-recv ((smtp-stream cmd smtp-response &optional response-code) &rest case-clauses) |
|
132 |
+ (let ((stream (gensym)) |
|
133 |
+ (sent (gensym))) |
|
134 |
+ `(let ((,stream ,smtp-stream) |
|
135 |
+ (,sent ,cmd)) |
|
136 |
+ (if* *smtp-debug* |
|
137 |
+ then (format *smtp-debug* "to smtp command: ~s~%" ,sent) |
|
138 |
+ (force-output *smtp-debug*)) |
|
139 |
+ (write-string ,sent ,stream) |
|
140 |
+ (write-char #\return ,stream) |
|
141 |
+ (write-char #\newline ,stream) |
|
142 |
+ (force-output ,stream) |
|
143 |
+ (macrolet ((smtp-transaction-error () |
|
144 |
+ (list |
|
145 |
+ 'error |
|
146 |
+ "SMTP transaction failed. We said: ~s, and the server replied: ~s" |
|
147 |
+ (quote ,sent) |
|
148 |
+ (quote ,smtp-response)))) |
|
149 |
+ |
|
150 |
+ (response-case (,stream ,smtp-response ,response-code) |
|
151 |
+ ,@case-clauses))))) |
|
152 |
+ |
|
131 | 153 |
(defvar *smtp-debug* nil) |
132 | 154 |
|
133 | 155 |
|
... | ... |
@@ -264,13 +286,12 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
264 | 286 |
(unwind-protect |
265 | 287 |
(progn |
266 | 288 |
|
267 |
- (smtp-command sock "MAIL from:<~a>" from) |
|
268 |
- (response-case (sock msg) |
|
289 |
+ (smtp-send-recv (sock (format nil "MAIL from:<~a>" from) msg) |
|
269 | 290 |
(2 ;; cool |
270 | 291 |
nil |
271 | 292 |
) |
272 |
- (t (error "Mail from command failed: ~s" msg))) |
|
273 |
- |
|
293 |
+ (t (smtp-transaction-error))) |
|
294 |
+ |
|
274 | 295 |
(let ((tos (if* (stringp to) |
275 | 296 |
then (list to) |
276 | 297 |
elseif (consp to) |
... | ... |
@@ -278,18 +299,16 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
278 | 299 |
else (error "to should be a string or list, not ~s" |
279 | 300 |
to)))) |
280 | 301 |
(dolist (to tos) |
281 |
- (smtp-command sock "RCPT to:<~a>" to) |
|
282 |
- (response-case (sock msg) |
|
302 |
+ (smtp-send-recv (sock (format nil "RCPT to:<~a>" to) msg) |
|
283 | 303 |
(2 ;; cool |
284 | 304 |
nil |
285 | 305 |
) |
286 |
- (t (error "rcpt to command failed: ~s" msg))))) |
|
306 |
+ (t (smtp-transaction-error))))) |
|
287 | 307 |
|
288 |
- (smtp-command sock "DATA") |
|
289 |
- (response-case (sock msg) |
|
308 |
+ (smtp-send-recv (sock "DATA" msg) |
|
290 | 309 |
(3 ;; cool |
291 | 310 |
nil) |
292 |
- (t (error "Data command failed: ~s" msg))) |
|
311 |
+ (t (smtp-transaction-error))) |
|
293 | 312 |
|
294 | 313 |
|
295 | 314 |
|
... | ... |
@@ -328,16 +347,11 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
328 | 347 |
|
329 | 348 |
(t (error "message not sent: ~s" msg))) |
330 | 349 |
|
331 |
- ;; Hmmmm, this is not good. Perhaps force-output on |
|
332 |
- ;; *error-output* is what was intended? I'm pretty sure that's |
|
333 |
- ;; *not needed. |
|
334 |
- #+ignore (force-output t) |
|
335 |
- |
|
336 |
- (smtp-command sock "QUIT") |
|
337 |
- (response-case (sock msg) |
|
350 |
+ (smtp-send-recv (sock "QUIT" msg) |
|
338 | 351 |
(2 ;; cool |
339 | 352 |
nil) |
340 |
- (t (error "quit failed: ~s" msg)))) |
|
353 |
+ (t (smtp-transaction-error)))) |
|
354 |
+ ;; Cleanup |
|
341 | 355 |
(close sock)))) |
342 | 356 |
|
343 | 357 |
(defun connect-to-mail-server (server login password) |
... | ... |
@@ -403,8 +417,7 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
403 | 417 |
;; This may need to be expanded in the future as we support |
404 | 418 |
;; more of the features that EHLO responds with. |
405 | 419 |
(defun smtp-ehlo (sock our-name) |
406 |
- (smtp-command sock "EHLO ~A" our-name) |
|
407 |
- (response-case (sock msg) |
|
420 |
+ (smtp-send-recv (sock (format nil "EHLO ~A" our-name) msg) |
|
408 | 421 |
(2 ;; ok |
409 | 422 |
;; Collect the auth mechanisms. |
410 | 423 |
(multiple-value-bind (found whole mechs) |
... | ... |
@@ -413,11 +426,10 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
413 | 426 |
(if found |
414 | 427 |
mechs))) |
415 | 428 |
(t |
416 |
- (smtp-command sock "HELO ~A" our-name) |
|
417 |
- (response-case (sock msg) |
|
429 |
+ (smtp-send-recv (sock (format nil "HELO ~A" our-name) msg) |
|
418 | 430 |
(2 ;; ok |
419 | 431 |
nil) |
420 |
- (t (error "hello greeting failed: ~s" msg)))))) |
|
432 |
+ (t (smtp-transaction-error)))))) |
|
421 | 433 |
|
422 | 434 |
(defun smtp-authenticate (sock server mechs login password) |
423 | 435 |
(let ((ctx (net.sasl:sasl-client-new "smtp" server |
... | ... |
@@ -440,11 +452,11 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
440 | 452 |
(2 ;; server is satisfied. |
441 | 453 |
;; Make sure the auth process really completed |
442 | 454 |
(if (not (net.sasl:sasl-conn-auth-complete-p ctx)) |
443 |
- (error "SMTP server indicated authentication complete before mechanisms was satisfied")) |
|
455 |
+ (error "SMTP server indicated authentication complete before mechanisms was satisfied")) |
|
444 | 456 |
;; It's all good. |
445 | 457 |
(return)) ;; break from loop |
446 | 458 |
(t |
447 |
- (error "SMTP authentication failed"))))) |
|
459 |
+ (error "SMTP authentication failed: ~a" msg))))) |
|
448 | 460 |
|
449 | 461 |
;; Reach here if authentication completed. |
450 | 462 |
;; If a security layer was negotiated, return an encapsulated sock, |
... | ... |
@@ -481,8 +493,7 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
481 | 493 |
|
482 | 494 |
(unwind-protect |
483 | 495 |
(progn |
484 |
- (smtp-command sock "VRFY ~a" name) |
|
485 |
- (response-case (sock msg code) |
|
496 |
+ (smtp-send-recv (sock (format nil "VRFY ~a" name) msg code) |
|
486 | 497 |
(5 |
487 | 498 |
(if* (eq code 550) |
488 | 499 |
then ; no such user |
... | ... |
@@ -491,8 +502,7 @@ Attachments must be filenames, streams, or mime-part-constructed, not ~s" |
491 | 502 |
else ;; otherwise we don't know |
492 | 503 |
(return-from test-email-address t))) |
493 | 504 |
(t (return-from test-email-address t))) |
494 |
- (smtp-command sock "VRFY ~a" address) |
|
495 |
- (response-case (sock msg code) |
|
505 |
+ (smtp-send-recv (sock (format nil "VRFY ~a" address) msg code) |
|
496 | 506 |
(5 |
497 | 507 |
(if* (eq code 550) |
498 | 508 |
then ; no such user |