Merge branch 'bigbreak' of https://github.com/Araq/Nimrod into bigbreak

This commit is contained in:
Araq 2014-08-31 15:15:38 +02:00
commit de29ce8ca8
7 changed files with 408 additions and 144 deletions

View file

@ -255,7 +255,7 @@ proc doUpload(ftp: AsyncFtpClient, file: TFile,
await countdownFut or sendFut await countdownFut or sendFut
proc storeFile*(ftp: AsyncFtpClient, file, dest: string, proc store*(ftp: AsyncFtpClient, file, dest: string,
onProgressChanged = defaultOnProgressChanged) {.async.} = onProgressChanged = defaultOnProgressChanged) {.async.} =
## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this
## function asynchronously is recommended to view the progress of ## function asynchronously is recommended to view the progress of
@ -288,7 +288,7 @@ when isMainModule:
await ftp.connect() await ftp.connect()
echo await ftp.pwd() echo await ftp.pwd()
echo await ftp.listDirs() 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") await ftp.retrFile("payload.jpg", "payload2.jpg")
echo("Finished") echo("Finished")

View file

@ -47,6 +47,7 @@
import asyncdispatch import asyncdispatch
import rawsockets import rawsockets
import net import net
import os
when defined(ssl): when defined(ssl):
import openssl import openssl
@ -54,7 +55,22 @@ when defined(ssl):
type type
# TODO: I would prefer to just do: # TODO: I would prefer to just do:
# PAsyncSocket* {.borrow: `.`.} = distinct PSocket. But that doesn't work. # 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 AsyncSocket* = ref AsyncSocketDesc
{.deprecated: [PAsyncSocket: AsyncSocket].} {.deprecated: [PAsyncSocket: AsyncSocket].}
@ -63,7 +79,7 @@ type
proc newSocket(fd: TAsyncFD, isBuff: bool): PAsyncSocket = proc newSocket(fd: TAsyncFD, isBuff: bool): PAsyncSocket =
assert fd != osInvalidSocket.TAsyncFD assert fd != osInvalidSocket.TAsyncFD
new(result.PSocket) new(result)
result.fd = fd.SocketHandle result.fd = fd.SocketHandle
result.isBuffered = isBuff result.isBuffered = isBuff
if isBuff: if isBuff:
@ -74,22 +90,94 @@ proc newAsyncSocket*(domain: TDomain = AF_INET, typ: TType = SOCK_STREAM,
## Creates a new asynchronous socket. ## Creates a new asynchronous socket.
result = newSocket(newAsyncRawSocket(domain, typ, protocol), buffered) 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, proc connect*(socket: PAsyncSocket, address: string, port: TPort,
af = AF_INET): Future[void] = af = AF_INET) {.async.} =
## Connects ``socket`` to server at ``address:port``. ## Connects ``socket`` to server at ``address:port``.
## ##
## Returns a ``Future`` which will complete when the connection succeeds ## Returns a ``Future`` which will complete when the connection succeeds
## or an error occurs. ## 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, proc readIntoBuf(socket: PAsyncSocket,
flags: set[TSocketFlags]): Future[int] {.async.} = flags: set[TSocketFlags]): Future[int] {.async.} =
var data = await recv(socket.fd.TAsyncFD, BufferSize, flags) var data = await recv(socket.fd.TAsyncFD, BufferSize, flags)
if data.len != 0: if data.len != 0:
copyMem(addr socket.buffer[0], addr data[0], data.len) copyMem(addr socket.buffer[0], addr data[0], data.len)
socket.bufLen = data.len if socket.isSsl:
socket.currPos = 0 when defined(ssl):
result = data.len # 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, proc recv*(socket: PAsyncSocket, size: int,
flags = {TSocketFlags.SafeDisconn}): Future[string] {.async.} = flags = {TSocketFlags.SafeDisconn}): Future[string] {.async.} =
@ -131,11 +219,18 @@ proc recv*(socket: PAsyncSocket, size: int,
result = await recv(socket.fd.TAsyncFD, size, flags) result = await recv(socket.fd.TAsyncFD, size, flags)
proc send*(socket: PAsyncSocket, data: string, 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 ## Sends ``data`` to ``socket``. The returned future will complete once all
## data has been sent. ## data has been sent.
assert socket != nil 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}): proc acceptAddr*(socket: PAsyncSocket, flags = {TSocketFlags.SafeDisconn}):
Future[tuple[address: string, client: PAsyncSocket]] = Future[tuple[address: string, client: PAsyncSocket]] =
@ -240,24 +335,67 @@ proc recvLine*(socket: PAsyncSocket,
return return
add(result.string, c) add(result.string, c)
proc bindAddr*(socket: PAsyncSocket, port = TPort(0), address = "") = proc listen*(socket: Socket, backlog = SOMAXCONN) {.tags: [ReadIOEffect].} =
## 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) =
## Marks ``socket`` as accepting connections. ## Marks ``socket`` as accepting connections.
## ``Backlog`` specifies the maximum length of the ## ``Backlog`` specifies the maximum length of the
## queue of pending connections. ## queue of pending connections.
## ##
## Raises an EOS error upon failure. ## 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) = proc close*(socket: PAsyncSocket) =
## Closes the socket. ## Closes the socket.
socket.fd.TAsyncFD.closeSocket() 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: when isMainModule:
type type

View file

@ -78,6 +78,7 @@
import sockets, strutils, parseurl, parseutils, strtabs, base64, os import sockets, strutils, parseurl, parseutils, strtabs, base64, os
import asyncnet, asyncdispatch import asyncnet, asyncdispatch
import rawsockets import rawsockets
from net import nil
type type
Response* = tuple[ Response* = tuple[
@ -164,16 +165,17 @@ proc parseBody(s: TSocket, headers: PStringTable, timeout: int): string =
var contentLengthHeader = headers["Content-Length"] var contentLengthHeader = headers["Content-Length"]
if contentLengthHeader != "": if contentLengthHeader != "":
var length = contentLengthHeader.parseint() var length = contentLengthHeader.parseint()
result = newString(length) if length > 0:
var received = 0 result = newString(length)
while true: var received = 0
if received >= length: break while true:
let r = s.recv(addr(result[received]), length-received, timeout) if received >= length: break
if r == 0: break let r = s.recv(addr(result[received]), length-received, timeout)
received += r if r == 0: break
if received != length: received += r
httpError("Got invalid content length. Expected: " & $length & if received != length:
" got: " & $received) httpError("Got invalid content length. Expected: " & $length &
" got: " & $received)
else: else:
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.4 TODO # (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 # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
if headers["Connection"] == "close": if headers["Connection"] == "close":
var buf = "" var buf = ""
while True: while true:
buf = newString(4000) buf = newString(4000)
let r = s.recv(addr(buf[0]), 4000, timeout) let r = s.recv(addr(buf[0]), 4000, timeout)
if r == 0: break if r == 0: break
@ -194,7 +196,7 @@ proc parseResponse(s: TSocket, getBody: bool, timeout: int): TResponse =
var fullyRead = false var fullyRead = false
var line = "" var line = ""
result.headers = newStringTable(modeCaseInsensitive) result.headers = newStringTable(modeCaseInsensitive)
while True: while true:
line = "" line = ""
linei = 0 linei = 0
s.readLine(line, timeout) s.readLine(line, timeout)
@ -294,7 +296,7 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
add(headers, "\c\L") add(headers, "\c\L")
var s = socket() var s = socket()
if s == InvalidSocket: raiseOSError(osLastError()) if s == invalidSocket: raiseOSError(osLastError())
var port = sockets.TPort(80) var port = sockets.TPort(80)
if r.scheme == "https": if r.scheme == "https":
when defined(ssl): when defined(ssl):
@ -321,7 +323,7 @@ proc redirection(status: string): bool =
const redirectionNRs = ["301", "302", "303", "307"] const redirectionNRs = ["301", "302", "303", "307"]
for i in items(redirectionNRs): for i in items(redirectionNRs):
if status.startsWith(i): if status.startsWith(i):
return True return true
proc getNewLocation(lastUrl: string, headers: PStringTable): string = proc getNewLocation(lastUrl: string, headers: PStringTable): string =
result = headers["Location"] result = headers["Location"]
@ -444,11 +446,13 @@ type
headers: StringTableRef headers: StringTableRef
maxRedirects: int maxRedirects: int
userAgent: string userAgent: string
when defined(ssl):
sslContext: net.SslContext
{.deprecated: [PAsyncHttpClient: AsyncHttpClient].} {.deprecated: [PAsyncHttpClient: AsyncHttpClient].}
proc newAsyncHttpClient*(userAgent = defUserAgent, proc newAsyncHttpClient*(userAgent = defUserAgent,
maxRedirects = 5): AsyncHttpClient = maxRedirects = 5, sslContext = defaultSslContext): AsyncHttpClient =
## Creates a new PAsyncHttpClient instance. ## Creates a new PAsyncHttpClient instance.
## ##
## ``userAgent`` specifies the user agent that will be used when making ## ``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, ## ``maxRedirects`` specifies the maximum amount of redirects to follow,
## default is 5. ## default is 5.
##
## ``sslContext`` specifies the SSL context to use for HTTPS requests.
new result new result
result.headers = newStringTable(modeCaseInsensitive) result.headers = newStringTable(modeCaseInsensitive)
result.userAgent = defUserAgent result.userAgent = defUserAgent
result.maxRedirects = maxRedirects result.maxRedirects = maxRedirects
result.sslContext = net.SslContext(sslContext)
proc close*(client: AsyncHttpClient) = proc close*(client: AsyncHttpClient) =
## Closes any connections held by the HTTP client. ## Closes any connections held by the HTTP client.
@ -467,7 +474,7 @@ proc close*(client: AsyncHttpClient) =
client.socket.close() client.socket.close()
client.connected = false 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. ## Ensures that all the data requested is read and returned.
result = "" result = ""
while true: while true:
@ -476,7 +483,7 @@ proc recvFull(socket: PAsyncSocket, size: int): PFuture[string] {.async.} =
if data == "": break # We've been disconnected. if data == "": break # We've been disconnected.
result.add data result.add data
proc parseChunks(client: PAsyncHttpClient): PFuture[string] {.async.} = proc parseChunks(client: PAsyncHttpClient): Future[string] {.async.} =
result = "" result = ""
var ri = 0 var ri = 0
while true: while true:
@ -509,7 +516,7 @@ proc parseChunks(client: PAsyncHttpClient): PFuture[string] {.async.} =
# them: http://tools.ietf.org/html/rfc2616#section-3.6.1 # them: http://tools.ietf.org/html/rfc2616#section-3.6.1
proc parseBody(client: PAsyncHttpClient, proc parseBody(client: PAsyncHttpClient,
headers: PStringTable): PFuture[string] {.async.} = headers: PStringTable): Future[string] {.async.} =
result = "" result = ""
if headers["Transfer-Encoding"] == "chunked": if headers["Transfer-Encoding"] == "chunked":
result = await parseChunks(client) result = await parseChunks(client)
@ -519,12 +526,13 @@ proc parseBody(client: PAsyncHttpClient,
var contentLengthHeader = headers["Content-Length"] var contentLengthHeader = headers["Content-Length"]
if contentLengthHeader != "": if contentLengthHeader != "":
var length = contentLengthHeader.parseint() var length = contentLengthHeader.parseint()
result = await client.socket.recvFull(length) if length > 0:
if result == "": result = await client.socket.recvFull(length)
httpError("Got disconnected while trying to read body.") if result == "":
if result.len != length: httpError("Got disconnected while trying to read body.")
httpError("Received length doesn't match expected length. Wanted " & if result.len != length:
$length & " got " & $result.len) httpError("Received length doesn't match expected length. Wanted " &
$length & " got " & $result.len)
else: else:
# (http://tools.ietf.org/html/rfc2616#section-4.4) NR.4 TODO # (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 # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5
if headers["Connection"] == "close": if headers["Connection"] == "close":
var buf = "" var buf = ""
while True: while true:
buf = await client.socket.recvFull(4000) buf = await client.socket.recvFull(4000)
if buf == "": break if buf == "": break
result.add(buf) result.add(buf)
proc parseResponse(client: PAsyncHttpClient, proc parseResponse(client: PAsyncHttpClient,
getBody: bool): PFuture[TResponse] {.async.} = getBody: bool): Future[TResponse] {.async.} =
var parsedStatus = false var parsedStatus = false
var linei = 0 var linei = 0
var fullyRead = false var fullyRead = false
var line = "" var line = ""
result.headers = newStringTable(modeCaseInsensitive) result.headers = newStringTable(modeCaseInsensitive)
while True: while true:
linei = 0 linei = 0
line = await client.socket.recvLine() line = await client.socket.recvLine()
if line == "": break # We've been disconnected. if line == "": break # We've been disconnected.
@ -590,20 +598,29 @@ proc newConnection(client: PAsyncHttpClient, url: TURL) {.async.} =
client.currentURL.scheme != url.scheme: client.currentURL.scheme != url.scheme:
if client.connected: client.close() if client.connected: client.close()
client.socket = newAsyncSocket() client.socket = newAsyncSocket()
if url.scheme == "https":
assert false, "TODO SSL"
# TODO: I should be able to write 'net.TPort' here... # TODO: I should be able to write 'net.TPort' here...
let port = 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) 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) await client.socket.connect(url.hostname, port)
client.currentURL = url client.currentURL = url
client.connected = true client.connected = true
proc request*(client: PAsyncHttpClient, url: string, httpMethod = httpGET, 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 ## Connects to the hostname specified by the URL and performs a request
## using the method specified. ## using the method specified.
## ##
@ -626,7 +643,7 @@ proc request*(client: PAsyncHttpClient, url: string, httpMethod = httpGET,
result = await parseResponse(client, httpMethod != httpHEAD) 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. ## 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 ## This procedure will follow redirects up to a maximum number of redirects

View file

@ -22,17 +22,17 @@ when defined(ssl):
when defined(ssl): when defined(ssl):
type type
SSLError* = object of Exception SslError* = object of Exception
SSLCVerifyMode* = enum SslCVerifyMode* = enum
CVerifyNone, CVerifyPeer CVerifyNone, CVerifyPeer
SSLProtVersion* = enum SslProtVersion* = enum
protSSLv2, protSSLv3, protTLSv1, protSSLv23 protSSLv2, protSSLv3, protTLSv1, protSSLv23
SSLContext* = distinct PSSLCTX SslContext* = distinct SslCtx
SSLAcceptResult* = enum SslAcceptResult* = enum
AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess
{.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode, {.deprecated: [ESSL: SSLError, TSSLCVerifyMode: SSLCVerifyMode,
@ -125,7 +125,8 @@ when defined(ssl):
ErrLoadBioStrings() ErrLoadBioStrings()
OpenSSL_add_all_algorithms() OpenSSL_add_all_algorithms()
proc raiseSSLError(s = "") = proc raiseSSLError*(s = "") =
## Raises a new SSL error.
if s != "": if s != "":
raise newException(SSLError, s) raise newException(SSLError, s)
let err = ErrPeekLastError() let err = ErrPeekLastError()
@ -161,7 +162,7 @@ when defined(ssl):
certFile = "", keyFile = ""): PSSLContext = certFile = "", keyFile = ""): PSSLContext =
## Creates an SSL context. ## 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 ## are available with the addition of ``protSSLv23`` which allows for
## compatibility with all of them. ## compatibility with all of them.
## ##

View file

@ -28,7 +28,8 @@
## For SSL support this module relies on OpenSSL. If you want to ## For SSL support this module relies on OpenSSL. If you want to
## enable SSL, compile with ``-d:ssl``. ## enable SSL, compile with ``-d:ssl``.
import sockets, strutils, strtabs, base64, os import net, strutils, strtabs, base64, os
import asyncnet, asyncdispatch
type type
Smtp* = object Smtp* = object
@ -44,8 +45,15 @@ type
ReplyError* = object of IOError ReplyError* = object of IOError
AsyncSmtp* = ref object
sock: AsyncSocket
address: string
port: Port
useSsl: bool
debug: bool
{.deprecated: [EInvalidReply: ReplyError, TMessage: Message, TSMTP: Smtp].} {.deprecated: [EInvalidReply: ReplyError, TMessage: Message, TSMTP: Smtp].}
proc debugSend(smtp: TSMTP, cmd: string) = proc debugSend(smtp: TSMTP, cmd: string) =
if smtp.debug: if smtp.debug:
echo("C:" & cmd) echo("C:" & cmd)
@ -70,19 +78,25 @@ proc checkReply(smtp: var TSMTP, reply: string) =
const compiledWithSsl = defined(ssl) const compiledWithSsl = defined(ssl)
proc connect*(address: string, port = 25, when not defined(ssl):
ssl = false, debug = false): TSMTP = 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. ## Establishes a connection with a SMTP server.
## May fail with EInvalidReply or with a socket error. ## May fail with EInvalidReply or with a socket error.
result.sock = socket() result.sock = newSocket()
if ssl: if ssl:
when compiledWithSsl: when compiledWithSsl:
let ctx = newContext(verifyMode = CVerifyNone) sslContext.wrapSocket(result.sock)
ctx.wrapSocket(result.sock)
else: else:
raise newException(ESystem, raise newException(ESystem,
"SMTP module compiled without SSL support") "SMTP module compiled without SSL support")
result.sock.connect(address, TPort(port)) result.sock.connect(address, port)
result.debug = debug result.debug = debug
result.checkReply("220") result.checkReply("220")
@ -162,6 +176,82 @@ proc `$`*(msg: TMessage): string =
result.add("\c\L") result.add("\c\L")
result.add(msg.msgBody) 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: when isMainModule:
#var msg = createMessage("Test subject!", #var msg = createMessage("Test subject!",
@ -172,15 +262,16 @@ when isMainModule:
#smtp.sendmail("root@localhost", @["dominik@localhost"], $msg) #smtp.sendmail("root@localhost", @["dominik@localhost"], $msg)
#echo(decode("a17sm3701420wbe.12")) #echo(decode("a17sm3701420wbe.12"))
var msg = createMessage("Hello from Nim's SMTP!", proc main() {.async.} =
"Hello!!!!.\n Is this awesome or what?", var client = newAsyncSmtp("smtp.gmail.com", Port(465), true)
@["someone@yahoo.com", "someone@gmail.com"]) await client.connect()
echo(msg) 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)
var smtp = connect("smtp.gmail.com", 465, True, True) await client.close()
smtp.auth("someone", "password")
smtp.sendmail("someone@gmail.com",
@["someone@yahoo.com", "someone@gmail.com"], $msg)
smtp.close()
waitFor main()

View file

@ -251,14 +251,14 @@ when defined(ssl):
ErrLoadBioStrings() ErrLoadBioStrings()
OpenSSL_add_all_algorithms() OpenSSL_add_all_algorithms()
proc SSLError(s = "") = proc raiseSSLError(s = "") =
if s != "": if s != "":
raise newException(ESSL, s) raise newException(ESSL, s)
let err = ErrPeekLastError() let err = ErrPeekLastError()
if err == 0: if err == 0:
raise newException(ESSL, "No error reported.") raise newException(ESSL, "No error reported.")
if err == -1: if err == -1:
OSError(OSLastError()) raiseOSError(osLastError())
var errStr = ErrErrorString(err, nil) var errStr = ErrErrorString(err, nil)
raise newException(ESSL, $errStr) raise newException(ESSL, $errStr)
@ -272,18 +272,18 @@ when defined(ssl):
if certFile != "": if certFile != "":
var ret = SSLCTXUseCertificateChainFile(ctx, certFile) var ret = SSLCTXUseCertificateChainFile(ctx, certFile)
if ret != 1: if ret != 1:
SSLError() raiseSslError()
# TODO: Password? www.rtfm.com/openssl-examples/part1.pdf # TODO: Password? www.rtfm.com/openssl-examples/part1.pdf
if keyFile != "": if keyFile != "":
if SSL_CTX_use_PrivateKey_file(ctx, keyFile, if SSL_CTX_use_PrivateKey_file(ctx, keyFile,
SSL_FILETYPE_PEM) != 1: SSL_FILETYPE_PEM) != 1:
SSLError() raiseSslError()
if SSL_CTX_check_private_key(ctx) != 1: 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 = certFile = "", keyFile = ""): PSSLContext =
## Creates an SSL context. ## Creates an SSL context.
## ##
@ -308,21 +308,21 @@ when defined(ssl):
when not defined(linux) and not defined(OpenBSD): when not defined(linux) and not defined(OpenBSD):
newCTX = SSL_CTX_new(SSLv2_method()) newCTX = SSL_CTX_new(SSLv2_method())
else: else:
SSLError() raiseSslError()
of protSSLv3: of protSSLv3:
newCTX = SSL_CTX_new(SSLv3_method()) newCTX = SSL_CTX_new(SSLv3_method())
of protTLSv1: of protTLSv1:
newCTX = SSL_CTX_new(TLSv1_method()) newCTX = SSL_CTX_new(TLSv1_method())
if newCTX.SSLCTXSetCipherList("ALL") != 1: if newCTX.SSLCTXSetCipherList("ALL") != 1:
SSLError() raiseSslError()
case verifyMode case verifyMode
of CVerifyPeer: of CVerifyPeer:
newCTX.SSLCTXSetVerify(SSLVerifyPeer, nil) newCTX.SSLCTXSetVerify(SSLVerifyPeer, nil)
of CVerifyNone: of CVerifyNone:
newCTX.SSLCTXSetVerify(SSLVerifyNone, nil) newCTX.SSLCTXSetVerify(SSLVerifyNone, nil)
if newCTX == nil: if newCTX == nil:
SSLError() raiseSslError()
discard newCTX.SSLCTXSetMode(SSL_MODE_AUTO_RETRY) discard newCTX.SSLCTXSetMode(SSL_MODE_AUTO_RETRY)
newCTX.loadCertificates(certFile, keyFile) newCTX.loadCertificates(certFile, keyFile)
@ -341,10 +341,10 @@ when defined(ssl):
socket.sslNoHandshake = false socket.sslNoHandshake = false
socket.sslHasPeekChar = false socket.sslHasPeekChar = false
if socket.sslHandle == nil: if socket.sslHandle == nil:
SSLError() raiseSslError()
if SSLSetFd(socket.sslHandle, socket.fd) != 1: if SSLSetFd(socket.sslHandle, socket.fd) != 1:
SSLError() raiseSslError()
proc raiseSocketError*(socket: Socket, err: int = -1, async = false) = proc raiseSocketError*(socket: Socket, err: int = -1, async = false) =
## Raises proper errors based on return values of ``recv`` functions. ## 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) var ret = SSLGetError(socket.sslHandle, err.cint)
case ret case ret
of SSL_ERROR_ZERO_RETURN: 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: of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
if async: if async:
return return
else: SSLError("Not enough data on socket.") else: raiseSslError("Not enough data on socket.")
of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ: of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
if async: if async:
return return
else: SSLError("Not enough data on socket.") else: raiseSslError("Not enough data on socket.")
of SSL_ERROR_WANT_X509_LOOKUP: 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: of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError() raiseSslError()
else: SSLError("Unknown Error") else: raiseSslError("Unknown Error")
if err == -1 and not (when defined(ssl): socket.isSSL else: false): if err == -1 and not (when defined(ssl): socket.isSSL else: false):
let lastError = osLastError() let lastError = osLastError()
@ -545,16 +545,16 @@ proc acceptAddr*(server: Socket, client: var Socket, address: var string) {.
if err != SSL_ERROR_WANT_ACCEPT: if err != SSL_ERROR_WANT_ACCEPT:
case err case err
of SSL_ERROR_ZERO_RETURN: 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, of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE,
SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: 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: 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: of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError() raiseSslError()
else: else:
SSLError("Unknown error") raiseSslError("Unknown error")
proc setBlocking*(s: Socket, blocking: bool) {.tags: [], gcsafe.} proc setBlocking*(s: Socket, blocking: bool) {.tags: [], gcsafe.}
## Sets blocking mode on socket ## Sets blocking mode on socket
@ -591,17 +591,17 @@ when defined(ssl):
if err != SSL_ERROR_WANT_ACCEPT: if err != SSL_ERROR_WANT_ACCEPT:
case err case err
of SSL_ERROR_ZERO_RETURN: 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, of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE,
SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT:
client.sslNoHandshake = true client.sslNoHandshake = true
return AcceptNoHandshake return AcceptNoHandshake
of SSL_ERROR_WANT_X509_LOOKUP: 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: of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError() raiseSslError()
else: else:
SSLError("Unknown error") raiseSslError("Unknown error")
client.sslNoHandshake = false client.sslNoHandshake = false
if client.isSSL and client.sslNoHandshake: 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) let err = SSLGetError(socket.sslHandle, ret)
case err case err
of SSL_ERROR_ZERO_RETURN: 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, of SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_CONNECT,
SSL_ERROR_WANT_ACCEPT: 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: 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: of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError() raiseSslError()
else: else:
SSLError("Unknown error") raiseSslError("Unknown error")
when false: when false:
var s: TSockAddrIn var s: TSockAddrIn
@ -901,19 +901,19 @@ when defined(ssl):
var errret = SSLGetError(socket.sslHandle, ret) var errret = SSLGetError(socket.sslHandle, ret)
case errret case errret
of SSL_ERROR_ZERO_RETURN: 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, of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT,
SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE: SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE:
return false return false
of SSL_ERROR_WANT_X509_LOOKUP: 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: of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError() raiseSslError()
else: else:
SSLError("Unknown Error") raiseSslError("Unknown Error")
socket.sslNoHandshake = false socket.sslNoHandshake = false
else: else:
SSLError("Socket is not an SSL socket.") raiseSslError("Socket is not an SSL socket.")
proc gotHandshake*(socket: TSocket): bool = proc gotHandshake*(socket: TSocket): bool =
## Determines whether a handshake has occurred between a client (``socket``) ## Determines whether a handshake has occurred between a client (``socket``)
@ -923,7 +923,7 @@ when defined(ssl):
if socket.isSSL: if socket.isSSL:
return not socket.sslNoHandshake return not socket.sslNoHandshake
else: else:
SSLError("Socket is not an SSL socket.") raiseSslError("Socket is not an SSL socket.")
proc timeValFromMilliseconds(timeout = 500): Timeval = proc timeValFromMilliseconds(timeout = 500): Timeval =
if timeout != -1: if timeout != -1:
@ -1412,7 +1412,7 @@ proc recv*(socket: Socket): TaintedString {.tags: [ReadIOEffect], deprecated.} =
while true: while true:
var bytesRead = recv(socket, cstring(buf), bufSize-1) var bytesRead = recv(socket, cstring(buf), bufSize-1)
# Error # Error
if bytesRead == -1: OSError(OSLastError()) if bytesRead == -1: OSError(osLastError())
buf[bytesRead] = '\0' # might not be necessary buf[bytesRead] = '\0' # might not be necessary
setLen(buf, bytesRead) setLen(buf, bytesRead)
@ -1457,16 +1457,16 @@ proc recvAsync*(socket: Socket, s: var TaintedString): bool {.
var ret = SSLGetError(socket.sslHandle, bytesRead.cint) var ret = SSLGetError(socket.sslHandle, bytesRead.cint)
case ret case ret
of SSL_ERROR_ZERO_RETURN: 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: 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: of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
return false return false
of SSL_ERROR_WANT_X509_LOOKUP: 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: of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError() raiseSslError()
else: SSLError("Unknown Error") else: raiseSslError("Unknown Error")
if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false): if bytesRead == -1 and not (when defined(ssl): socket.isSSL else: false):
let err = osLastError() let err = osLastError()
@ -1578,7 +1578,7 @@ proc send*(socket: Socket, data: string) {.tags: [WriteIOEffect].} =
if sent < 0: if sent < 0:
when defined(ssl): when defined(ssl):
if socket.isSSL: if socket.isSSL:
SSLError() raiseSslError()
raiseOSError(osLastError()) raiseOSError(osLastError())
@ -1600,16 +1600,16 @@ proc sendAsync*(socket: Socket, data: string): int {.tags: [WriteIOEffect].} =
let ret = SSLGetError(socket.sslHandle, result.cint) let ret = SSLGetError(socket.sslHandle, result.cint)
case ret case ret
of SSL_ERROR_ZERO_RETURN: 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: 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: of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ:
return 0 return 0
of SSL_ERROR_WANT_X509_LOOKUP: 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: of SSL_ERROR_SYSCALL, SSL_ERROR_SSL:
SSLError() raiseSslError()
else: SSLError("Unknown Error") else: raiseSslError("Unknown Error")
else: else:
return return
if result == -1: if result == -1:
@ -1692,7 +1692,7 @@ discard """ proc setReuseAddr*(s: TSocket) =
var blah: int = 1 var blah: int = 1
var mode = SO_REUSEADDR var mode = SO_REUSEADDR
if setsockopt(s.fd, SOL_SOCKET, mode, addr blah, TSOcklen(sizeof(int))) == -1: 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, proc connect*(socket: Socket, address: string, port = Port(0), timeout: int,
af: Domain = AF_INET) {.tags: [ReadIOEffect, WriteIOEffect].} = af: Domain = AF_INET) {.tags: [ReadIOEffect, WriteIOEffect].} =

View file

@ -63,14 +63,13 @@ type
SslStruct {.final, pure.} = object SslStruct {.final, pure.} = object
SslPtr* = ptr SslStruct SslPtr* = ptr SslStruct
PSslPtr* = ptr SslPtr PSslPtr* = ptr SslPtr
PSSL_CTX* = SslPtr SslCtx* = SslPtr
PSSL* = SslPtr
PSSL_METHOD* = SslPtr PSSL_METHOD* = SslPtr
PX509* = SslPtr PX509* = SslPtr
PX509_NAME* = SslPtr PX509_NAME* = SslPtr
PEVP_MD* = SslPtr PEVP_MD* = SslPtr
PBIO_METHOD* = SslPtr PBIO_METHOD* = SslPtr
PBIO* = SslPtr BIO* = SslPtr
EVP_PKEY* = SslPtr EVP_PKEY* = SslPtr
PRSA* = SslPtr PRSA* = SslPtr
PASN1_UTCTIME* = SslPtr PASN1_UTCTIME* = SslPtr
@ -85,6 +84,8 @@ type
des_key_schedule* = array[1..16, des_ks_struct] des_key_schedule* = array[1..16, des_ks_struct]
{.deprecated: [PSSL: SslPtr, PSSL_CTX: SslCtx, PBIO: BIO].}
const const
EVP_MAX_MD_SIZE* = 16 + 20 EVP_MAX_MD_SIZE* = 16 + 20
SSL_ERROR_NONE* = 0 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 = proc SSLCTXSetMode*(ctx: PSSL_CTX, mode: int): int =
result = SSL_CTX_ctrl(ctx, SSL_CTRL_MODE, mode, nil) 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: when true:
discard discard
else: else:
@ -328,12 +357,7 @@ else:
proc SslConnect*(ssl: PSSL): cInt{.cdecl, dynlib: DLLSSLName, importc.} 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 SslGetVersion*(ssl: PSSL): cstring{.cdecl, dynlib: DLLSSLName, importc.}
proc SslGetPeerCertificate*(ssl: PSSL): PX509{.cdecl, dynlib: DLLSSLName, proc SslGetPeerCertificate*(ssl: PSSL): PX509{.cdecl, dynlib: DLLSSLName,
importc.} importc.}
@ -393,14 +417,7 @@ else:
proc OPENSSLaddallalgorithms*(){.cdecl, dynlib: DLLUtilName, importc.} proc OPENSSLaddallalgorithms*(){.cdecl, dynlib: DLLUtilName, importc.}
proc CRYPTOcleanupAllExData*(){.cdecl, dynlib: DLLUtilName, importc.} proc CRYPTOcleanupAllExData*(){.cdecl, dynlib: DLLUtilName, importc.}
proc RandScreen*(){.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, proc d2iPKCS12bio*(b: PBIO, Pkcs12: SslPtr): SslPtr{.cdecl, dynlib: DLLUtilName,
importc.} importc.}
proc PKCS12parse*(p12: SslPtr, pass: cstring, pkey, cert, ca: var SslPtr): cint{. proc PKCS12parse*(p12: SslPtr, pass: cstring, pkey, cert, ca: var SslPtr): cint{.