Browse code
smtp changes
jkf authored on 29/09/1999 16:25:23
Showing 5 changed files
Showing 5 changed files
... | ... |
@@ -1,3 +1,12 @@ |
1 |
+1999-09-29 John Foderaro <jkf@tiger.franz.com> |
|
2 |
+version 1.1 |
|
3 |
+ |
|
4 |
+ * imap.html - document send-letter, send-smtp |
|
5 |
+ * smtp.cl - add this to the imap module |
|
6 |
+ * t-imap.cl - adjust for the change in package |
|
7 |
+ |
|
8 |
+ |
|
9 |
+ |
|
1 | 10 |
1999-09-27 John Foderaro <jkf@tiger.franz.com> |
2 | 11 |
version 1.0 |
3 | 12 |
* start ChangeLog. |
... | ... |
@@ -19,7 +19,7 @@ |
19 | 19 |
;; Commercial Software developed at private expense as specified in |
20 | 20 |
;; DOD FAR Supplement 52.227-7013 (c) (1) (ii), as applicable. |
21 | 21 |
;; |
22 |
-;; $Id: imap.cl,v 1.4 1999/09/27 20:26:15 jkf Exp $ |
|
22 |
+;; $Id: imap.cl,v 1.5 1999/09/29 16:25:23 jkf Exp $ |
|
23 | 23 |
|
24 | 24 |
;; Description: |
25 | 25 |
;; |
... | ... |
@@ -84,7 +84,7 @@ |
84 | 84 |
(in-package :post-office) |
85 | 85 |
|
86 | 86 |
|
87 |
-(defparameter *imap-version-number* '(:major 1 :minor 0)) ; major.minor |
|
87 |
+(defparameter *imap-version-number* '(:major 1 :minor 1)) ; major.minor |
|
88 | 88 |
|
89 | 89 |
(defvar *debug-imap* nil) |
90 | 90 |
|
... | ... |
@@ -20,9 +20,21 @@ protocol. It is <strong>not</strong> an upward compatible successor. |
20 | 20 |
protocol. Only one small section describes the functions in the <strong>pop</strong> |
21 | 21 |
interface.</p> |
22 | 22 |
|
23 |
-<p align="left">This document and interface is based on the Imap4rev1 protocol described |
|
24 |
-in rfc2060. Where this document is describing the actions of the imap commands |
|
25 |
-it should be considered a secondary source of information about those commands and rfc2060 |
|
23 |
+<p align="left">The contents of this document are:</p> |
|
24 |
+ |
|
25 |
+<ul> |
|
26 |
+ <li><p align="left">the <strong>imap</strong> interface</p> |
|
27 |
+ </li> |
|
28 |
+ <li><p align="left"><a href="#pop">the <strong>pop</strong> interface</a></p> |
|
29 |
+ </li> |
|
30 |
+ <li><p align="left"><a href="#smtp">the <strong>smtp</strong> interface</a> (used for |
|
31 |
+ sending mail)</p> |
|
32 |
+ </li> |
|
33 |
+</ul> |
|
34 |
+ |
|
35 |
+<p align="left">The imap interface is based on the Imap4rev1 protocol described in |
|
36 |
+rfc2060. Where this document is describing the actions of the imap commands it |
|
37 |
+should be considered a secondary source of information about those commands and rfc2060 |
|
26 | 38 |
should be considered the primary source.</p> |
27 | 39 |
|
28 | 40 |
<p align="left">The advantages of <strong>imap</strong> over <strong>pop</strong> are:</p> |
... | ... |
@@ -68,12 +80,12 @@ between simple names to create a hierarchical name (in this example "/" |
68 | 80 |
separator character). </p> |
69 | 81 |
|
70 | 82 |
<p align="left">Each mailbox has an associated unique number called its <strong>uidvalidity</strong>. |
71 |
- This number won't change as long as only <strong>imap</strong> is the |
|
72 |
-only program used to manipulate the mailbox. In fact if you see that the |
|
73 |
-number has changed then that means that some other program has done something to the |
|
74 |
-mailbox that destroyed the information that <strong>imap</strong> had been keeping about |
|
75 |
-the mailbox. In particular you can't now retrieve messages by their |
|
76 |
-unique ids that you had used before.</p> |
|
83 |
+ This number won't change as long as <strong>imap</strong> is the only |
|
84 |
+program used to manipulate the mailbox. In fact if you see that the number has |
|
85 |
+changed then that means that some other program has done something to the mailbox that |
|
86 |
+destroyed the information that <strong>imap</strong> had been keeping about the |
|
87 |
+mailbox. In particular you can't now retrieve messages by their unique |
|
88 |
+ids that you had used before.</p> |
|
77 | 89 |
|
78 | 90 |
<h1 align="left">Messages</h1> |
79 | 91 |
|
... | ... |
@@ -816,7 +828,7 @@ t |
816 | 828 |
|
817 | 829 |
<p align="left"> </p> |
818 | 830 |
|
819 |
-<h1>The Pop interface</h1> |
|
831 |
+<h1><a name="pop"></a>The Pop interface</h1> |
|
820 | 832 |
|
821 | 833 |
<p>The <strong>pop</strong> protocol is a very simple means for retreiving messages from a |
822 | 834 |
single mailbox. The functions in the interface are:</p> |
... | ... |
@@ -885,6 +897,67 @@ will contain the current count of messages in the mailbox.</p> |
885 | 897 |
|
886 | 898 |
<p> </p> |
887 | 899 |
|
900 |
+<h1><a name="smtp"></a>The smtp interface</h1> |
|
901 |
+ |
|
902 |
+<p>With the smtp interface, a Lisp program can contact a mail server and send electronic |
|
903 |
+mail. The contents of the message must be a simple text string. There is |
|
904 |
+no provision for encoding binary data and sending it as a Mime attachment.</p> |
|
905 |
+ |
|
906 |
+<p> </p> |
|
907 |
+ |
|
908 |
+<p><font face="Courier New"><strong>(send-letter mail-server from to message &key |
|
909 |
+subject reply-to)</strong></font></p> |
|
910 |
+ |
|
911 |
+<p><strong>mail-server</strong> can be a string naming a machine or an integer IP address. |
|
912 |
+ The <strong>mail-server</strong> is contacted and asked to send a <strong>message</strong> |
|
913 |
+(a string) <strong>from</strong> a given email address <strong>to</strong> a given email |
|
914 |
+address or list of addresses. The email addresses must be of the form |
|
915 |
+"foo" or <a href="mailto:foo@bar.com">"foo@bar.com"</a>. You can |
|
916 |
+<strong>not</strong> use addresses like <a href="mailto:Joe%20%3cfoo@bar.com%3e">"Joe |
|
917 |
+<foo@bar.com>"</a> or <a href="mailto:(Joe)%20foo@bar.com">"(Joe) |
|
918 |
+foo@bar.com"</a>. </p> |
|
919 |
+ |
|
920 |
+<p>A mail header is built and prepended to the <strong>message</strong> before it is sent. |
|
921 |
+ The mail header includes a <strong>From </strong>and <strong>To</strong> line and |
|
922 |
+will optionally include a <strong>Subject</strong> and <strong>Reply-To</strong> |
|
923 |
+line if those are given in the call to <strong>send-letter.</strong>.</p> |
|
924 |
+ |
|
925 |
+<p>The text of the <strong>message</strong> should be lines separated by #\newline's. |
|
926 |
+ The <strong>smtp</strong> interface will automatically insert the necessary |
|
927 |
+#\returns's when it transmits the message to the mail server.</p> |
|
928 |
+ |
|
929 |
+<p> </p> |
|
930 |
+ |
|
931 |
+<p> </p> |
|
932 |
+ |
|
933 |
+<p><font face="Courier New"><strong>(send-smtp mail-server from to &rest messages)</strong></font></p> |
|
934 |
+ |
|
935 |
+<p><strong>mail-server</strong> can be a string naming a machine or an integer IP address. |
|
936 |
+ The <strong>mail-server</strong> is contacted and asked to send a message <strong>from</strong> |
|
937 |
+a given email address <strong>to</strong> a given email address or list of addresses. |
|
938 |
+ The email addresses must be of the form "foo" or <a |
|
939 |
+href="mailto:foo@bar.com">"foo@bar.com"</a>. You can <strong>not</strong> |
|
940 |
+use addresses like <a href="mailto:Joe%20%3cfoo@bar.com%3e">"Joe |
|
941 |
+<foo@bar.com>"</a> or <a href="mailto:(Joe)%20foo@bar.com">"(Joe) |
|
942 |
+foo@bar.com"</a>. </p> |
|
943 |
+ |
|
944 |
+<p>The message sent is a concatenation of all of the <strong>messages</strong> (which |
|
945 |
+should be strings). A header is <strong>not</strong> prepended to the message. |
|
946 |
+ This means that the application program can build its own header if it wants to |
|
947 |
+include in that header more than <strong>send-letter</strong> supports (e.g. a Mime |
|
948 |
+encoded attachment). If no header is provided then some mail servers (e.g. <strong>sendmail</strong>) |
|
949 |
+will notice this fact and will automatically create a header.</p> |
|
950 |
+ |
|
951 |
+<p>The text of the <strong>messages</strong> should be lines separated by #\newline's. |
|
952 |
+ The <strong>smtp</strong> interface will automatically insert the necessary |
|
953 |
+#\returns's when it transmits the message to the mail server.</p> |
|
954 |
+ |
|
955 |
+<p> </p> |
|
956 |
+ |
|
957 |
+<p> </p> |
|
958 |
+ |
|
959 |
+<p> </p> |
|
960 |
+ |
|
888 | 961 |
<p> </p> |
889 | 962 |
</body> |
890 | 963 |
</html> |
891 | 964 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,290 @@ |
1 |
+;; mail sending package |
|
2 |
+;; |
|
3 |
+;; smtp - rfc821 |
|
4 |
+;; |
|
5 |
+(defpackage :post-office |
|
6 |
+ (:nicknames :po) |
|
7 |
+ (:use #:lisp #:excl) |
|
8 |
+ (:export |
|
9 |
+ #:send-letter |
|
10 |
+ #:send-smtp)) |
|
11 |
+ |
|
12 |
+(in-package :post-office) |
|
13 |
+ |
|
14 |
+;; the exported functions: |
|
15 |
+ |
|
16 |
+;; (send-letter "mail-server" "from" "to" "message" &key subject reply-to) |
|
17 |
+;; |
|
18 |
+;; sends a message to the mail server (which may be a relay server |
|
19 |
+;; or the final destination). "from" is the address to be given |
|
20 |
+;; as the sender. "to" can be a string or a list of strings naming |
|
21 |
+;; recipients. |
|
22 |
+;; "message" is the message to be sent |
|
23 |
+;; This builds a header and inserts the optional subject and reply-to |
|
24 |
+;; lines. |
|
25 |
+;; |
|
26 |
+;; (send-smtp "mail-server" "from" "to" &rest messages) |
|
27 |
+;; this is like send-letter except that it doesn't build a header. |
|
28 |
+;; the messages should contain a header (and if not then sendmail |
|
29 |
+;; notices this and builds one -- other MTAs may not be that smart). |
|
30 |
+;; The messages ia list of string to be concatenated together |
|
31 |
+;; and sent as one message |
|
32 |
+;; |
|
33 |
+;; |
|
34 |
+ |
|
35 |
+ |
|
36 |
+ |
|
37 |
+ |
|
38 |
+ |
|
39 |
+ |
|
40 |
+ |
|
41 |
+ |
|
42 |
+ |
|
43 |
+ |
|
44 |
+ |
|
45 |
+(defmacro response-case ((ftp-stream) &rest case-clauses) |
|
46 |
+ (let ((response-class (gensym))) |
|
47 |
+ `(multiple-value-bind (,response-class ftp-response) |
|
48 |
+ (progn (force-output ,ftp-stream) |
|
49 |
+ (wait-for-response ,ftp-stream)) |
|
50 |
+ (declare (ignorable ftp-response)) |
|
51 |
+ (case ,response-class |
|
52 |
+ ,@case-clauses)))) |
|
53 |
+ |
|
54 |
+(defvar *smtp-debug* nil) |
|
55 |
+ |
|
56 |
+ |
|
57 |
+ |
|
58 |
+(defun send-letter (server from to message |
|
59 |
+ &key subject |
|
60 |
+ reply-to) |
|
61 |
+ (let ((header (make-string-output-stream))) |
|
62 |
+ (format header "From: ~a~c~cTo: " |
|
63 |
+ from |
|
64 |
+ #\return |
|
65 |
+ #\linefeed) |
|
66 |
+ (let ((tos (if* (stringp to) |
|
67 |
+ then (list to) |
|
68 |
+ elseif (consp to) |
|
69 |
+ then to |
|
70 |
+ else (error "to should be a string or list, not ~s" |
|
71 |
+ to)))) |
|
72 |
+ (format header "~{ ~a~^,~}~c~c" tos #\return #\linefeed)) |
|
73 |
+ |
|
74 |
+ (if* subject |
|
75 |
+ then (format header "Subject: ~a~c~c" subject #\return #\linefeed)) |
|
76 |
+ |
|
77 |
+ (if* reply-to |
|
78 |
+ then (format header "Reply-To: ~a~c~c" reply-to #\return #\linefeed)) |
|
79 |
+ |
|
80 |
+ |
|
81 |
+ (format header "~c~c" #\return #\linefeed) |
|
82 |
+ |
|
83 |
+ (send-smtp server from to (get-output-stream-string header) message) |
|
84 |
+ |
|
85 |
+ |
|
86 |
+ |
|
87 |
+ )) |
|
88 |
+ |
|
89 |
+ |
|
90 |
+ |
|
91 |
+ |
|
92 |
+ |
|
93 |
+(defun send-smtp (server from to &rest messages) |
|
94 |
+ ;; send the effective concatenation of the messages via |
|
95 |
+ ;; smtp to the mail server |
|
96 |
+ ;; Each message should be a string |
|
97 |
+ ;; |
|
98 |
+ ;; 'to' can be a single string or a list of strings. |
|
99 |
+ ;; each string should be in the official rfc822 format "foo@bar.com" |
|
100 |
+ ;; |
|
101 |
+ (let ((sock (socket:make-socket :remote-host server |
|
102 |
+ :remote-port 25 ; smtp |
|
103 |
+ ))) |
|
104 |
+ (unwind-protect |
|
105 |
+ (progn |
|
106 |
+ (response-case (sock) |
|
107 |
+ (2 ;; to the initial connect |
|
108 |
+ nil) |
|
109 |
+ (t (error "initial connect failed"))) |
|
110 |
+ |
|
111 |
+ ;; now that we're connected we can compute our hostname |
|
112 |
+ (let ((hostname (socket:ipaddr-to-hostname |
|
113 |
+ (socket:local-host sock)))) |
|
114 |
+ (if* (null hostname) |
|
115 |
+ then (format nil "[~a]" (socket:ipaddr-to-dotted |
|
116 |
+ (socket:local-host sock)))) |
|
117 |
+ (smtp-command sock "HELO ~a" hostname) |
|
118 |
+ (response-case (sock) |
|
119 |
+ (2 ;; ok |
|
120 |
+ nil) |
|
121 |
+ (t (error "hello greeting failed")))) |
|
122 |
+ |
|
123 |
+ (smtp-command sock "MAIL from:<~a>" from) |
|
124 |
+ (response-case (sock) |
|
125 |
+ (2 ;; cool |
|
126 |
+ nil |
|
127 |
+ ) |
|
128 |
+ (t (error "Mail from command failed"))) |
|
129 |
+ |
|
130 |
+ (let ((tos (if* (stringp to) |
|
131 |
+ then (list to) |
|
132 |
+ elseif (consp to) |
|
133 |
+ then to |
|
134 |
+ else (error "to should be a string or list, not ~s" |
|
135 |
+ to)))) |
|
136 |
+ (dolist (to tos) |
|
137 |
+ (smtp-command sock "RCPT to:<~a>" to) |
|
138 |
+ (response-case (sock) |
|
139 |
+ (2 ;; cool |
|
140 |
+ nil |
|
141 |
+ ) |
|
142 |
+ (t (error "rcpt to command failed"))))) |
|
143 |
+ |
|
144 |
+ (smtp-command sock "DATA") |
|
145 |
+ (response-case (sock) |
|
146 |
+ (3 ;; cool |
|
147 |
+ nil) |
|
148 |
+ (t (error "Data command failed"))) |
|
149 |
+ ;(format t "sending message~%") (force-output t) |
|
150 |
+ |
|
151 |
+ |
|
152 |
+ (let ((at-bol t)) |
|
153 |
+ (dolist (message messages) |
|
154 |
+ (dotimes (i (length message)) |
|
155 |
+ (let ((ch (aref message i))) |
|
156 |
+ (if* (and at-bol (eq ch #\.)) |
|
157 |
+ then ; to prevent . from being interpreted as eol |
|
158 |
+ (write-char #\. sock)) |
|
159 |
+ (if* (eq ch #\newline) |
|
160 |
+ then (setq at-bol t) |
|
161 |
+ (write-char #\return sock) |
|
162 |
+ else (setq at-bol nil)) |
|
163 |
+ (write-char ch sock))))) |
|
164 |
+ |
|
165 |
+ (write-char #\return sock) (write-char #\linefeed sock) |
|
166 |
+ (write-char #\. sock) |
|
167 |
+ (write-char #\return sock) (write-char #\linefeed sock) |
|
168 |
+ |
|
169 |
+ (response-case (sock) |
|
170 |
+ (2 nil ; (format t "Message sent to ~a~%" to) |
|
171 |
+ ) |
|
172 |
+ |
|
173 |
+ (t (error "message not sent"))) |
|
174 |
+ |
|
175 |
+ (force-output t) |
|
176 |
+ |
|
177 |
+ (smtp-command sock "QUIT") |
|
178 |
+ (response-case (sock) |
|
179 |
+ (2 ;; cool |
|
180 |
+ nil) |
|
181 |
+ (t (error "quit failed")))) |
|
182 |
+ (close sock)))) |
|
183 |
+ |
|
184 |
+ |
|
185 |
+ |
|
186 |
+ |
|
187 |
+ |
|
188 |
+ |
|
189 |
+ |
|
190 |
+ |
|
191 |
+ |
|
192 |
+(defun wait-for-response (stream) |
|
193 |
+ ;; read the response of the ftp server. |
|
194 |
+ ;; collect it all in a string. |
|
195 |
+ ;; Return two values: |
|
196 |
+ ;; response class |
|
197 |
+ ;; whole string |
|
198 |
+ ;; The string should begin with a decimal digit, and that is converted |
|
199 |
+ ;; into a number which is returned as the response class. |
|
200 |
+ ;; If the string doesn't begin with a decimal digit then the |
|
201 |
+ ;; response class is -1. |
|
202 |
+ ;; |
|
203 |
+ (flet ((match-chars (string pos1 pos2 count) |
|
204 |
+ ;; like strncmp |
|
205 |
+ (dotimes (i count t) |
|
206 |
+ (if* (not (eq (aref string (+ pos1 i)) |
|
207 |
+ (aref string (+ pos2 i)))) |
|
208 |
+ then (return nil))))) |
|
209 |
+ |
|
210 |
+ (let ((res (make-array 20 :element-type 'character |
|
211 |
+ :adjustable t |
|
212 |
+ :fill-pointer 0))) |
|
213 |
+ (if* (null (read-a-line stream res)) |
|
214 |
+ then ; eof encountered before end of line |
|
215 |
+ (return-from wait-for-response (values -1 res))) |
|
216 |
+ |
|
217 |
+ ;; a multi-line response begins with line containing |
|
218 |
+ ;; a hyphen in the 4th column: |
|
219 |
+ ;; xyz- some text |
|
220 |
+ ;; |
|
221 |
+ ;; and ends with a line containing the same reply code but no |
|
222 |
+ ;; hyphen. |
|
223 |
+ ;; xyz some text |
|
224 |
+ ;; |
|
225 |
+ |
|
226 |
+ (if* (and (>= (length res) 4) (eq #\- (aref res 3))) |
|
227 |
+ then ;; multi line response |
|
228 |
+ (let ((old-length (length res)) |
|
229 |
+ (new-length nil)) |
|
230 |
+ (loop |
|
231 |
+ (if* (null (read-a-line stream res)) |
|
232 |
+ then ; eof encountered before end of line |
|
233 |
+ (return-from wait-for-response (values -1 res))) |
|
234 |
+ (setq new-length (length res)) |
|
235 |
+ ;; see if this is the last line |
|
236 |
+ (if* (and (>= (- new-length old-length) 4) |
|
237 |
+ (eq (aref res (+ old-length 3)) #\space) |
|
238 |
+ (match-chars res 0 old-length 3)) |
|
239 |
+ then (return)) |
|
240 |
+ |
|
241 |
+ (setq old-length new-length)))) |
|
242 |
+ |
|
243 |
+ ;; complete response is in res |
|
244 |
+ ;; compute class and return the whole thing |
|
245 |
+ (let ((class (or (and (> (length res) 0) |
|
246 |
+ (digit-char-p (aref res 0))) |
|
247 |
+ -1))) |
|
248 |
+ (values class res))))) |
|
249 |
+ |
|
250 |
+(defun smtp-command (stream &rest format-args) |
|
251 |
+ ;; send a command to the smtp server |
|
252 |
+ (let ((command (apply #'format nil format-args))) |
|
253 |
+ (if* *smtp-debug* |
|
254 |
+ then (format *smtp-debug* "to smtp command: ~s~%" command) |
|
255 |
+ (force-output *smtp-debug*)) |
|
256 |
+ (write-string command stream) |
|
257 |
+ (write-char #\return stream) |
|
258 |
+ (write-char #\newline stream) |
|
259 |
+ (force-output stream))) |
|
260 |
+ |
|
261 |
+(defun read-a-line (stream res) |
|
262 |
+ ;; read from stream and put the result in the adjust able array res |
|
263 |
+ ;; if line ends in cr-lf, only put a newline in res. |
|
264 |
+ ;; If we get an eof before the line finishes, return nil, |
|
265 |
+ ;; else return t if all is ok |
|
266 |
+ (let (ch last-ch) |
|
267 |
+ (loop |
|
268 |
+ (setq ch (read-char stream nil nil)) |
|
269 |
+ (if* (null ch) |
|
270 |
+ then ; premature eof |
|
271 |
+ (return nil)) |
|
272 |
+ |
|
273 |
+ (if* *smtp-debug* |
|
274 |
+ then (format *smtp-debug* "~c" ch) |
|
275 |
+ (force-output *smtp-debug*) |
|
276 |
+ ) |
|
277 |
+ |
|
278 |
+ (if* (eq last-ch #\return) |
|
279 |
+ then (if* (eq ch #\linefeed) |
|
280 |
+ then (vector-push-extend #\newline res) |
|
281 |
+ (return t) |
|
282 |
+ else (vector-push-extend last-ch res)) |
|
283 |
+ elseif (eq ch #\linefeed) |
|
284 |
+ then ; line ends with just lf, not cr-lf |
|
285 |
+ (vector-push-extend #\newline res) |
|
286 |
+ (return t) |
|
287 |
+ elseif (not (eq ch #\return)) |
|
288 |
+ then (vector-push-extend ch res)) |
|
289 |
+ |
|
290 |
+ (setq last-ch ch)))) |