git.fiddlerwoaroof.com
Browse code

2006-12-19 Kevin Layer <layer@gemini.franz.com>

layer authored on 19/12/2006 20:55:35
Showing 2 changed files
... ...
@@ -1,3 +1,9 @@
1
+2006-12-19  Kevin Layer  <layer@gemini.franz.com>
2
+
3
+	* (NEW) rfc2822.cl: new file, authored by Ahmon -- only change I
4
+	  made was to make sure the regular expressions are compiled at
5
+	  compile-file time.
6
+
1 7
 2006-12-11  Ahmon Dancy  <dancy@dancy>
2 8
 
3 9
 	* mime-api.cl: New map-over-parts function. 
4 10
new file mode 100644
... ...
@@ -0,0 +1,164 @@
1
+(in-package :user)
2
+
3
+#|
4
+Email address parser.  If parsing succeeds, then the email address
5
+has valid syntax.  
6
+
7
+The parser should be RFC2822 compliant except: 
8
+
9
+* It optionally allows for domain-less addresses.
10
+* By default, it requires the domain part to have two components (not
11
+  actually required by the spec).
12
+* It does not allow domain literals (e.g., "joe-user@[192.132.95.23]")
13
+* It does not allow quoted strings.
14
+
15
+Exports:
16
+
17
+Function: parse-email-address 
18
+Args: string &key require-domain require-dotted-domain
19
+
20
+Parses an email address string and returns two values: the username 
21
+part of the address and the domain part of the address.  
22
+
23
+Keyword arguments:
24
+
25
+:require-domain  
26
+
27
+defaults to true.  If true, then the @domain part of the email address
28
+is required.  If nil, then the @domain part of the email address is
29
+not required.  If it is not found, then the second return value of
30
+this function will be nil.
31
+
32
+:require-dotted-domain
33
+
34
+defaults to true.  If true, then the domain part of the email address
35
+must have two dotted components (e.g., "franz.com").  If nil, then a
36
+single-component domain part is accepted (e.g., "com").
37
+
38
+---
39
+
40
+Function: valid-email-domain-p
41
+Args: domain
42
+
43
+Returns information on whether or not the DNS configuration for
44
+'domain' is configured properly for Internet email reception.  
45
+
46
+Possible return values:
47
+
48
+nil
49
+
50
+This means that the DNS records for 'domain' are not properly
51
+configured for Internet email. 
52
+
53
+:unknown
54
+
55
+This means that no information was successfully collected.  No
56
+conclusion can be drawn.
57
+
58
+t
59
+
60
+This means that 'domain' has DNS records that are suitable for
61
+Internet email reception.  This does not necessarily mean that email
62
+delivery will succeed.  
63
+
64
+Note:  This function is more useful for its negative response (nil)
65
+than any other response.  If it returns nil, it means that no standard
66
+mail transfer agent would be able to locate the mail server for the
67
+domain.
68
+
69
+|#
70
+
71
+(eval-when (compile eval)
72
+  ;; dash at the end to avoid mistaking it for a character range
73
+  ;; indicator.
74
+  (defconstant *atext-chars*
75
+      "!#$%&'*+/0123456789=?ABCDEFGHIJKLMNOPQRSTUVWXYZ^_`abcdefghijklmnopqrstuvwxyz{|}~-")
76
+  
77
+  (defconstant *dot-atom* 
78
+      (format nil "[~a]+(\\.[~a]+)*" *atext-chars* *atext-chars*))
79
+
80
+  (defconstant *dotted-dot-atom* 
81
+      (format nil "[~a]+(\\.[~a]+)+"  *atext-chars* *atext-chars*))
82
+  
83
+  (defvar *rfc822-dotted-domain-re*
84
+      (format nil "^(~a)(@(~a))?$" *dot-atom* *dotted-dot-atom*))
85
+
86
+  (defvar *rfc822-re* (format nil "^(~a)(@(~a))?$" *dot-atom* *dot-atom*))
87
+  )
88
+
89
+(defun parse-email-address (string &key (require-domain t)
90
+					(require-dotted-domain t))
91
+  (multiple-value-bind (matched whole user dummy1 dummy2 domain)
92
+      (if* require-dotted-domain
93
+	 then (match-re #.*rfc822-dotted-domain-re* string)
94
+	 else (match-re #.*rfc822-re* string))
95
+    (declare (ignore whole dummy1 dummy2))
96
+    (if (or (not matched) (and require-domain (null domain)))
97
+	nil
98
+      (values user domain))))
99
+
100
+;; Ripped from maild:dns.cl and modified.
101
+
102
+(eval-when (compile load eval)
103
+  (require :acldns))
104
+
105
+;; Only follows one CNAME lookup.  If there is any more than that, the
106
+;; domain has a really jacked up setup.
107
+
108
+;; possible answers
109
+;;  t -- yes, there exists a record of that type.
110
+;;  nil -- no record of that type exists
111
+;;  :nxdomain -- the domain itself doesn't exist
112
+;;  :unknown -- couldn't get any answers.
113
+(defun dns-record-exists-p (domain type &key (try-cname t))
114
+  (block nil
115
+    (let ((resp (socket:dns-query domain :decode nil :type type)))
116
+      (if (null resp)
117
+	  (return :unknown))
118
+      (let ((flags (socket:dns-response-flags resp))
119
+	    (answer (socket:dns-response-answer resp)))
120
+	(cond 
121
+	 ((member :nameserver-internal-error flags)
122
+	  (return :unknown))
123
+	 ((member :no-such-domain flags)
124
+	  (return :nxdomain))
125
+	 ((null answer)
126
+	  (return nil)) ;; no records of that type for that name
127
+	 ((member :cname answer
128
+		  :test #'eq :key #'socket:dns-rr-type)
129
+	  (if* (not try-cname)
130
+	     then (return nil)
131
+	     else ;; There should only be one cname answer.
132
+		  (return (dns-record-exists-p (socket:dns-rr-answer 
133
+						(first answer))
134
+					       type :try-cname nil))))
135
+	 (t
136
+	  t))))))
137
+  
138
+;; A valid email domain is one that has an MX record or an A record
139
+;; [or a CNAME to an MX or A record (illegal, but people do it)]
140
+
141
+;; possible answers:  
142
+;;  t -- there is either an MX or A record for that domain
143
+;;  nil -- there is neither an MX nor A record for that domain
144
+;          (possibly because the domain does not exist at all)
145
+;; :unknown -- couldn't get answers
146
+(defun valid-email-domain-p (domain)
147
+  (block nil
148
+    (let ((res (dns-record-exists-p domain :mx)))
149
+      (cond
150
+       ((eq res t)
151
+	(return t))
152
+       ((eq res :nxdomain)
153
+	(return nil))
154
+       ((eq res :unknown)
155
+	(return :unknown)))
156
+      (setf res (dns-record-exists-p domain :a))
157
+      (cond
158
+       ((eq res t)
159
+	(return t))
160
+       ((eq res :nxdomain)
161
+	(return nil))
162
+       ((eq res :unknown)
163
+	(return :unknown)))
164
+      nil)))