Browse code
2006-12-19 Kevin Layer <layer@gemini.franz.com>
layer authored on 19/12/2006 20:55:35
Showing 2 changed files
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))) |