factor/basis/smtp/smtp.factor

238 lines
6.1 KiB
Factor
Raw Normal View History

2008-02-09 22:34:42 -05:00
! Copyright (C) 2007, 2008 Elie CHAFTARI, Dirk Vleugels,
2008-08-29 00:19:18 -04:00
! Slava Pestov, Doug Coleman.
2007-09-20 18:09:08 -04:00
! See http://factorcode.org/license.txt for BSD license.
USING: arrays namespaces make io io.encodings.string
io.encodings.utf8 io.timeouts io.sockets io.sockets.secure
io.encodings.ascii kernel logging sequences combinators
splitting assocs strings math.order math.parser random system
calendar summary calendar.format accessors sets hashtables
2009-01-27 19:42:17 -05:00
base64 debugger classes prettyprint io.crlf ;
2007-09-20 18:09:08 -04:00
IN: smtp
SYMBOL: smtp-domain
SYMBOL: smtp-server
"localhost" 25 <inet> smtp-server set-global
SYMBOL: smtp-tls?
SYMBOL: smtp-read-timeout
1 minutes smtp-read-timeout set-global
SINGLETON: no-auth
TUPLE: plain-auth username password ;
C: <plain-auth> plain-auth
SYMBOL: smtp-auth
no-auth smtp-auth set-global
LOG: log-smtp-connection NOTICE ( addrspec -- )
: with-smtp-connection ( quot -- )
smtp-server get
dup log-smtp-connection
ascii [
2008-02-07 18:07:43 -05:00
smtp-domain [ host-name or ] change
2008-08-29 00:19:18 -04:00
smtp-read-timeout get timeouts
2008-02-07 18:07:43 -05:00
call
] with-client ; inline
TUPLE: email
{ from string }
{ to array }
2008-08-16 18:28:38 -04:00
{ cc array }
{ bcc array }
{ subject string }
{ body string } ;
: <email> ( -- email ) email new ; inline
2008-08-29 00:19:18 -04:00
<PRIVATE
2008-03-20 15:25:23 -04:00
: command ( string -- ) write crlf flush ;
\ command DEBUG add-input-logging
: helo ( -- ) "EHLO " host-name append command ;
2007-09-20 18:09:08 -04:00
: start-tls ( -- ) "STARTTLS" command ;
2008-08-16 15:20:13 -04:00
ERROR: bad-email-address email ;
: validate-address ( string -- string' )
#! Make sure we send funky stuff to the server by accident.
dup "\r\n>" intersects?
[ bad-email-address ] when ;
2007-09-20 18:09:08 -04:00
: mail-from ( fromaddr -- )
2008-12-06 19:58:45 -05:00
validate-address
"MAIL FROM:<" ">" surround command ;
2007-09-20 18:09:08 -04:00
: rcpt-to ( to -- )
2008-12-06 19:58:45 -05:00
validate-address
"RCPT TO:<" ">" surround command ;
2007-09-20 18:09:08 -04:00
: data ( -- )
2008-03-20 15:25:23 -04:00
"DATA" command ;
2007-09-20 18:09:08 -04:00
2008-08-16 15:20:13 -04:00
ERROR: message-contains-dot message ;
M: message-contains-dot summary ( obj -- string )
drop "Message cannot contain . on a line by itself" ;
2008-08-16 15:20:13 -04:00
: validate-message ( msg -- msg' )
2008-08-16 15:20:13 -04:00
"." over member?
[ message-contains-dot ] when ;
2007-09-20 18:09:08 -04:00
: send-body ( body -- )
utf8 encode
>base64-lines write crlf
2008-03-20 15:25:23 -04:00
"." command ;
2007-09-20 18:09:08 -04:00
: quit ( -- )
2008-03-20 15:25:23 -04:00
"QUIT" command ;
2007-09-20 18:09:08 -04:00
2008-02-07 18:07:43 -05:00
LOG: smtp-response DEBUG
2007-09-20 18:09:08 -04:00
2008-12-11 19:52:22 -05:00
: multiline? ( response -- ? )
3 swap ?nth CHAR: - = ;
: (receive-response) ( -- )
read-crlf
[ , ]
[ smtp-response ]
[ multiline? [ (receive-response) ] when ]
tri ;
TUPLE: response code messages ;
: <response> ( lines -- response )
[ first 3 head string>number ] keep response boa ;
: receive-response ( -- response )
[ (receive-response) ] { } make <response> ;
ERROR: smtp-error response ;
M: smtp-error error.
"SMTP error (" write dup class pprint ")" print
response>> messages>> [ print ] each ;
2008-08-16 15:14:20 -04:00
ERROR: smtp-server-busy < smtp-error ;
ERROR: smtp-syntax-error < smtp-error ;
ERROR: smtp-command-not-implemented < smtp-error ;
ERROR: smtp-bad-authentication < smtp-error ;
ERROR: smtp-mailbox-unavailable < smtp-error ;
ERROR: smtp-user-not-local < smtp-error ;
ERROR: smtp-exceeded-storage-allocation < smtp-error ;
ERROR: smtp-bad-mailbox-name < smtp-error ;
ERROR: smtp-transaction-failed < smtp-error ;
2007-09-20 18:09:08 -04:00
: check-response ( response -- )
dup code>> {
{ [ dup { 220 235 250 221 354 } member? ] [ 2drop ] }
{ [ dup 400 499 between? ] [ drop smtp-server-busy ] }
{ [ dup 500 = ] [ drop smtp-syntax-error ] }
{ [ dup 501 = ] [ drop smtp-command-not-implemented ] }
{ [ dup 500 509 between? ] [ drop smtp-syntax-error ] }
{ [ dup 530 539 between? ] [ drop smtp-bad-authentication ] }
{ [ dup 550 = ] [ drop smtp-mailbox-unavailable ] }
{ [ dup 551 = ] [ drop smtp-user-not-local ] }
{ [ dup 552 = ] [ drop smtp-exceeded-storage-allocation ] }
{ [ dup 553 = ] [ drop smtp-bad-mailbox-name ] }
{ [ dup 554 = ] [ drop smtp-transaction-failed ] }
[ drop smtp-error ]
2007-09-20 18:09:08 -04:00
} cond ;
2008-03-20 15:25:23 -04:00
: get-ok ( -- ) receive-response check-response ;
GENERIC: send-auth ( auth -- )
M: no-auth send-auth drop ;
: plain-auth-string ( username password -- string )
[ "\0" prepend ] bi@ append utf8 encode >base64 ;
M: plain-auth send-auth
[ username>> ] [ password>> ] bi plain-auth-string
"AUTH PLAIN " prepend command get-ok ;
: auth ( -- ) smtp-auth get send-auth ;
: encode-header ( string -- string' )
dup aux>> [
"=?utf-8?B?"
swap utf8 encode >base64
"?=" 3append
] when ;
2008-08-16 15:20:13 -04:00
ERROR: invalid-header-string string ;
: validate-header ( string -- string' )
dup "\r\n" intersects?
[ invalid-header-string ] when ;
2008-03-07 02:55:38 -05:00
: write-header ( key value -- )
[ validate-header write ]
[ ": " write validate-header encode-header write ] bi* crlf ;
2008-03-07 02:55:38 -05:00
: write-headers ( assoc -- )
[ write-header ] assoc-each ;
: message-id ( -- string )
[
"<" %
2008-03-20 00:29:35 -04:00
64 random-bits #
"-" %
micros #
"@" %
smtp-domain get [ host-name ] unless* %
">" %
] "" make ;
: extract-email ( recepient -- email )
2008-08-29 00:19:18 -04:00
! This could be much smarter.
2008-11-22 21:00:37 -05:00
" " split1-last swap or "<" ?head drop ">" ?tail drop ;
: utf8-mime-header ( -- alist )
{
{ "MIME-Version" "1.0" }
{ "Content-Transfer-Encoding" "base64" }
{ "Content-Type" "Text/plain; charset=utf-8" }
} ;
: email>headers ( email -- hashtable )
[
2008-08-16 18:28:38 -04:00
{
[ from>> "From" set ]
[ to>> ", " join "To" set ]
[ cc>> ", " join [ "Cc" set ] unless-empty ]
2008-08-16 18:28:38 -04:00
[ subject>> "Subject" set ]
} cleave
now timestamp>rfc822 "Date" set
message-id "Message-Id" set
] { } make-assoc utf8-mime-header append ;
: (send-email) ( headers email -- )
[
get-ok
helo get-ok
smtp-tls? get [ start-tls get-ok send-secure-handshake ] when
auth
dup from>> extract-email mail-from get-ok
dup to>> [ extract-email rcpt-to get-ok ] each
2008-08-16 18:28:38 -04:00
dup cc>> [ extract-email rcpt-to get-ok ] each
dup bcc>> [ extract-email rcpt-to get-ok ] each
data get-ok
swap write-headers
crlf
body>> send-body get-ok
quit get-ok
] with-smtp-connection ;
2008-08-29 00:19:18 -04:00
PRIVATE>
: send-email ( email -- )
[ email>headers ] keep (send-email) ;