diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..da8071f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Revision history for HaskellNet-SSL + +## 0.4.0.0 -- 2025-01-07 + +- drop support for connection in favour of crypton-connection +- compatibility with GHCs up to ghc 9.8 (bump base and bytestring) +- fix example +- add tested-with stanza + +## 0.4.0.1 -- 2025-01-17 + +- Ignore 502 error on helo - fixes communication with some servers + +## 0.4.0.1 -- 2025-02-15 + +- bump data-default and network diff --git a/HaskellNet-SSL.cabal b/HaskellNet-SSL.cabal index 88c245d..02fc313 100644 --- a/HaskellNet-SSL.cabal +++ b/HaskellNet-SSL.cabal @@ -1,23 +1,24 @@ name: HaskellNet-SSL synopsis: Helpers to connect to SSL/TLS mail servers with HaskellNet -version: 0.3.4.1 +version: 0.4.0.2 description: This package ties together the HaskellNet and connection packages to make it easy to open IMAP and SMTP connections over SSL. homepage: https://github.com/dpwright/HaskellNet-SSL +tested-with: GHC ==9.4.8 || ==9.6.5 || ==9.8.2 license: BSD3 license-file: LICENSE author: Daniel P. Wright -maintainer: Leza M. Lutonda , dani@dpwright.com +maintainer: Leza M. Lutonda , dani@dpwright.com, contact@mangoiv.com copyright: (c) 2013 Daniel P. Wright category: Network build-type: Simple -cabal-version: >=1.8 -data-files: README.md +cabal-version: 1.18 +extra-doc-files: README.md, CHANGELOG.md -Flag NoUpperBounds - Description: Removes upper bounds from all packages - Default: False +flag network-bsd + description: Get Network.BSD from the network-bsd package + default: True source-repository head type: git @@ -26,24 +27,30 @@ source-repository head library hs-source-dirs: src ghc-options: -Wall + default-language: Haskell2010 exposed-modules: Network.HaskellNet.IMAP.SSL Network.HaskellNet.POP3.SSL Network.HaskellNet.SMTP.SSL Network.HaskellNet.SSL other-modules: Network.HaskellNet.SSL.Internal - if flag(NoUpperBounds) - build-depends: base >= 4, - HaskellNet >= 0.3, - tls >= 1.2, - connection >= 0.2.7, - network >= 2.4, - bytestring, - data-default + build-depends: base >= 4 && < 5, + HaskellNet >= 0.3 && < 0.7, + crypton-connection >= 0.3.1 && < 0.5, + bytestring >= 0.9 && < 0.13, + data-default >= 0.2 && < 0.9 + if flag(network-bsd) + build-depends: network >= 3.0 && < 3.3, + network-bsd >= 2.7 && < 2.9 else - build-depends: base >= 4 && < 5, - HaskellNet >= 0.3 && < 0.6, - tls >= 1.2 && < 1.5, - connection >= 0.2.7 && < 0.3, - network >= 2.4 && < 2.9, - bytestring, - data-default + build-depends: network >= 2.4 && < 3.3 + +executable HaskellNet-SSL-example + hs-source-dirs: examples + main-is: gmail.hs + other-modules: + build-depends: base, + HaskellNet-SSL, + HaskellNet, + bytestring + + default-language: Haskell2010 diff --git a/README.md b/README.md index c22d782..88ad29a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -HaskellNet-SSL --------------- +# HaskellNet-SSL -[![Build Status](https://travis-ci.org/dpwright/HaskellNet-SSL.svg?branch=master)](https://travis-ci.org/dpwright/HaskellNet-SSL) +[![haskell ci](https://github.com/dpwright/HaskellNet-SSL/actions/workflows/haskell.yml/badge.svg)](https://github.com/dpwright/HaskellNet-SSL/actions/workflows/haskell.yml) This package ties together the excellent [HaskellNet][HaskellNet] and -[connection][connection] packages to make it easy to open IMAP and SMTP +[crypton-connection][crypton-connection] packages to make it easy to open IMAP and SMTP connections over SSL. This is a simple "glue" library; all credit for a) connecting to IMAP/SMTP servers and b) making an SSL connection goes to the aforementioned libraries. [HaskellNet]: https://github.com/jtdaugherty/HaskellNet -[connection]: https://github.com/vincenthz/hs-connection +[crypton-connection]: https://github.com/kazu-yamamoto/crypton-connection diff --git a/cabal.project b/cabal.project new file mode 100644 index 0000000..7fe8cdc --- /dev/null +++ b/cabal.project @@ -0,0 +1,6 @@ +packages: . + +allow-newer: + , HaskellNet:base + , HaskellNet:network + , HaskellNet:data-default diff --git a/examples/gmail.hs b/examples/gmail.hs index e54d7df..de7f615 100644 --- a/examples/gmail.hs +++ b/examples/gmail.hs @@ -3,34 +3,52 @@ import Network.HaskellNet.IMAP.SSL import Network.HaskellNet.SMTP.SSL as SMTP -import Network.HaskellNet.Auth (AuthType(LOGIN)) +import Network.HaskellNet.Auth (AuthType(LOGIN), Password) +import Network.Mail.Mime import qualified Data.ByteString.Char8 as B +import Data.String +username :: IsString s => s username = "username@gmail.com" + +password :: Password password = "password" + +recipient :: Address recipient = "someone@somewhere.com" +imapTest :: IO () imapTest = do c <- connectIMAPSSLWithSettings "imap.gmail.com" cfg login c username password mboxes <- list c mapM_ print mboxes select c "INBOX" - msgs <- search c [ALLs] - let firstMsg = head msgs + msgs@(firstMsg : _) <- search c [ALLs] msgContent <- fetch c firstMsg B.putStrLn msgContent logout c where cfg = defaultSettingsIMAPSSL { sslMaxLineLength = 100000 } +smtpTest :: IO () smtpTest = doSMTPSTARTTLS "smtp.gmail.com" $ \c -> do authSucceed <- SMTP.authenticate LOGIN username password c if authSucceed - then sendPlainTextMail recipient username subject body c + then do + mail <- simpleMail + recipient + username + subject + body + mempty + mempty + sendMail mail c -- recipient username subject body else print "Authentication error." where subject = "Test message" body = "This is a test message" main :: IO () -main = smtpTest >> imapTest >> return () +main = do + smtpTest + imapTest diff --git a/src/Network/HaskellNet/IMAP/SSL.hs b/src/Network/HaskellNet/IMAP/SSL.hs index 7e6d5b3..7690bad 100644 --- a/src/Network/HaskellNet/IMAP/SSL.hs +++ b/src/Network/HaskellNet/IMAP/SSL.hs @@ -1,3 +1,4 @@ +-- | IMAP SSL Connections module Network.HaskellNet.IMAP.SSL ( -- * Establishing connection connectIMAPSSL @@ -15,11 +16,14 @@ import Network.HaskellNet.SSL import Network.HaskellNet.SSL.Internal +-- | Create IMAP connection with default settings connectIMAPSSL :: String -> IO IMAPConnection connectIMAPSSL hostname = connectIMAPSSLWithSettings hostname defaultSettingsIMAPSSL +-- | Create IMAP connection with given settings connectIMAPSSLWithSettings :: String -> Settings -> IO IMAPConnection connectIMAPSSLWithSettings hostname cfg = connectSSL hostname cfg >>= connectStream +-- | Default IMAP SSL settings, port 993 defaultSettingsIMAPSSL :: Settings defaultSettingsIMAPSSL = defaultSettingsWithPort 993 diff --git a/src/Network/HaskellNet/SMTP/SSL.hs b/src/Network/HaskellNet/SMTP/SSL.hs index 9f5ab2b..2ed35de 100644 --- a/src/Network/HaskellNet/SMTP/SSL.hs +++ b/src/Network/HaskellNet/SMTP/SSL.hs @@ -48,10 +48,12 @@ connectSTARTTLS hostname cfg = do (bs, startTLS) <- connectPlain hostname cfg greeting <- bsGetLine bs - failIfNot bs 220 $ parseResponse greeting + failIfNot bs 220 $ parse $ B.unpack greeting hn <- getHostName bsPut bs $ B.pack ("HELO " ++ hn ++ "\r\n") + getResponse bs >>= failIfNotEx bs (`elem` [250, 502]) + bsPut bs $ B.pack ("EHLO " ++ hn ++ "\r\n") getResponse bs >>= failIfNot bs 250 bsPut bs $ B.pack "STARTTLS\r\n" getResponse bs >>= failIfNot bs 220 @@ -60,15 +62,22 @@ connectSTARTTLS hostname cfg = do prefixRef <- newIORef [greeting] return $ bs {bsGetLine = prefixedGetLine prefixRef (bsGetLine bs)} - where parseResponse = parse . B.unpack - parse s = (getCode s, s) + where getFinalResponse bs = do + line <- fmap B.unpack $ bsGetLine bs + if (line !! 3) == '-' then getFinalResponse bs else return line + parse s = (getCode s, s) getCode = read . head . words - getResponse bs = liftM parseResponse $ bsGetLine bs + getResponse bs = liftM parse $ getFinalResponse bs failIfNot :: BSStream -> Integer -> (Integer, String) -> IO () failIfNot bs code (rc, rs) = when (code /= rc) closeAndFail where closeAndFail = bsClose bs >> fail ("cannot connect to server: " ++ rs) +-- | Extended version of fail if, can support multiple statuses +failIfNotEx :: BSStream -> (Integer -> Bool) -> (Integer, String) -> IO () +failIfNotEx bs f (rc, rs) = unless (f rc) closeAndFail + where closeAndFail = bsClose bs >> fail ("cannot connect to server: " ++ rs) + -- This is a bit of a nasty hack. Network.HaskellNet.SMTP.connectStream -- expects to receive a status 220 from the server as soon as it connects, -- but we've intercepted it in order to establish a STARTTLS connection. diff --git a/src/Network/HaskellNet/SSL.hs b/src/Network/HaskellNet/SSL.hs index acb9b84..bbbf38f 100644 --- a/src/Network/HaskellNet/SSL.hs +++ b/src/Network/HaskellNet/SSL.hs @@ -1,17 +1,24 @@ +{-# LANGUAGE CPP #-} module Network.HaskellNet.SSL ( Settings (..) , defaultSettingsWithPort ) where +#if MIN_VERSION_network(3,0,0) +import Network.Socket (PortNumber) +#else import Network.Socket.Internal (PortNumber) +#endif +-- | Settings for configuring HaskellNet connections data Settings = Settings - { sslPort :: PortNumber - , sslMaxLineLength :: Int - , sslLogToConsole :: Bool - , sslDisableCertificateValidation :: Bool + { sslPort :: PortNumber -- ^ Port number to connect to + , sslMaxLineLength :: Int -- ^ Max line lengths + , sslLogToConsole :: Bool -- ^ Log info to console + , sslDisableCertificateValidation :: Bool -- ^ Disable certificate validation } deriving(Eq, Ord, Show) +-- | Construct default settings for a port defaultSettingsWithPort :: PortNumber -> Settings defaultSettingsWithPort p = Settings { sslPort = p