1 ! Copyright (C) 2007, 2008 Elie CHAFTARI, Dirk Vleugels,
2 ! Slava Pestov, Doug Coleman.
3 ! See http://factorcode.org/license.txt for BSD license.
4 USING: arrays namespaces make io io.encodings.string
5 io.encodings.utf8 io.timeouts io.sockets io.sockets.secure
6 io.encodings.ascii kernel logging sequences combinators
7 splitting assocs strings math.order math.parser random system
8 calendar summary calendar.format accessors sets hashtables
9 base64 debugger classes prettyprint ;
15 "localhost" 25 <inet> smtp-server set-global
19 SYMBOL: smtp-read-timeout
20 1 minutes smtp-read-timeout set-global
24 TUPLE: plain-auth username password ;
25 C: <plain-auth> plain-auth
28 no-auth smtp-auth set-global
30 LOG: log-smtp-connection NOTICE ( addrspec -- )
32 : with-smtp-connection ( quot -- )
34 dup log-smtp-connection
36 smtp-domain [ host-name or ] change
37 smtp-read-timeout get timeouts
39 ] with-client ; inline
49 : <email> ( -- email ) email new ; inline
53 : crlf ( -- ) "\r\n" write ;
55 : read-crlf ( -- bytes )
57 [ CHAR: \r assert= read1 CHAR: \n assert= ] when* ;
59 : command ( string -- ) write crlf flush ;
61 \ command DEBUG add-input-logging
63 : helo ( -- ) "EHLO " host-name append command ;
65 : start-tls ( -- ) "STARTTLS" command ;
67 ERROR: bad-email-address email ;
69 : validate-address ( string -- string' )
70 #! Make sure we send funky stuff to the server by accident.
71 dup "\r\n>" intersect empty?
72 [ bad-email-address ] unless ;
74 : mail-from ( fromaddr -- )
76 "MAIL FROM:<" ">" surround command ;
80 "RCPT TO:<" ">" surround command ;
85 ERROR: message-contains-dot message ;
87 M: message-contains-dot summary ( obj -- string )
88 drop "Message cannot contain . on a line by itself" ;
90 : validate-message ( msg -- msg' )
92 [ message-contains-dot ] when ;
94 : send-body ( body -- )
103 LOG: smtp-response DEBUG
105 : multiline? ( response -- boolean )
106 3 swap ?nth CHAR: - = ;
108 : (receive-response) ( -- )
112 [ multiline? [ (receive-response) ] when ]
115 TUPLE: response code messages ;
117 : <response> ( lines -- response )
118 [ first 3 head string>number ] keep response boa ;
120 : receive-response ( -- response )
121 [ (receive-response) ] { } make <response> ;
123 ERROR: smtp-error response ;
126 "SMTP error (" write dup class pprint ")" print
127 response>> messages>> [ print ] each ;
129 ERROR: smtp-server-busy < smtp-error ;
130 ERROR: smtp-syntax-error < smtp-error ;
131 ERROR: smtp-command-not-implemented < smtp-error ;
132 ERROR: smtp-bad-authentication < smtp-error ;
133 ERROR: smtp-mailbox-unavailable < smtp-error ;
134 ERROR: smtp-user-not-local < smtp-error ;
135 ERROR: smtp-exceeded-storage-allocation < smtp-error ;
136 ERROR: smtp-bad-mailbox-name < smtp-error ;
137 ERROR: smtp-transaction-failed < smtp-error ;
139 : check-response ( response -- )
141 { [ dup { 220 235 250 221 354 } member? ] [ 2drop ] }
142 { [ dup 400 499 between? ] [ drop smtp-server-busy ] }
143 { [ dup 500 = ] [ drop smtp-syntax-error ] }
144 { [ dup 501 = ] [ drop smtp-command-not-implemented ] }
145 { [ dup 500 509 between? ] [ drop smtp-syntax-error ] }
146 { [ dup 530 539 between? ] [ drop smtp-bad-authentication ] }
147 { [ dup 550 = ] [ drop smtp-mailbox-unavailable ] }
148 { [ dup 551 = ] [ drop smtp-user-not-local ] }
149 { [ dup 552 = ] [ drop smtp-exceeded-storage-allocation ] }
150 { [ dup 553 = ] [ drop smtp-bad-mailbox-name ] }
151 { [ dup 554 = ] [ drop smtp-transaction-failed ] }
155 : get-ok ( -- ) receive-response check-response ;
157 GENERIC: send-auth ( auth -- )
159 M: no-auth send-auth drop ;
161 : plain-auth-string ( username password -- string )
162 [ "\0" prepend ] bi@ append utf8 encode >base64 ;
164 M: plain-auth send-auth
165 [ username>> ] [ password>> ] bi plain-auth-string
166 "AUTH PLAIN " prepend command get-ok ;
168 : auth ( -- ) smtp-auth get send-auth ;
170 ERROR: invalid-header-string string ;
172 : validate-header ( string -- string' )
173 dup "\r\n" intersect empty?
174 [ invalid-header-string ] unless ;
176 : write-header ( key value -- )
177 [ validate-header write ]
178 [ ": " write validate-header write ] bi* crlf ;
180 : write-headers ( assoc -- )
181 [ write-header ] assoc-each ;
183 : message-id ( -- string )
190 smtp-domain get [ host-name ] unless* %
194 : extract-email ( recepient -- email )
195 ! This could be much smarter.
196 " " split1-last swap or "<" ?head drop ">" ?tail drop ;
198 : email>headers ( email -- hashtable )
201 [ from>> "From" set ]
202 [ to>> ", " join "To" set ]
203 [ cc>> ", " join [ "Cc" set ] unless-empty ]
204 [ subject>> "Subject" set ]
206 now timestamp>rfc822 "Date" set
207 message-id "Message-Id" set
210 : (send-email) ( headers email -- )
214 smtp-tls? get [ start-tls get-ok send-secure-handshake ] when
216 dup from>> extract-email mail-from get-ok
217 dup to>> [ extract-email rcpt-to get-ok ] each
218 dup cc>> [ extract-email rcpt-to get-ok ] each
219 dup bcc>> [ extract-email rcpt-to get-ok ] each
223 body>> send-body get-ok
225 ] with-smtp-connection ;
229 : send-email ( email -- )
230 [ email>headers ] keep (send-email) ;