git.fiddlerwoaroof.com
Browse code

smtp changes

jkf authored on 29/09/1999 16:25:23
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.&nbsp;&nbsp; It is <strong>not</strong> an upward compatible successor.
20 20
 protocol.&nbsp;&nbsp;&nbsp; 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.&nbsp;&nbsp; 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.&nbsp;&nbsp; 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 &quot;/&quot
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
-&nbsp;&nbsp;&nbsp; This number won't change as long as only <strong>imap</strong> is the
72
-only program used to manipulate the mailbox.&nbsp;&nbsp; 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.&nbsp;&nbsp;&nbsp; In particular you can't now retrieve messages by their
76
-unique ids that you had used before.</p>
83
+&nbsp;&nbsp;&nbsp; This number won't change as long as <strong>imap</strong> is the only
84
+program used to manipulate the mailbox.&nbsp;&nbsp; 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.&nbsp;&nbsp;&nbsp; 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">&nbsp;</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.&nbsp;&nbsp;&nbsp;&nbsp; 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>&nbsp;</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.&nbsp;&nbsp; The contents of the message must be a simple text string.&nbsp; There is
904
+no provision for encoding binary data and sending it as a Mime attachment.</p>
905
+
906
+<p>&nbsp;</p>
907
+
908
+<p><font face="Courier New"><strong>(send-letter mail-server from to message &amp;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
+&nbsp; 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.&nbsp;&nbsp; The email addresses must be of the form
915
+&quot;foo&quot; or <a href="mailto:foo@bar.com">&quot;foo@bar.com&quot;</a>.&nbsp; You can
916
+<strong>not</strong> use addresses like <a href="mailto:Joe%20%3cfoo@bar.com%3e">&quot;Joe
917
+&lt;foo@bar.com&gt;&quot;</a> or <a href="mailto:(Joe)%20foo@bar.com">&quot;(Joe)
918
+foo@bar.com&quot;</a>.&nbsp;&nbsp; </p>
919
+
920
+<p>A mail header is built and prepended to the <strong>message</strong> before it is sent.
921
+&nbsp; The mail header includes a <strong>From </strong>and <strong>To</strong> line and
922
+will optionally include a&nbsp; <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
+&nbsp;&nbsp; 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>&nbsp;</p>
930
+
931
+<p>&nbsp;</p>
932
+
933
+<p><font face="Courier New"><strong>(send-smtp mail-server from to &amp;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
+&nbsp; The <strong>mail-server</strong> is contacted and asked to send a&nbsp; message <strong>from</strong>
937
+a given email address <strong>to</strong> a given email address or list of addresses.
938
+&nbsp;&nbsp; The email addresses must be of the form &quot;foo&quot; or <a
939
+href="mailto:foo@bar.com">&quot;foo@bar.com&quot;</a>.&nbsp; You can <strong>not</strong>
940
+use addresses like <a href="mailto:Joe%20%3cfoo@bar.com%3e">&quot;Joe
941
+&lt;foo@bar.com&gt;&quot;</a> or <a href="mailto:(Joe)%20foo@bar.com">&quot;(Joe)
942
+foo@bar.com&quot;</a>.&nbsp;&nbsp; </p>
943
+
944
+<p>The message sent is a concatenation of all of the <strong>messages</strong> (which
945
+should be strings).&nbsp;&nbsp; A header is <strong>not</strong> prepended to the message.
946
+&nbsp; 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).&nbsp; 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
+&nbsp;&nbsp; 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>&nbsp;</p>
956
+
957
+<p>&nbsp;</p>
958
+
959
+<p>&nbsp;</p>
960
+
888 961
 <p>&nbsp;</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))))
... ...
@@ -66,7 +66,7 @@
66 66
     
67 67
 	  ;; send five letters
68 68
 	  (dotimes (i 5)
69
-	    (smtp:send-smtp *test-machine*
69
+	    (po:send-smtp *test-machine*
70 70
 			    *test-email*
71 71
 			    *test-email*
72 72
 			    (format nil "message number ~d" (1+ i))))