net/imap: add TLS support

Based on a patch from Thomas Spurden

original commit: 537f194c4e
This commit is contained in:
Matthew Flatt 2012-03-10 10:40:30 -07:00
parent 43ad58579f
commit 51972b8036
2 changed files with 64 additions and 23 deletions

View File

@ -1,6 +1,9 @@
#lang racket/base #lang racket/base
(require racket/contract/base racket/tcp "private/rbtree.rkt") (require racket/contract/base
racket/tcp
openssl
"private/rbtree.rkt")
;; define the imap struct and its predicate here, for use in the contract, below ;; define the imap struct and its predicate here, for use in the contract, below
(define-struct imap (r w exists recent unseen uidnext uidvalidity (define-struct imap (r w exists recent unseen uidnext uidvalidity
@ -311,25 +314,54 @@
v)) v))
v))) v)))
(define (imap-connect* r w username password inbox) (define (has-starttls? imap)
(with-handlers ([void (let ([has? #f])
(lambda (x) (check-ok (imap-send imap
(close-input-port r) "CAPABILITY"
(close-output-port w) (lambda (caps)
(raise x))]) (when (member 'STARTTLS caps)
(set! has? #t)))))
has?))
(let ([imap (make-imap r w #f #f #f #f #f (define (imap-login imap username password inbox)
(new-tree) (new-tree) #f)])
(check-ok (imap-send imap "NOOP" void))
(let ([reply (imap-send imap (list "LOGIN" username password) void)]) (let ([reply (imap-send imap (list "LOGIN" username password) void)])
(if (and (pair? reply) (tag-eq? 'NO (car reply))) (if (and (pair? reply) (tag-eq? 'NO (car reply)))
(error 'imap-connect (error 'imap-connect
"username or password rejected by server: ~s" reply) "username or password rejected by server: ~s" reply)
(check-ok reply))) (check-ok reply)))
(let-values ([(init-count init-recent) (imap-reselect imap inbox)]) (let-values ([(init-count init-recent) (imap-reselect imap inbox)])
(values imap init-count init-recent))))) (values imap init-count init-recent)))
(define (imap-connect server username password inbox) (define (ports->tls-ports r w)
(ports->ssl-ports r w #:close-original? #t #:encrypt 'tls))
(define (imap-connect* r w username password inbox
#:tls? [tls? #f]
#:try-tls? [try-tls? #t])
(with-handlers ([void
(lambda (x)
(close-input-port r)
(close-output-port w)
(raise x))])
(let-values ([(r w)
(if tls?
(ports->tls-ports r w)
(values r w))])
(let ([imap (make-imap r w #f #f #f #f #f
(new-tree) (new-tree) #f)])
(check-ok (imap-send imap "NOOP" void))
(define imap-maybe-tls
(if (and (not tls?) try-tls? (has-starttls? imap))
(begin
(check-ok (imap-send imap "STARTTLS" void))
(let-values ([(ssl-in ssl-out) (ports->tls-ports r w)])
(make-imap ssl-in ssl-out #f #f #f #f #f (new-tree) (new-tree) #f)))
imap))
(imap-login imap-maybe-tls username password inbox)))))
(define (imap-connect server username password inbox
#:tls? [tls? #f]
#:try-tls? [try-tls? #t])
;; => imap count-k recent-k ;; => imap count-k recent-k
(let-values ([(r w) (let-values ([(r w)
(if debug-via-stdio? (if debug-via-stdio?
@ -337,7 +369,7 @@
(printf "stdin == ~a\n" server) (printf "stdin == ~a\n" server)
(values (current-input-port) (current-output-port))) (values (current-input-port) (current-output-port)))
(tcp-connect server (imap-port-number)))]) (tcp-connect server (imap-port-number)))])
(imap-connect* r w username password inbox))) (imap-connect* r w username password inbox #:tls? tls? #:try-tls? try-tls?)))
(define (imap-reselect imap inbox) (define (imap-reselect imap inbox)
(imap-selectish-command imap (list "SELECT" inbox) #t)) (imap-selectish-command imap (list "SELECT" inbox) #t))

View File

@ -55,13 +55,20 @@ opaque), @racket[#f] otherwise.}
@defproc[(imap-connect [server string?] @defproc[(imap-connect [server string?]
[username (or/c string? bytes?)] [username (or/c string? bytes?)]
[password (or/c string? bytes?)] [password (or/c string? bytes?)]
[mailbox (or/c string? bytes?)]) [mailbox (or/c string? bytes?)]
[#:tls? tls? any/c #f]
[#:try-tls? try-tls? any/c #t])
(values imap-connection? exact-nonnegative-integer? exact-nonnegative-integer?)]{ (values imap-connection? exact-nonnegative-integer? exact-nonnegative-integer?)]{
Establishes an IMAP connection to the given server using the given Establishes an IMAP connection to the given server using the given
username and password, and selects the specified mailbox. The first username and password, and selects the specified mailbox. If
result value reprsents the connection. @racket[tls?] is true, a TLS connection is made to the server before
communicating using the IMAP protocol. If @racket[tls?] is @racket[#f]
but @racket[try-tls?] is true, then after the IMAP connection is
initially established, the connection is switched to a TLS connection
if the server supports it.
The first result value represents the connection.
The second and third return values indicate the total number of The second and third return values indicate the total number of
messages in the mailbox and the number of recent messages (i.e., messages in the mailbox and the number of recent messages (i.e.,
messages received since the mailbox was last selected), respectively. messages received since the mailbox was last selected), respectively.
@ -87,7 +94,9 @@ is @racket[143].}
[out output-port?] [out output-port?]
[username (or/c string? bytes?)] [username (or/c string? bytes?)]
[password (or/c string? bytes?)] [password (or/c string? bytes?)]
[mailbox (or/c string? bytes?)]) [mailbox (or/c string? bytes?)]
[#:tls? tls? any/c #f]
[#:try-tls? try-tls? any/c #t])
(values imap-connection? exact-nonnegative-integer? exact-nonnegative-integer?)]{ (values imap-connection? exact-nonnegative-integer? exact-nonnegative-integer?)]{
Like @racket[imap-connect], but given input and output ports (e.g., Like @racket[imap-connect], but given input and output ports (e.g.,