137 lines
3.6 KiB
Scheme
137 lines
3.6 KiB
Scheme
|
|
(module smtp-unit mzscheme
|
|
(require (lib "unitsig.ss")
|
|
(lib "kw.ss")
|
|
"base64.ss")
|
|
|
|
(require "smtp-sig.ss")
|
|
|
|
(provide net:smtp@)
|
|
(define net:smtp@
|
|
(unit/sig net:smtp^
|
|
(import)
|
|
|
|
(define smtp-sending-server (make-parameter "localhost"))
|
|
|
|
(define debug-via-stdio? #f)
|
|
|
|
(define crlf (string #\return #\linefeed))
|
|
|
|
(define (log . args)
|
|
;; (apply printf args)
|
|
(void))
|
|
|
|
(define (starts-with? l n)
|
|
(and (>= (string-length l) (string-length n))
|
|
(string=? n (substring l 0 (string-length n)))))
|
|
|
|
(define (check-reply r v w)
|
|
(flush-output w)
|
|
(let ([l (read-line r (if debug-via-stdio?
|
|
'linefeed
|
|
'return-linefeed))])
|
|
(log "server: ~a~n" l)
|
|
(if (eof-object? l)
|
|
(error 'check-reply "got EOF")
|
|
(let ([n (number->string v)])
|
|
(unless (starts-with? l n)
|
|
(error 'check-reply "expected reply ~a; got: ~a" v l))
|
|
(let ([n- (string-append n "-")])
|
|
(when (starts-with? l n-)
|
|
;; Multi-line reply. Go again.
|
|
(check-reply r v w)))))))
|
|
|
|
(define (protect-line l)
|
|
;; If begins with a dot, add one more
|
|
(if (or (equal? l #"")
|
|
(equal? l "")
|
|
(and (string? l)
|
|
(not (char=? #\. (string-ref l 0))))
|
|
(and (bytes? l)
|
|
(not (= (char->integer #\.) (bytes-ref l 0)))))
|
|
l
|
|
(if (bytes? l)
|
|
(bytes-append #"." l)
|
|
(string-append "." l))))
|
|
|
|
(define smtp-sending-end-of-message
|
|
(make-parameter void
|
|
(lambda (f)
|
|
(unless (and (procedure? f)
|
|
(procedure-arity-includes? f 0))
|
|
(raise-type-error 'smtp-sending-end-of-message "thunk" f))
|
|
f)))
|
|
|
|
(define (smtp-send-message* r w sender recipients header message-lines
|
|
auth-user auth-passwd)
|
|
(with-handlers ([void (lambda (x)
|
|
(close-input-port r)
|
|
(close-output-port w)
|
|
(raise x))])
|
|
(check-reply r 220 w)
|
|
(log "hello~n")
|
|
(fprintf w "EHLO ~a~a" (smtp-sending-server) crlf)
|
|
(check-reply r 250 w)
|
|
|
|
(when auth-user
|
|
(log "auth~n")
|
|
(fprintf w "AUTH PLAIN ~a"
|
|
;; Encoding adds CRLF
|
|
(base64-encode
|
|
(string->bytes/latin-1
|
|
(format "~a\0~a\0~a" auth-user auth-user auth-passwd))))
|
|
(check-reply r 235 w))
|
|
|
|
(log "from~n")
|
|
(fprintf w "MAIL FROM:<~a>~a" sender crlf)
|
|
(check-reply r 250 w)
|
|
|
|
(log "to~n")
|
|
(for-each
|
|
(lambda (dest)
|
|
(fprintf w "RCPT TO:<~a>~a" dest crlf)
|
|
(check-reply r 250 w))
|
|
recipients)
|
|
|
|
(log "header~n")
|
|
(fprintf w "DATA~a" crlf)
|
|
(check-reply r 354 w)
|
|
(fprintf w "~a" header)
|
|
(for-each
|
|
(lambda (l)
|
|
(log "body: ~a~n" l)
|
|
(fprintf w "~a~a" (protect-line l) crlf))
|
|
message-lines)
|
|
|
|
;; After we send the ".", then only break in an emergency
|
|
((smtp-sending-end-of-message))
|
|
|
|
(log "dot~n")
|
|
(fprintf w ".~a" crlf)
|
|
(flush-output w)
|
|
(check-reply r 250 w)
|
|
|
|
(log "quit~n")
|
|
(fprintf w "QUIT~a" crlf)
|
|
(check-reply r 221 w)
|
|
|
|
(close-output-port w)
|
|
(close-input-port r)))
|
|
|
|
(define smtp-send-message
|
|
(lambda/kw (server sender recipients header message-lines
|
|
#:key
|
|
[port-no 25]
|
|
[auth-user #f]
|
|
[auth-passwd #f]
|
|
[tcp-connect tcp-connect]
|
|
#:body
|
|
(#:optional [opt-port-no port-no]))
|
|
(when (null? recipients)
|
|
(error 'send-smtp-message "no receivers"))
|
|
(let-values ([(r w) (if debug-via-stdio?
|
|
(values (current-input-port) (current-output-port))
|
|
(tcp-connect server opt-port-no))])
|
|
(smtp-send-message* r w sender recipients header message-lines
|
|
auth-user auth-passwd)))))))
|