git.fiddlerwoaroof.com
Browse code

add test-email-address function

jkf authored on 11/05/2001 16:40:30
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)