1 ! Copyright (C) 2007, 2008 Elie CHAFTARI, Dirk Vleugels,
3 ! See http://factorcode.org/license.txt for BSD license.
4 USING: namespaces io io.timeouts kernel logging io.sockets
5 sequences combinators sequences.lib splitting assocs strings
6 math.parser random system calendar io.encodings.ascii
7 calendar.format accessors sets ;
11 SYMBOL: smtp-server "localhost" "smtp" <inet> smtp-server set-global
12 SYMBOL: read-timeout 1 minutes read-timeout set-global
13 SYMBOL: esmtp t esmtp set-global
15 LOG: log-smtp-connection NOTICE ( addrspec -- )
17 : with-smtp-connection ( quot -- )
19 dup log-smtp-connection
21 smtp-domain [ host-name or ] change
22 read-timeout get timeouts
24 ] with-client ; inline
26 : crlf ( -- ) "\r\n" write ;
28 : command ( string -- ) write crlf flush ;
31 esmtp get "EHLO " "HELO " ? host-name append command ;
33 : validate-address ( string -- string' )
34 #! Make sure we send funky stuff to the server by accident.
35 dup "\r\n>" intersect empty?
36 [ "Bad e-mail address: " prepend throw ] unless ;
38 : mail-from ( fromaddr -- )
39 "MAIL FROM:<" swap validate-address ">" 3append command ;
42 "RCPT TO:<" swap validate-address ">" 3append command ;
47 : validate-message ( msg -- msg' )
48 "." over member? [ "Message cannot contain . on a line by itself" throw ] when ;
50 : send-body ( body -- )
59 LOG: smtp-response DEBUG
61 : check-response ( response -- )
63 { [ dup "220" head? ] [ smtp-response ] }
64 { [ dup "235" swap subseq? ] [ smtp-response ] }
65 { [ dup "250" head? ] [ smtp-response ] }
66 { [ dup "221" head? ] [ smtp-response ] }
67 { [ dup "bye" head? ] [ smtp-response ] }
68 { [ dup "4" head? ] [ "server busy" throw ] }
69 { [ dup "354" head? ] [ smtp-response ] }
70 { [ dup "50" head? ] [ smtp-response "syntax error" throw ] }
71 { [ dup "53" head? ] [ smtp-response "invalid authentication data" throw ] }
72 { [ dup "55" head? ] [ smtp-response "fatal error" throw ] }
73 [ "unknown error" throw ]
76 : multiline? ( response -- boolean )
79 : process-multiline ( multiline -- response )
80 >r readln r> 2dup " " append head? [
81 drop dup smtp-response
83 swap check-response process-multiline
86 : receive-response ( -- response )
88 dup multiline? [ 3 head process-multiline ] when ;
90 : get-ok ( -- ) receive-response check-response ;
92 : validate-header ( string -- string' )
93 dup "\r\n" intersect empty?
94 [ "Invalid header string: " prepend throw ] unless ;
96 : write-header ( key value -- )
100 validate-header write
103 : write-headers ( assoc -- )
104 [ write-header ] assoc-each ;
106 TUPLE: email from to subject headers body ;
109 call-next-method [ clone ] change-headers ;
111 : (send) ( email -- )
114 dup from>> mail-from get-ok
115 dup to>> [ rcpt-to get-ok ] each
117 dup headers>> write-headers
119 body>> send-body get-ok
121 ] with-smtp-connection ;
123 : extract-email ( recepient -- email )
124 #! This could be much smarter.
125 " " last-split1 swap or "<" ?head drop ">" ?tail drop ;
127 : message-id ( -- string )
134 smtp-domain get [ host-name ] unless* %
138 : set-header ( email value key -- email )
139 pick headers>> set-at ;
141 : prepare ( email -- email )
143 dup from>> "From" set-header
144 [ extract-email ] change-from
145 dup to>> ", " join "To" set-header
146 [ [ extract-email ] map ] change-to
147 dup subject>> "Subject" set-header
148 now timestamp>rfc822 "Date" set-header
149 message-id "Message-Id" set-header ;
151 : <email> ( -- email )
153 H{ } clone >>headers ;
155 : send-email ( email -- )
158 ! Dirk's old AUTH CRAM-MD5 code. I don't know anything about
159 ! CRAM MD5, and the old code didn't work properly either, so here
160 ! it is in case anyone wants to fix it later.
162 ! check-response used to have this clause:
163 ! { [ dup "334" head? ] [ " " split 1 swap nth base64> challenge set ] }
165 ! and the rest of the code was as follows:
166 ! : (cram-md5-auth) ( -- response )
168 ! string>md5-hmac hex-string
172 ! : cram-md5-auth ( key login -- )
173 ! "AUTH CRAM-MD5\r\n" get-ok
174 ! (cram-md5-auth) "\r\n" append get-ok ;
176 ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!