summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2014-08-31 15:15:38 +0200
committerAraq <rumpf_a@web.de>2014-08-31 15:15:38 +0200
commitde29ce8ca84d5cbed173abc1324138fc263c4b42 (patch)
treece9f3e40554e1715e8e9fc6749059805232eeaa0
parent30823c1ce3992d48251069af48ed9d26b1238ba4 (diff)
parent25e0c26a91c0336694ffbbd64341d623923f9580 (diff)
downloadNim-de29ce8ca84d5cbed173abc1324138fc263c4b42.tar.gz
Merge branch 'bigbreak' of https://github.com/Araq/Nimrod into bigbreak
-rw-r--r--lib/pure/asyncftpclient.nim4
-rw-r--r--lib/pure/asyncnet.nim174
-rw-r--r--lib/pure/httpclient.nim81
-rw-r--r--lib/pure/net.nim15
-rw-r--r--lib/pure/smtp.nim129
-rw-r--r--lib/pure/sockets.nim100
-rw-r--r--lib/wrappers/openssl.nim51
7 files changed, 409 insertions, 145 deletions
diff --git a/lib/pure/asyncftpclient.nim b/lib/pure/asyncftpclient.nim
index a3d977660..d4ac63e75 100644
--- a/lib/pure/asyncftpclient.nim
+++ b/lib/pure/asyncftpclient.nim
@@ -255,7 +255,7 @@ proc doUpload(ftp: AsyncFtpClient, file: TFile,
 
     await countdownFut or sendFut
 
-proc storeFile*(ftp: AsyncFtpClient, file, dest: string,
+proc store*(ftp: AsyncFtpClient, file, dest: string,
             onProgressChanged = defaultOnProgressChanged) {.async.} =
   ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this
   ## function asynchronously is recommended to view the progress of
@@ -288,7 +288,7 @@ when isMainModule:
     await ftp.connect()
     echo await ftp.pwd()
     echo await ftp.listDirs()
-    await ftp.storeFile("payload.jpg", "payload.jpg")
+    await ftp.store("payload.jpg", "payload.jpg")
     await ftp.retrFile("payload.jpg", "payload2.jpg")
     echo("Finished")
 
diff --git a/lib/pure/asyncnet.nim b/lib/pure/asyncnet.nim
index 8734bab4c..f55442488 100644
--- a/lib/pure/asyncnet.nim
+++ b/lib/pure/asyncnet.nim
@@ -47,6 +47,7 @@
 import asyncdispatch
 import rawsockets
 import net
+import os
 
 when defined(ssl):
   import openssl
@@ -54,7 +55,22 @@ when defined(ssl):
 type
   # TODO: I would prefer to just do:
   # PAsyncSocket* {.borrow: `.`.} = distinct PSocket. But that doesn't work.
-  AsyncSocketDesc {.borrow: `.`.} = distinct TSocketImpl
+  AsyncSocketDesc  = object
+    fd*: SocketHandle
+    case isBuffered*: bool # determines whether this socket is buffered.
+    of true:
+      buffer*: array[0..BufferSize, char]
+      currPos*: int # current index in buffer
+      bufLen*: int # current length of buffer
+    of false: nil
+    case isSsl: bool
+    of true:
+      when defined(ssl):
+        sslHandle: SslPtr
+        sslContext: SslContext
+        bioIn: BIO
+        bioOut: BIO
+    of false: nil
   AsyncSocket* = ref AsyncSocketDesc
 
 {.deprecated: [PAsyncSocket: AsyncSocket].}
@@ -63,7 +79,7 @@ type
 
 proc newSocket(fd: TAsyncFD, isBuff: bool): PAsyncSocket =
   assert fd != osInvalidSocket.TAsyncFD
-  new(result.PSocket)
+  new(result)
   result.fd = fd.SocketHandle
   result.isBuffered = isBuff
   if isBuff:
@@ -74,22 +90,94 @@ proc newAsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
   ## Creates a new asynchronous socket.
   result = newSocket(newAsyncRawSocket(domain, typ, protocol), buffered)
 
+when defined(ssl):
+  proc getSslError(handle: SslPtr, err: cint): cint =
+    assert err < 0
+    var ret = SSLGetError(handle, err.cint)
+    case ret
+    of SSL_ERROR_ZERO_RETURN:
+      raiseSSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
+    of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
+      return ret
+    of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
+      return ret
+    of SSL_ERROR_WANT_X509_LOOKUP:
+      raiseSSLError("Function for x509 lookup has been called.")
+    of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
+      raiseSSLError()
+    else: raiseSSLError("Unknown Error")
+
+  proc sendPendingSslData(socket: AsyncSocket,
+      flags: set[TSocketFlags]) {.async.} =
+    let len = bioCtrlPending(socket.bioOut)
+    if len > 0:
+      var data = newStringOfCap(len)
+      let read = bioRead(socket.bioOut, addr data[0], len)
+      assert read != 0
+      if read < 0:
+        raiseSslError()
+      data.setLen(read)
+      await socket.fd.TAsyncFd.send(data, flags)
+
+  proc appeaseSsl(socket: AsyncSocket, flags: set[TSocketFlags],
+                  sslError: cint) {.async.} =
+    case sslError
+    of SSL_ERROR_WANT_WRITE:
+      await sendPendingSslData(socket, flags)
+    of SSL_ERROR_WANT_READ:
+      var data = await recv(socket.fd.TAsyncFD, BufferSize, flags)
+      let ret = bioWrite(socket.bioIn, addr data[0], data.len.cint)
+      if ret < 0:
+        raiseSSLError()
+    else:
+      raiseSSLError("Cannot appease SSL.")
+
+  template sslLoop(socket: AsyncSocket, flags: set[TSocketFlags],
+                   op: expr) =
+    var opResult {.inject.} = -1.cint
+    while opResult < 0:
+      opResult = op
+      # Bit hackish here.
+      # TODO: Introduce an async template transformation pragma?
+      yield sendPendingSslData(socket, flags)
+      if opResult < 0:
+        let err = getSslError(socket.sslHandle, opResult.cint)
+        yield appeaseSsl(socket, flags, err.cint)
+
 proc connect*(socket: PAsyncSocket, address: string, port: TPort,
-    af = AF_INET): Future[void] =
+    af = AF_INET) {.async.} =
   ## Connects ``socket`` to server at ``address:port``.
   ##
   ## Returns a ``Future`` which will complete when the connection succeeds
   ## or an error occurs.
-  result = connect(socket.fd.TAsyncFD, address, port, af)
+  await connect(socket.fd.TAsyncFD, address, port, af)
+  let flags = {TSocketFlags.SafeDisconn}
+  if socket.isSsl:
+    when defined(ssl):
+      sslSetConnectState(socket.sslHandle)
+      sslLoop(socket, flags, sslDoHandshake(socket.sslHandle))
 
 proc readIntoBuf(socket: PAsyncSocket,
     flags: set[TSocketFlags]): Future[int] {.async.} =
   var data = await recv(socket.fd.TAsyncFD, BufferSize, flags)
   if data.len != 0:
     copyMem(addr socket.buffer[0], addr data[0], data.len)
-  socket.bufLen = data.len
-  socket.currPos = 0
-  result = data.len
+  if socket.isSsl:
+    when defined(ssl):
+      # SSL mode.
+      let ret = bioWrite(socket.bioIn, addr socket.buffer[0], data.len.cint)
+      if ret < 0:
+        raiseSSLError()
+      sslLoop(socket, flags,
+        sslRead(socket.sslHandle, addr socket.buffer[0], BufferSize.cint))
+      socket.currPos = 0
+      socket.bufLen = opResult # Injected from sslLoop template.
+      result = opResult
+  else:
+    # Not in SSL mode.
+    socket.bufLen = data.len
+    socket.currPos = 0
+    result = data.len
 
 proc recv*(socket: PAsyncSocket, size: int,
            flags = {TSocketFlags.SafeDisconn}): Future[string] {.async.} =
@@ -131,11 +219,18 @@ proc recv*(socket: PAsyncSocket, size: int,
     result = await recv(socket.fd.TAsyncFD, size, flags)
 
 proc send*(socket: PAsyncSocket, data: string,
-           flags = {TSocketFlags.SafeDisconn}): Future[void] =
+           flags = {TSocketFlags.SafeDisconn}) {.async.} =
   ## Sends ``data`` to ``socket``. The returned future will complete once all
   ## data has been sent.
   assert socket != nil
-  result = send(socket.fd.TAsyncFD, data, flags)
+  if socket.isSsl:
+    when defined(ssl):
+      var copy = data
+      sslLoop(socket, flags,
+        sslWrite(socket.sslHandle, addr copy[0], copy.len.cint))
+      await sendPendingSslData(socket, flags)
+  else:
+    await send(socket.fd.TAsyncFD, data, flags)
 
 proc acceptAddr*(socket: PAsyncSocket, flags = {TSocketFlags.SafeDisconn}):
       Future[tuple[address: string, client: PAsyncSocket]] =
@@ -240,24 +335,67 @@ proc recvLine*(socket: PAsyncSocket,
         return
       add(result.string, c)
 
-proc bindAddr*(socket: PAsyncSocket, port = TPort(0), address = "") =
-  ## Binds ``address``:``port`` to the socket.
-  ##
-  ## If ``address`` is "" then ADDR_ANY will be bound.
-  socket.PSocket.bindAddr(port, address)
-
-proc listen*(socket: PAsyncSocket, backlog = SOMAXCONN) =
+proc listen*(socket: Socket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
   ## Marks ``socket`` as accepting connections.
   ## ``Backlog`` specifies the maximum length of the
   ## queue of pending connections.
   ##
   ## Raises an EOS error upon failure.
-  socket.PSocket.listen(backlog)
+  if listen(socket.fd, backlog) < 0'i32: raiseOSError(osLastError())
+
+proc bindAddr*(socket: Socket, port = Port(0), address = "") {.
+  tags: [ReadIOEffect].} =
+  ## Binds ``address``:``port`` to the socket.
+  ##
+  ## If ``address`` is "" then ADDR_ANY will be bound.
+
+  if address == "":
+    var name: Sockaddr_in
+    when defined(Windows) or defined(nimdoc):
+      name.sin_family = toInt(AF_INET).int16
+    else:
+      name.sin_family = toInt(AF_INET)
+    name.sin_port = htons(int16(port))
+    name.sin_addr.s_addr = htonl(INADDR_ANY)
+    if bindAddr(socket.fd, cast[ptr SockAddr](addr(name)),
+                  sizeof(name).Socklen) < 0'i32:
+      raiseOSError(osLastError())
+  else:
+    var aiList = getAddrInfo(address, port, AF_INET)
+    if bindAddr(socket.fd, aiList.ai_addr, aiList.ai_addrlen.Socklen) < 0'i32:
+      dealloc(aiList)
+      raiseOSError(osLastError())
+    dealloc(aiList)
 
 proc close*(socket: PAsyncSocket) =
   ## Closes the socket.
   socket.fd.TAsyncFD.closeSocket()
-  # TODO SSL
+  when defined(ssl):
+    if socket.isSSL:
+      let res = SslShutdown(socket.sslHandle)
+      if res == 0:
+        if SslShutdown(socket.sslHandle) != 1:
+          raiseSslError()
+      elif res != 1:
+        raiseSslError()
+
+when defined(ssl):
+  proc wrapSocket*(ctx: SslContext, socket: AsyncSocket) =
+    ## Wraps a socket in an SSL context. This function effectively turns
+    ## ``socket`` into an SSL socket.
+    ##
+    ## **Disclaimer**: This code is not well tested, may be very unsafe and
+    ## prone to security vulnerabilities.
+    socket.isSsl = true
+    socket.sslContext = ctx
+    socket.sslHandle = SSLNew(PSSLCTX(socket.sslContext))
+    if socket.sslHandle == nil:
+      raiseSslError()
+
+    socket.bioIn = bioNew(bio_s_mem())
+    socket.bioOut = bioNew(bio_s_mem())
+    sslSetBio(socket.sslHandle, socket.bioIn, socket.bioOut)
+
 
 when isMainModule:
   type
diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim
index 5eff6cfa2..8b19c58f8 100644
--- a/lib/pure/httpclient.nim
+++ b/lib/pure/httpclient.nim
@@ -78,6 +78,7 @@
 import sockets, strutils, parseurl, parseutils, strtabs, base64, os
 import asyncnet, asyncdispatch
 import rawsockets
+from net import nil
 
 type
   Response* = tuple[
@@ -164,16 +165,17 @@ proc parseBody(s: TSocket, headers: PStringTable, timeout: int): string =
     var contentLengthHeader = headers["Content-Length"]
     if contentLengthHeader != "":
       var length = contentLengthHeader.parseint()
-      result = newString(length)
-      var received = 0
-      while true:
-        if received >= length: break
-        let r = s.recv(addr(result[received]), length-received, timeout)
-        if r == 0: break
-        received += r
-      if received != length:
-        httpError("Got invalid content length. Expected: " & $length &
-                  " got: " & $received)
+      if length > 0:
+        result = newString(length)
+        var received = 0
+        while true:
+          if received >= length: break
+          let r = s.recv(addr(result[received]), length-received, timeout)
+          if r == 0: break
+          received += r
+        if received != length:
+          httpError("Got invalid content length. Expected: " & $length &
+                    " got: " & $received)
     else:
       # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.4 TODO
       
@@ -181,7 +183,7 @@ proc parseBody(s: TSocket, headers: PStringTable, timeout: int): string =
       # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
       if headers["Connection"] == "close":
         var buf = ""
-        while True:
+        while true:
           buf = newString(4000)
           let r = s.recv(addr(buf[0]), 4000, timeout)
           if r == 0: break
@@ -194,7 +196,7 @@ proc parseResponse(s: TSocket, getBody: bool, timeout: int): TResponse =
   var fullyRead = false
   var line = ""
   result.headers = newStringTable(modeCaseInsensitive)
-  while True:
+  while true:
     line = ""
     linei = 0
     s.readLine(line, timeout)
@@ -294,7 +296,7 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
   add(headers, "\c\L")
   
   var s = socket()
-  if s == InvalidSocket: raiseOSError(osLastError())
+  if s == invalidSocket: raiseOSError(osLastError())
   var port = sockets.TPort(80)
   if r.scheme == "https":
     when defined(ssl):
@@ -321,7 +323,7 @@ proc redirection(status: string): bool =
   const redirectionNRs = ["301", "302", "303", "307"]
   for i in items(redirectionNRs):
     if status.startsWith(i):
-      return True
+      return true
 
 proc getNewLocation(lastUrl: string, headers: PStringTable): string =
   result = headers["Location"]
@@ -444,11 +446,13 @@ type
     headers: StringTableRef
     maxRedirects: int
     userAgent: string
+    when defined(ssl):
+      sslContext: net.SslContext
 
 {.deprecated: [PAsyncHttpClient: AsyncHttpClient].}
 
 proc newAsyncHttpClient*(userAgent = defUserAgent,
-    maxRedirects = 5): AsyncHttpClient =
+    maxRedirects = 5, sslContext = defaultSslContext): AsyncHttpClient =
   ## Creates a new PAsyncHttpClient instance.
   ##
   ## ``userAgent`` specifies the user agent that will be used when making
@@ -456,10 +460,13 @@ proc newAsyncHttpClient*(userAgent = defUserAgent,
   ##
   ## ``maxRedirects`` specifies the maximum amount of redirects to follow,
   ## default is 5.
+  ##
+  ## ``sslContext`` specifies the SSL context to use for HTTPS requests.
   new result
   result.headers = newStringTable(modeCaseInsensitive)
   result.userAgent = defUserAgent
   result.maxRedirects = maxRedirects
+  result.sslContext = net.SslContext(sslContext)
 
 proc close*(client: AsyncHttpClient) =
   ## Closes any connections held by the HTTP client.
@@ -467,7 +474,7 @@ proc close*(client: AsyncHttpClient) =
     client.socket.close()
     client.connected = false
 
-proc recvFull(socket: PAsyncSocket, size: int): PFuture[string] {.async.} =
+proc recvFull(socket: PAsyncSocket, size: int): Future[string] {.async.} =
   ## Ensures that all the data requested is read and returned.
   result = ""
   while true:
@@ -476,7 +483,7 @@ proc recvFull(socket: PAsyncSocket, size: int): PFuture[string] {.async.} =
     if data == "": break # We've been disconnected.
     result.add data
 
-proc parseChunks(client: PAsyncHttpClient): PFuture[string] {.async.} =
+proc parseChunks(client: PAsyncHttpClient): Future[string] {.async.} =
   result = ""
   var ri = 0
   while true:
@@ -509,7 +516,7 @@ proc parseChunks(client: PAsyncHttpClient): PFuture[string] {.async.} =
     # them: http://tools.ietf.org/html/rfc2616#section-3.6.1
   
 proc parseBody(client: PAsyncHttpClient,
-               headers: PStringTable): PFuture[string] {.async.} =
+               headers: PStringTable): Future[string] {.async.} =
   result = ""
   if headers["Transfer-Encoding"] == "chunked":
     result = await parseChunks(client)
@@ -519,12 +526,13 @@ proc parseBody(client: PAsyncHttpClient,
     var contentLengthHeader = headers["Content-Length"]
     if contentLengthHeader != "":
       var length = contentLengthHeader.parseint()
-      result = await client.socket.recvFull(length)
-      if result == "":
-        httpError("Got disconnected while trying to read body.")
-      if result.len != length:
-        httpError("Received length doesn't match expected length. Wanted " &
-                  $length & " got " & $result.len)
+      if length > 0:
+        result = await client.socket.recvFull(length)
+        if result == "":
+          httpError("Got disconnected while trying to read body.")
+        if result.len != length:
+          httpError("Received length doesn't match expected length. Wanted " &
+                    $length & " got " & $result.len)
     else:
       # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.4 TODO
       
@@ -532,19 +540,19 @@ proc parseBody(client: PAsyncHttpClient,
       # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
       if headers["Connection"] == "close":
         var buf = ""
-        while True:
+        while true:
           buf = await client.socket.recvFull(4000)
           if buf == "": break
           result.add(buf)
 
 proc parseResponse(client: PAsyncHttpClient,
-                   getBody: bool): PFuture[TResponse] {.async.} =
+                   getBody: bool): Future[TResponse] {.async.} =
   var parsedStatus = false
   var linei = 0
   var fullyRead = false
   var line = ""
   result.headers = newStringTable(modeCaseInsensitive)
-  while True:
+  while true:
     linei = 0
     line = await client.socket.recvLine()
     if line == "": break # We've been disconnected.
@@ -590,20 +598,29 @@ proc newConnection(client: PAsyncHttpClient, url: TURL) {.async.} =
       client.currentURL.scheme != url.scheme:
     if client.connected: client.close()
     client.socket = newAsyncSocket()
-    if url.scheme == "https":
-      assert false, "TODO SSL"
 
     # TODO: I should be able to write 'net.TPort' here...
     let port =
-      if url.port == "": rawsockets.TPort(80)
+      if url.port == "":
+        if url.scheme.toLower() == "https":
+          rawsockets.TPort(443)
+        else:
+          rawsockets.TPort(80)
       else: rawsockets.TPort(url.port.parseInt)
     
+    if url.scheme.toLower() == "https":
+      when defined(ssl):
+        client.sslContext.wrapSocket(client.socket)
+      else:
+        raise newException(EHttpRequestErr,
+                  "SSL support is not available. Cannot connect over SSL.")
+    
     await client.socket.connect(url.hostname, port)
     client.currentURL = url
     client.connected = true
 
 proc request*(client: PAsyncHttpClient, url: string, httpMethod = httpGET,
-              body = ""): PFuture[TResponse] {.async.} =
+              body = ""): Future[TResponse] {.async.} =
   ## Connects to the hostname specified by the URL and performs a request
   ## using the method specified.
   ##
@@ -626,7 +643,7 @@ proc request*(client: PAsyncHttpClient, url: string, httpMethod = httpGET,
   
   result = await parseResponse(client, httpMethod != httpHEAD)
 
-proc get*(client: PAsyncHttpClient, url: string): PFuture[TResponse] {.async.} =
+proc get*(client: PAsyncHttpClient, url: string): Future[TResponse] {.async.} =
   ## Connects to the hostname specified by the URL and performs a GET request.
   ##
   ## This procedure will follow redirects up to a maximum number of redirects
diff --git a/lib/pure/net.nim b/lib/pure/net.nim
index fd485340c..5f202f05c 100644
--- a/lib/pure/net.nim
+++ b/lib/pure/net.nim
@@ -22,17 +22,17 @@ when defined(ssl):
 
 when defined(ssl):
   type
-    SSLError* = object of Exception
+    SslError* = object of Exception
 
-    SSLCVerifyMode* = enum
+    SslCVerifyMode* = enum
       CVerifyNone, CVerifyPeer
     
-    SSLProtVersion* = enum
+    SslProtVersion* = enum
       protSSLv2, protSSLv3, protTLSv1, protSSLv23
     
-    SSLContext* = distinct PSSLCTX
+    SslContext* = distinct SslCtx
 
-    SSLAcceptResult* = enum
+    SslAcceptResult* = enum
       AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess
 
   {.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode,
@@ -125,7 +125,8 @@ when defined(ssl):
   ErrLoadBioStrings()
   OpenSSL_add_all_algorithms()
 
-  proc raiseSSLError(s = "") =
+  proc raiseSSLError*(s = "") =
+    ## Raises a new SSL error.
     if s != "":
       raise newException(SSLError, s)
     let err = ErrPeekLastError()
@@ -161,7 +162,7 @@ when defined(ssl):
                    certFile = "", keyFile = ""): PSSLContext =
     ## Creates an SSL context.
     ## 
-    ## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1 are 
+    ## Protocol version specifies the protocol to use. SSLv2, SSLv3, TLSv1 
     ## are available with the addition of ``protSSLv23`` which allows for 
     ## compatibility with all of them.
     ##
diff --git a/lib/pure/smtp.nim b/lib/pure/smtp.nim
index 83eb60807..e088e0bd9 100644
--- a/lib/pure/smtp.nim
+++ b/lib/pure/smtp.nim
@@ -28,7 +28,8 @@
 ## For SSL support this module relies on OpenSSL. If you want to 
 ## enable SSL, compile with ``-d:ssl``.
 
-import sockets, strutils, strtabs, base64, os
+import net, strutils, strtabs, base64, os
+import asyncnet, asyncdispatch
 
 type
   Smtp* = object
@@ -44,8 +45,15 @@ type
   
   ReplyError* = object of IOError
 
+  AsyncSmtp* = ref object
+    sock: AsyncSocket
+    address: string
+    port: Port
+    useSsl: bool
+    debug: bool
+
 {.deprecated: [EInvalidReply: ReplyError, TMessage: Message, TSMTP: Smtp].}
-  
+
 proc debugSend(smtp: TSMTP, cmd: string) =
   if smtp.debug:
     echo("C:" & cmd)
@@ -70,19 +78,25 @@ proc checkReply(smtp: var TSMTP, reply: string) =
 
 const compiledWithSsl = defined(ssl)
 
-proc connect*(address: string, port = 25, 
-              ssl = false, debug = false): TSMTP =
+when not defined(ssl):
+  type PSSLContext = ref object
+  let defaultSSLContext: PSSLContext = nil
+else:
+  let defaultSSLContext = newContext(verifyMode = CVerifyNone)
+
+proc connect*(address: string, port = Port(25), 
+              ssl = false, debug = false,
+              sslContext = defaultSSLContext): TSMTP =
   ## Establishes a connection with a SMTP server.
   ## May fail with EInvalidReply or with a socket error.
-  result.sock = socket()
+  result.sock = newSocket()
   if ssl:
     when compiledWithSsl:
-      let ctx = newContext(verifyMode = CVerifyNone)
-      ctx.wrapSocket(result.sock)
+      sslContext.wrapSocket(result.sock)
     else:
       raise newException(ESystem, 
                          "SMTP module compiled without SSL support")
-  result.sock.connect(address, TPort(port))
+  result.sock.connect(address, port)
   result.debug = debug
   
   result.checkReply("220")
@@ -162,6 +176,82 @@ proc `$`*(msg: TMessage): string =
   result.add("\c\L")
   result.add(msg.msgBody)
   
+proc newAsyncSmtp*(address: string, port: Port, useSsl = false,
+                   sslContext = defaultSslContext): AsyncSmtp =
+  ## Creates a new ``AsyncSmtp`` instance.
+  new result
+  result.address = address
+  result.port = port
+  result.useSsl = useSsl
+
+  result.sock = newAsyncSocket()
+  if useSsl:
+    when compiledWithSsl:
+      sslContext.wrapSocket(result.sock)
+    else:
+      raise newException(ESystem, 
+                         "SMTP module compiled without SSL support")
+
+proc quitExcpt(smtp: AsyncSmtp, msg: string): PFuture[void] =
+  var retFuture = newFuture[void]()
+  var sendFut = smtp.sock.send("QUIT")
+  sendFut.callback =
+    proc () =
+      # TODO: Fix this in async procs.
+      raise newException(ReplyError, msg)
+  return retFuture
+
+proc checkReply(smtp: AsyncSmtp, reply: string) {.async.} =
+  var line = await smtp.sock.recvLine()
+  if not line.string.startswith(reply):
+    await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line.string)
+
+proc connect*(smtp: AsyncSmtp) {.async.} =
+  ## Establishes a connection with a SMTP server.
+  ## May fail with EInvalidReply or with a socket error.
+  await smtp.sock.connect(smtp.address, smtp.port)
+
+  await smtp.checkReply("220")
+  await smtp.sock.send("HELO " & smtp.address & "\c\L")
+  await smtp.checkReply("250")
+
+proc auth*(smtp: AsyncSmtp, username, password: string) {.async.} =
+  ## Sends an AUTH command to the server to login as the `username` 
+  ## using `password`.
+  ## May fail with EInvalidReply.
+
+  await smtp.sock.send("AUTH LOGIN\c\L")
+  await smtp.checkReply("334") # TODO: Check whether it's asking for the "Username:"
+                               # i.e "334 VXNlcm5hbWU6"
+  await smtp.sock.send(encode(username) & "\c\L")
+  await smtp.checkReply("334") # TODO: Same as above, only "Password:" (I think?)
+  
+  await smtp.sock.send(encode(password) & "\c\L")
+  await smtp.checkReply("235") # Check whether the authentification was successful.
+
+proc sendMail*(smtp: AsyncSmtp, fromAddr: string,
+               toAddrs: seq[string], msg: string) {.async.} =
+  ## Sends ``msg`` from ``fromAddr`` to the addresses specified in ``toAddrs``.
+  ## Messages may be formed using ``createMessage`` by converting the
+  ## TMessage into a string.
+
+  await smtp.sock.send("MAIL FROM:<" & fromAddr & ">\c\L")
+  await smtp.checkReply("250")
+  for address in items(toAddrs):
+    await smtp.sock.send("RCPT TO:<" & smtp.address & ">\c\L")
+    await smtp.checkReply("250")
+  
+  # Send the message
+  await smtp.sock.send("DATA " & "\c\L")
+  await smtp.checkReply("354")
+  await smtp.sock.send(msg & "\c\L")
+  await smtp.sock.send(".\c\L")
+  await smtp.checkReply("250")
+
+proc close*(smtp: AsyncSmtp) {.async.} =
+  ## Disconnects from the SMTP server and closes the socket.
+  await smtp.sock.send("QUIT\c\L")
+  smtp.sock.close()
 
 when isMainModule:
   #var msg = createMessage("Test subject!", 
@@ -172,15 +262,16 @@ when isMainModule:
   #smtp.sendmail("root@localhost", @["dominik@localhost"], $msg)
   
   #echo(decode("a17sm3701420wbe.12"))
-  var msg = createMessage("Hello from Nim's SMTP!", 
-                          "Hello!!!!.\n Is this awesome or what?", 
-                          @["someone@yahoo.com", "someone@gmail.com"])
-  echo(msg)
-
-  var smtp = connect("smtp.gmail.com", 465, True, True)
-  smtp.auth("someone", "password")
-  smtp.sendmail("someone@gmail.com", 
-                @["someone@yahoo.com", "someone@gmail.com"], $msg)
-  smtp.close()
-  
+  proc main() {.async.} =
+    var client = newAsyncSmtp("smtp.gmail.com", Port(465), true)
+    await client.connect()
+    await client.auth("johndoe", "foo")
+    var msg = createMessage("Hello from Nim's SMTP!", 
+                            "Hello!!!!.\n Is this awesome or what?", 
+                            @["blah@gmail.com"])
+    echo(msg)
+    await client.sendMail("blah@gmail.com", @["blah@gmail.com"], $msg)
 
+    await client.close()
+  
+  waitFor main()
diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim
index 157d5837e..5ac3589a2 100644
--- a/lib/pure/sockets.nim
+++ b/lib/pure/sockets.nim
@@ -251,14 +251,14 @@ when defined(ssl):
   ErrLoadBioStrings()
   OpenSSL_add_all_algorithms()
 
-  proc SSLError(s = "") =
+  proc raiseSSLError(s = "") =
     if s != "":
       raise newException(ESSL, s)
     let err = ErrPeekLastError()
     if err == 0:
       raise newException(ESSL, "No error reported.")
     if err == -1:
-      OSError(OSLastError())
+      raiseOSError(osLastError())
     var errStr = ErrErrorString(err, nil)
     raise newException(ESSL, $errStr)
 
@@ -272,18 +272,18 @@ when defined(ssl):
     if certFile != "":
       var ret = SSLCTXUseCertificateChainFile(ctx, certFile)
       if ret != 1:
-        SSLError()
+        raiseSslError()
     
     # TODO: Password? www.rtfm.com/openssl-examples/part1.pdf
     if keyFile != "":
       if SSL_CTX_use_PrivateKey_file(ctx, keyFile,
                                      SSL_FILETYPE_PEM) != 1:
-        SSLError()
+        raiseSslError()
         
       if SSL_CTX_check_private_key(ctx) != 1:
-        SSLError("Verification of private key file failed.")
+        raiseSslError("Verification of private key file failed.")
 
-  proc newContext*(protVersion = ProtSSLv23, verifyMode = CVerifyPeer,
+  proc newContext*(protVersion = protSSLv23, verifyMode = CVerifyPeer,
                    certFile = "", keyFile = ""): PSSLContext =
     ## Creates an SSL context.
     ## 
@@ -308,21 +308,21 @@ when defined(ssl):
       when not defined(linux) and not defined(OpenBSD):
         newCTX = SSL_CTX_new(SSLv2_method())
       else:
-        SSLError()
+        raiseSslError()
     of protSSLv3:
       newCTX = SSL_CTX_new(SSLv3_method())
     of protTLSv1:
       newCTX = SSL_CTX_new(TLSv1_method())
     
     if newCTX.SSLCTXSetCipherList("ALL") != 1:
-      SSLError()
+      raiseSslError()
     case verifyMode
     of CVerifyPeer:
       newCTX.SSLCTXSetVerify(SSLVerifyPeer, nil)
     of CVerifyNone:
       newCTX.SSLCTXSetVerify(SSLVerifyNone, nil)
     if newCTX == nil:
-      SSLError()
+      raiseSslError()
 
     discard newCTX.SSLCTXSetMode(SSL_MODE_AUTO_RETRY)
     newCTX.loadCertificates(certFile, keyFile)
@@ -341,10 +341,10 @@ when defined(ssl):
     socket.sslNoHandshake = false
     socket.sslHasPeekChar = false
     if socket.sslHandle == nil:
-      SSLError()
+      raiseSslError()
     
     if SSLSetFd(socket.sslHandle, socket.fd) != 1:
-      SSLError()
+      raiseSslError()
 
 proc raiseSocketError*(socket: Socket, err: int = -1, async = false) =
   ## Raises proper errors based on return values of ``recv`` functions.
@@ -359,20 +359,20 @@ proc raiseSocketError*(socket: Socket, err: int = -1, async = false) =
         var ret = SSLGetError(socket.sslHandle, err.cint)
         case ret
         of SSL_ERROR_ZERO_RETURN:
-          SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
+          raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
         of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
           if async:
             return
-          else: SSLError("Not enough data on socket.")
+          else: raiseSslError("Not enough data on socket.")
         of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
           if async:
             return
-          else: SSLError("Not enough data on socket.")
+          else: raiseSslError("Not enough data on socket.")
         of SSL_ERROR_WANT_X509_LOOKUP:
-          SSLError("Function for x509 lookup has been called.")
+          raiseSslError("Function for x509 lookup has been called.")
         of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
-          SSLError()
-        else: SSLError("Unknown Error")
+          raiseSslError()
+        else: raiseSslError("Unknown Error")
   
   if err == -1 and not (when defined(ssl): socket.isSSL else: false):
     let lastError = osLastError()
@@ -545,16 +545,16 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string) {.
           if err != SSL_ERROR_WANT_ACCEPT:
             case err
             of SSL_ERROR_ZERO_RETURN:
-              SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
+              raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
             of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE,
                SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
-              SSLError("acceptAddrSSL should be used for non-blocking SSL sockets.")
+              raiseSslError("acceptAddrSSL should be used for non-blocking SSL sockets.")
             of SSL_ERROR_WANT_X509_LOOKUP:
-              SSLError("Function for x509 lookup has been called.")
+              raiseSslError("Function for x509 lookup has been called.")
             of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
-              SSLError()
+              raiseSslError()
             else:
-              SSLError("Unknown error")
+              raiseSslError("Unknown error")
 
 proc setBlocking*(s: Socket, blocking: bool) {.tags: [], gcsafe.}
   ## Sets blocking mode on socket
@@ -591,17 +591,17 @@ when defined(ssl):
             if err != SSL_ERROR_WANT_ACCEPT:
               case err
               of SSL_ERROR_ZERO_RETURN:
-                SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
+                raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
               of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE,
                  SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
                 client.sslNoHandshake = true
                 return AcceptNoHandshake
               of SSL_ERROR_WANT_X509_LOOKUP:
-                SSLError("Function for x509 lookup has been called.")
+                raiseSslError("Function for x509 lookup has been called.")
               of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
-                SSLError()
+                raiseSslError()
               else:
-                SSLError("Unknown error")
+                raiseSslError("Unknown error")
           client.sslNoHandshake = false
 
     if client.isSSL and client.sslNoHandshake:
@@ -813,16 +813,16 @@ proc connect*(socket: Socket, address: string, port = Port(0),
         let err = SSLGetError(socket.sslHandle, ret)
         case err
         of SSL_ERROR_ZERO_RETURN:
-          SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
+          raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
         of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT, 
            SSL_ERROR_WANT_ACCEPT:
-          SSLError("The operation did not complete. Perhaps you should use connectAsync?")
+          raiseSslError("The operation did not complete. Perhaps you should use connectAsync?")
         of SSL_ERROR_WANT_X509_LOOKUP:
-          SSLError("Function for x509 lookup has been called.")
+          raiseSslError("Function for x509 lookup has been called.")
         of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
-          SSLError()
+          raiseSslError()
         else:
-          SSLError("Unknown error")
+          raiseSslError("Unknown error")
         
   when false:
     var s: TSockAddrIn
@@ -901,19 +901,19 @@ when defined(ssl):
         var errret = SSLGetError(socket.sslHandle, ret)
         case errret
         of SSL_ERROR_ZERO_RETURN:
-          SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
+          raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
         of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT,
           SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE:
           return false
         of SSL_ERROR_WANT_X509_LOOKUP:
-          SSLError("Function for x509 lookup has been called.")
+          raiseSslError("Function for x509 lookup has been called.")
         of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
-          SSLError()
+          raiseSslError()
         else:
-          SSLError("Unknown Error")
+          raiseSslError("Unknown Error")
       socket.sslNoHandshake = false
     else:
-      SSLError("Socket is not an SSL socket.")
+      raiseSslError("Socket is not an SSL socket.")
 
   proc gotHandshake*(socket: TSocket): bool =
     ## Determines whether a handshake has occurred between a client (``socket``)
@@ -923,7 +923,7 @@ when defined(ssl):
     if socket.isSSL:
       return not socket.sslNoHandshake
     else:
-      SSLError("Socket is not an SSL socket.")
+      raiseSslError("Socket is not an SSL socket.")
 
 proc timeValFromMilliseconds(timeout = 500): Timeval =
   if timeout != -1:
@@ -1412,7 +1412,7 @@ proc recv*(socket: Socket): TaintedString {.tags: [ReadIOEffect], deprecated.} =
     while true:
       var bytesRead = recv(socket, cstring(buf), bufSize-1)
       # Error
-      if bytesRead == -1: OSError(OSLastError())
+      if bytesRead == -1: OSError(osLastError())
       
       buf[bytesRead] = '\0' # might not be necessary
       setLen(buf, bytesRead)
@@ -1457,16 +1457,16 @@ proc recvAsync*(socket: Socket, s: var TaintedString): bool {.
           var ret = SSLGetError(socket.sslHandle, bytesRead.cint)
           case ret
           of SSL_ERROR_ZERO_RETURN:
-            SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
+            raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
           of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
-            SSLError("Unexpected error occured.") # This should just not happen.
+            raiseSslError("Unexpected error occured.") # This should just not happen.
           of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
             return false
           of SSL_ERROR_WANT_X509_LOOKUP:
-            SSLError("Function for x509 lookup has been called.")
+            raiseSslError("Function for x509 lookup has been called.")
           of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
-            SSLError()
-          else: SSLError("Unknown Error")
+            raiseSslError()
+          else: raiseSslError("Unknown Error")
           
     if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false):
       let err = osLastError()
@@ -1578,7 +1578,7 @@ proc send*(socket: Socket, data: string) {.tags: [WriteIOEffect].} =
   if sent < 0:
     when defined(ssl):
       if socket.isSSL:
-        SSLError()
+        raiseSslError()
     
     raiseOSError(osLastError())
 
@@ -1600,16 +1600,16 @@ proc sendAsync*(socket: Socket, data: string): int {.tags: [WriteIOEffect].} =
           let ret = SSLGetError(socket.sslHandle, result.cint)
           case ret
           of SSL_ERROR_ZERO_RETURN:
-            SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.")
+            raiseSslError("TLS/SSL connection failed to initiate, socket closed prematurely.")
           of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
-            SSLError("Unexpected error occured.") # This should just not happen.
+            raiseSslError("Unexpected error occured.") # This should just not happen.
           of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
             return 0
           of SSL_ERROR_WANT_X509_LOOKUP:
-            SSLError("Function for x509 lookup has been called.")
+            raiseSslError("Function for x509 lookup has been called.")
           of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
-            SSLError()
-          else: SSLError("Unknown Error")
+            raiseSslError()
+          else: raiseSslError("Unknown Error")
       else:
         return
   if result == -1:
@@ -1692,7 +1692,7 @@ discard """ proc setReuseAddr*(s: TSocket) =
   var blah: int = 1
   var mode = SO_REUSEADDR
   if setsockopt(s.fd, SOL_SOCKET, mode, addr blah, TSOcklen(sizeof(int))) == -1:
-    OSError(OSLastError()) """
+    raiseOSError(osLastError()) """
 
 proc connect*(socket: Socket, address: string, port = Port(0), timeout: int,
              af: Domain = AF_INET) {.tags: [ReadIOEffect, WriteIOEffect].} =
diff --git a/lib/wrappers/openssl.nim b/lib/wrappers/openssl.nim
index da684eed9..7f24507ba 100644
--- a/lib/wrappers/openssl.nim
+++ b/lib/wrappers/openssl.nim
@@ -63,14 +63,13 @@ type
   SslStruct {.final, pure.} = object
   SslPtr* = ptr SslStruct
   PSslPtr* = ptr SslPtr
-  PSSL_CTX* = SslPtr
-  PSSL* = SslPtr
+  SslCtx* = SslPtr
   PSSL_METHOD* = SslPtr
   PX509* = SslPtr
   PX509_NAME* = SslPtr
   PEVP_MD* = SslPtr
   PBIO_METHOD* = SslPtr
-  PBIO* = SslPtr
+  BIO* = SslPtr
   EVP_PKEY* = SslPtr
   PRSA* = SslPtr
   PASN1_UTCTIME* = SslPtr
@@ -85,6 +84,8 @@ type
 
   des_key_schedule* = array[1..16, des_ks_struct]
 
+{.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].}
+
 const 
   EVP_MAX_MD_SIZE* = 16 + 20
   SSL_ERROR_NONE* = 0
@@ -282,6 +283,34 @@ proc SSL_CTX_ctrl*(ctx: PSSL_CTX, cmd: cInt, larg: int, parg: pointer): int{.
 proc SSLCTXSetMode*(ctx: PSSL_CTX, mode: int): int =
   result = SSL_CTX_ctrl(ctx, SSL_CTRL_MODE, mode, nil)
 
+proc bioNew*(b: PBIO_METHOD): PBIO{.cdecl, dynlib: DLLUtilName, importc: "BIO_new".}
+proc bioFreeAll*(b: PBIO){.cdecl, dynlib: DLLUtilName, importc: "BIO_free_all".}
+proc bioSMem*(): PBIO_METHOD{.cdecl, dynlib: DLLUtilName, importc: "BIO_s_mem".}
+proc bioCtrlPending*(b: PBIO): cInt{.cdecl, dynlib: DLLUtilName, importc: "BIO_ctrl_pending".}
+proc bioRead*(b: PBIO, Buf: cstring, length: cInt): cInt{.cdecl,
+    dynlib: DLLUtilName, importc: "BIO_read".}
+proc bioWrite*(b: PBIO, Buf: cstring, length: cInt): cInt{.cdecl,
+    dynlib: DLLUtilName, importc: "BIO_write".}
+
+proc sslSetConnectState*(s: SslPtr) {.cdecl,
+    dynlib: DLLSSLName, importc: "SSL_set_connect_state".}
+proc sslSetAcceptState*(s: SslPtr) {.cdecl,
+    dynlib: DLLSSLName, importc: "SSL_set_accept_state".}
+
+proc sslRead*(ssl: SslPtr, buf: cstring, num: cInt): cInt{.cdecl,
+      dynlib: DLLSSLName, importc: "SSL_read".}
+proc sslPeek*(ssl: SslPtr, buf: cstring, num: cInt): cInt{.cdecl,
+    dynlib: DLLSSLName, importc: "SSL_peek".}
+proc sslWrite*(ssl: SslPtr, buf: cstring, num: cInt): cInt{.cdecl,
+    dynlib: DLLSSLName, importc: "SSL_write".}
+
+proc sslSetBio*(ssl: SslPtr, rbio, wbio: BIO) {.cdecl,
+    dynlib: DLLSSLName, importc: "SSL_set_bio".}
+
+proc sslDoHandshake*(ssl: SslPtr): cint {.cdecl,
+    dynlib: DLLSSLName, importc: "SSL_do_handshake".}
+
+
 when true:
   discard
 else:
@@ -328,12 +357,7 @@ else:
 
   proc SslConnect*(ssl: PSSL): cInt{.cdecl, dynlib: DLLSSLName, importc.}
 
-  proc SslRead*(ssl: PSSL, buf: SslPtr, num: cInt): cInt{.cdecl, 
-      dynlib: DLLSSLName, importc.}
-  proc SslPeek*(ssl: PSSL, buf: SslPtr, num: cInt): cInt{.cdecl, 
-      dynlib: DLLSSLName, importc.}
-  proc SslWrite*(ssl: PSSL, buf: SslPtr, num: cInt): cInt{.cdecl, 
-      dynlib: DLLSSLName, importc.}
+  
   proc SslGetVersion*(ssl: PSSL): cstring{.cdecl, dynlib: DLLSSLName, importc.}
   proc SslGetPeerCertificate*(ssl: PSSL): PX509{.cdecl, dynlib: DLLSSLName, 
       importc.}
@@ -393,14 +417,7 @@ else:
   proc OPENSSLaddallalgorithms*(){.cdecl, dynlib: DLLUtilName, importc.}
   proc CRYPTOcleanupAllExData*(){.cdecl, dynlib: DLLUtilName, importc.}
   proc RandScreen*(){.cdecl, dynlib: DLLUtilName, importc.}
-  proc BioNew*(b: PBIO_METHOD): PBIO{.cdecl, dynlib: DLLUtilName, importc.}
-  proc BioFreeAll*(b: PBIO){.cdecl, dynlib: DLLUtilName, importc.}
-  proc BioSMem*(): PBIO_METHOD{.cdecl, dynlib: DLLUtilName, importc.}
-  proc BioCtrlPending*(b: PBIO): cInt{.cdecl, dynlib: DLLUtilName, importc.}
-  proc BioRead*(b: PBIO, Buf: cstring, length: cInt): cInt{.cdecl, 
-      dynlib: DLLUtilName, importc.}
-  proc BioWrite*(b: PBIO, Buf: cstring, length: cInt): cInt{.cdecl, 
-      dynlib: DLLUtilName, importc.}
+
   proc d2iPKCS12bio*(b: PBIO, Pkcs12: SslPtr): SslPtr{.cdecl, dynlib: DLLUtilName, 
       importc.}
   proc PKCS12parse*(p12: SslPtr, pass: cstring, pkey, cert, ca: var SslPtr): cint{.