Browse code
add test-email-address function
jkf authored on 11/05/2001 16:40:30
Showing 2 changed files
Showing 2 changed files
... | ... |
@@ -1,3 +1,9 @@ |
1 |
+2001-05-11 John Foderaro <jkf@tiger.franz.com> |
|
2 |
+ |
|
3 |
+ * smtp.cl: add test-email-address function to check to see |
|
4 |
+ if an email address can be determined to be bogus without |
|
5 |
+ sending a letter. |
|
6 |
+ |
|
1 | 7 |
2001-05-02 John Foderaro <jkf@tiger.franz.com> |
2 | 8 |
1.8 |
3 | 9 |
* imap will signal a :response-too-large error if it encounter |
... | ... |
@@ -1,4 +1,4 @@ |
1 |
-;; -*- mode: common-lisp; package: net.aserve -*- |
|
1 |
+;; -*- mode: common-lisp; package: net.post-office -*- |
|
2 | 2 |
;; |
3 | 3 |
;; smtp.cl |
4 | 4 |
;; |
... | ... |
@@ -23,7 +23,7 @@ |
23 | 23 |
;; Suite 330, Boston, MA 02111-1307 USA |
24 | 24 |
;; |
25 | 25 |
;; |
26 |
-;; $Id: smtp.cl,v 1.3 2001/05/04 16:01:45 jkf Exp $ |
|
26 |
+;; $Id: smtp.cl,v 1.4 2001/05/11 16:40:30 jkf Exp $ |
|
27 | 27 |
|
28 | 28 |
;; Description: |
29 | 29 |
;; send mail to an smtp server. See rfc821 for the spec. |
... | ... |
@@ -37,7 +37,8 @@ |
37 | 37 |
(:use #:lisp #:excl) |
38 | 38 |
(:export |
39 | 39 |
#:send-letter |
40 |
- #:send-smtp)) |
|
40 |
+ #:send-smtp |
|
41 |
+ #:test-email-address)) |
|
41 | 42 |
|
42 | 43 |
(in-package :net.post-office) |
43 | 44 |
|
... | ... |
@@ -72,12 +73,20 @@ |
72 | 73 |
;; and sent as one message |
73 | 74 |
;; |
74 | 75 |
;; |
76 |
+;; (test-email-address "user@machine.com") |
|
77 |
+;; return t is this could be a valid email address on the machine |
|
78 |
+;; named. Do this by contacting the mail server and using the VRFY |
|
79 |
+;; command from smtp. Since some mail servers don't implement VRFY |
|
80 |
+;; we return t if VRFY doesn't work. |
|
81 |
+;; nil means that this address is bad (or we can't make contact with |
|
82 |
+;; the mail server, which could of course be a transient problem). |
|
83 |
+;; |
|
75 | 84 |
|
76 | 85 |
|
77 | 86 |
|
78 | 87 |
|
79 | 88 |
|
80 |
-(defmacro response-case ((smtp-stream &optional smtp-response) &rest case-clauses) |
|
89 |
+(defmacro response-case ((smtp-stream &optional smtp-response response-code) &rest case-clauses) |
|
81 | 90 |
;; get a response from the smtp server and dispatch in a 'case' like |
82 | 91 |
;; fashion to a clause based on the first digit of the return |
83 | 92 |
;; code of the response. |
... | ... |
@@ -85,8 +94,9 @@ |
85 | 94 |
;; the actual response |
86 | 95 |
;; |
87 | 96 |
(let ((response-class (gensym))) |
88 |
- `(multiple-value-bind (,response-class ,@(if* smtp-response |
|
89 |
- then (list smtp-response))) |
|
97 |
+ `(multiple-value-bind (,response-class |
|
98 |
+ ,@(if* smtp-response then (list smtp-response)) |
|
99 |
+ ,@(if* response-code then (list response-code))) |
|
90 | 100 |
(progn (force-output ,smtp-stream) |
91 | 101 |
(wait-for-response ,smtp-stream)) |
92 | 102 |
;;(declare (ignorable smtp-response)) |
... | ... |
@@ -164,28 +174,11 @@ |
164 | 174 |
;; 'to' can be a single string or a list of strings. |
165 | 175 |
;; each string should be in the official rfc822 format "foo@bar.com" |
166 | 176 |
;; |
167 |
- (let ((sock (socket:make-socket :remote-host server |
|
168 |
- :remote-port 25 ; smtp |
|
169 |
- ))) |
|
177 |
+ |
|
178 |
+ (let ((sock (connect-to-mail-server server))) |
|
179 |
+ |
|
170 | 180 |
(unwind-protect |
171 | 181 |
(progn |
172 |
- (response-case (sock msg) |
|
173 |
- (2 ;; to the initial connect |
|
174 |
- nil) |
|
175 |
- (t (error "initial connect failed: ~s" msg))) |
|
176 |
- |
|
177 |
- ;; now that we're connected we can compute our hostname |
|
178 |
- (let ((hostname (socket:ipaddr-to-hostname |
|
179 |
- (socket:local-host sock)))) |
|
180 |
- (if* (null hostname) |
|
181 |
- then (setq hostname |
|
182 |
- (format nil "[~a]" (socket:ipaddr-to-dotted |
|
183 |
- (socket:local-host sock))))) |
|
184 |
- (smtp-command sock "HELO ~a" hostname) |
|
185 |
- (response-case (sock msg) |
|
186 |
- (2 ;; ok |
|
187 |
- nil) |
|
188 |
- (t (error "hello greeting failed: ~s" msg)))) |
|
189 | 182 |
|
190 | 183 |
(smtp-command sock "MAIL from:<~a>" from) |
191 | 184 |
(response-case (sock msg) |
... | ... |
@@ -248,7 +241,99 @@ |
248 | 241 |
(t (error "quit failed: ~s" msg)))) |
249 | 242 |
(close sock)))) |
250 | 243 |
|
244 |
+(defun connect-to-mail-server (server) |
|
245 |
+ ;; make that initial connection to the mail server |
|
246 |
+ ;; returning a socket connected to it and |
|
247 |
+ ;; signaling an error if it can't be made. |
|
248 |
+ (let ((ipaddr (determine-mail-server server)) |
|
249 |
+ (sock) |
|
250 |
+ (ok)) |
|
251 |
+ |
|
252 |
+ (if* (null ipaddr) |
|
253 |
+ then (error "Can't determine ip addres for mail server ~s" server)) |
|
254 |
+ |
|
255 |
+ (setq sock (socket:make-socket :remote-host ipaddr |
|
256 |
+ :remote-port 25 ; smtp |
|
257 |
+ )) |
|
258 |
+ (unwind-protect |
|
259 |
+ (progn |
|
260 |
+ (response-case (sock msg) |
|
261 |
+ (2 ;; to the initial connect |
|
262 |
+ nil) |
|
263 |
+ (t (error "initial connect failed: ~s" msg))) |
|
264 |
+ |
|
265 |
+ ;; now that we're connected we can compute our hostname |
|
266 |
+ (let ((hostname (socket:ipaddr-to-hostname |
|
267 |
+ (socket:local-host sock)))) |
|
268 |
+ (if* (null hostname) |
|
269 |
+ then (setq hostname |
|
270 |
+ (format nil "[~a]" (socket:ipaddr-to-dotted |
|
271 |
+ (socket:local-host sock))))) |
|
272 |
+ (smtp-command sock "HELO ~a" hostname) |
|
273 |
+ (response-case (sock msg) |
|
274 |
+ (2 ;; ok |
|
275 |
+ nil) |
|
276 |
+ (t (error "hello greeting failed: ~s" msg)))) |
|
277 |
+ |
|
278 |
+ ; all is good |
|
279 |
+ (setq ok t)) |
|
280 |
+ |
|
281 |
+ ; cleanup: |
|
282 |
+ (if* (null ok) |
|
283 |
+ then (close sock :abort t) |
|
284 |
+ (setq sock nil))) |
|
285 |
+ |
|
286 |
+ ; return: |
|
287 |
+ sock |
|
288 |
+ )) |
|
289 |
+ |
|
251 | 290 |
|
291 |
+ |
|
292 |
+(defun test-email-address (address) |
|
293 |
+ ;; test to see if we can determine if the address is valid |
|
294 |
+ ;; return nil if the address is bogus |
|
295 |
+ ;; return t if the address may or may not be bogus |
|
296 |
+ (if* (or (not (stringp address)) |
|
297 |
+ (zerop (length address))) |
|
298 |
+ then (error "mail address should be a non-empty string: ~s" address)) |
|
299 |
+ |
|
300 |
+ ; split on the @ sign |
|
301 |
+ (let (name hostname) |
|
302 |
+ (let ((pos (position #\@ address))) |
|
303 |
+ (if* (null pos) |
|
304 |
+ then (setq name address |
|
305 |
+ hostname "localhost") |
|
306 |
+ elseif (or (eql pos 0) |
|
307 |
+ (eql pos (1- (length address)))) |
|
308 |
+ then ; @ at beginning or end, bogus since we don't do route addrs |
|
309 |
+ (return-from test-email-address nil) |
|
310 |
+ else (setq name (subseq address 0 pos) |
|
311 |
+ hostname (subseq address (1+ pos))))) |
|
312 |
+ |
|
313 |
+ (let ((sock (ignore-errors (connect-to-mail-server hostname)))) |
|
314 |
+ (if* (null sock) then (return-from test-email-address nil)) |
|
315 |
+ |
|
316 |
+ (unwind-protect |
|
317 |
+ (progn |
|
318 |
+ (smtp-command sock "VRFY ~a" name) |
|
319 |
+ (response-case (sock msg code) |
|
320 |
+ (5 |
|
321 |
+ (if* (eq code 550) |
|
322 |
+ then ; no such user |
|
323 |
+ msg ; to remove unused warning |
|
324 |
+ nil |
|
325 |
+ else t ; otherwise we don't know |
|
326 |
+ )) |
|
327 |
+ (t t))) |
|
328 |
+ (close sock :abort t))))) |
|
329 |
+ |
|
330 |
+ |
|
331 |
+ |
|
332 |
+ |
|
333 |
+ |
|
334 |
+ |
|
335 |
+ |
|
336 |
+ |
|
252 | 337 |
|
253 | 338 |
|
254 | 339 |
|
... | ... |
@@ -312,7 +397,12 @@ |
312 | 397 |
(let ((class (or (and (> (length res) 0) |
313 | 398 |
(digit-char-p (aref res 0))) |
314 | 399 |
-1))) |
315 |
- (values class res))))) |
|
400 |
+ (values class res |
|
401 |
+ (if* (>= (length res) 3) |
|
402 |
+ then ; compute the whole response value |
|
403 |
+ (+ (* (or (digit-char-p (aref res 0)) 0) 100) |
|
404 |
+ (* (or (digit-char-p (aref res 1)) 0) 10) |
|
405 |
+ (or (digit-char-p (aref res 2)) 0)))))))) |
|
316 | 406 |
|
317 | 407 |
(defun smtp-command (stream &rest format-args) |
318 | 408 |
;; send a command to the smtp server |
... | ... |
@@ -356,4 +446,33 @@ |
356 | 446 |
|
357 | 447 |
(setq last-ch ch)))) |
358 | 448 |
|
449 |
+ |
|
450 |
+(defun determine-mail-server (name) |
|
451 |
+ ;; return the ipaddress to be used to connect to the |
|
452 |
+ ;; the mail server. |
|
453 |
+ ;; name is any method for naming a machine: |
|
454 |
+ ;; integer ip address |
|
455 |
+ ;; string with dotted ip address |
|
456 |
+ ;; string naming a machine |
|
457 |
+ ;; we can only do the mx lookup for the third case, the rest |
|
458 |
+ ;; we just return the ipaddress for what we were given |
|
459 |
+ ;; |
|
460 |
+ (let (ipaddr) |
|
461 |
+ (if* (integerp name) |
|
462 |
+ then name |
|
463 |
+ elseif (integerp (setq ipaddr |
|
464 |
+ (socket:dotted-to-ipaddr name :errorp nil))) |
|
465 |
+ then ipaddr |
|
466 |
+ else ; do mx lookup if acldns is being used |
|
467 |
+ (if* (or (eq socket:*dns-mode* :acldns) |
|
468 |
+ (member :acldns socket:*dns-mode* :test #'eq)) |
|
469 |
+ then (let ((res (socket:dns-query name :type :mx))) |
|
470 |
+ (if* (and res (consp res)) |
|
471 |
+ then (cadr res) ; the ip address |
|
472 |
+ else (socket:dns-query name :type :a))) |
|
473 |
+ else ; just do a hostname lookup |
|
474 |
+ (ignore-errors (socket:lookup-hostname name)))))) |
|
475 |
+ |
|
476 |
+ |
|
477 |
+ |
|
359 | 478 |
(provide :smtp) |