Added support for sending "ehlo" and receiving multiline "ehlo" response. (#19077)

* Added support for sending "ehlo"
and receiving multiline "ehlo" response.

Signed-off-by: David Krause <enthus1ast@users.noreply.github.com>

* fix typo

Signed-off-by: David Krause <enthus1ast@users.noreply.github.com>

* send "EHLO" first, if not supported, send "HELO" to smtp server.

Signed-off-by: David Krause <enthus1ast@users.noreply.github.com>

* fix english

Signed-off-by: David Krause <enthus1ast@users.noreply.github.com>

* add changelog entry for smtp `ehlo`

Signed-off-by: David Krause <enthus1ast@users.noreply.github.com>

* recvEhlo must not be exported

Signed-off-by: David Krause <enthus1ast@users.noreply.github.com>
This commit is contained in:
David Krause 2021-11-03 09:14:59 +01:00 • committed by GitHub
commit f7cfc46ae1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 26 additions and 9 deletions

View file

@ -7,7 +7,9 @@
## Standard library additions and changes ## Standard library additions and changes
## `std/smtp`
- Sends `ehlo` first. If the mail server does not understand, it sends `helo` as a fallback.
## Language changes ## Language changes

View file

@ -100,7 +100,6 @@ proc debugRecv*(smtp: Smtp | AsyncSmtp): Future[string] {.multisync.} =
## `SMTP extensions<https://en.wikipedia.org/wiki/Extended_SMTP>`_. ## `SMTP extensions<https://en.wikipedia.org/wiki/Extended_SMTP>`_.
## ##
## See `checkReply(reply)<#checkReply,AsyncSmtp,string>`_. ## See `checkReply(reply)<#checkReply,AsyncSmtp,string>`_.
result = await smtp.sock.recvLine() result = await smtp.sock.recvLine()
if smtp.debug: if smtp.debug:
echo("S:" & result) echo("S:" & result)
@ -171,8 +170,7 @@ proc `$`*(msg: Message): string =
result.add("\c\L") result.add("\c\L")
result.add(msg.msgBody) result.add(msg.msgBody)
proc newSmtp*(useSsl = false, debug = false, proc newSmtp*(useSsl = false, debug = false, sslContext: SslContext = nil): Smtp =
sslContext: SslContext = nil): Smtp =
## Creates a new `Smtp` instance. ## Creates a new `Smtp` instance.
new result new result
result.debug = debug result.debug = debug
@ -186,12 +184,10 @@ proc newSmtp*(useSsl = false, debug = false,
else: else:
{.error: "SMTP module compiled without SSL support".} {.error: "SMTP module compiled without SSL support".}
proc newAsyncSmtp*(useSsl = false, debug = false, proc newAsyncSmtp*(useSsl = false, debug = false, sslContext: SslContext = nil): AsyncSmtp =
sslContext: SslContext = nil): AsyncSmtp =
## Creates a new `AsyncSmtp` instance. ## Creates a new `AsyncSmtp` instance.
new result new result
result.debug = debug result.debug = debug
result.sock = newAsyncSocket() result.sock = newAsyncSocket()
if useSsl: if useSsl:
when compiledWithSsl: when compiledWithSsl:
@ -220,7 +216,6 @@ proc checkReply*(smtp: Smtp | AsyncSmtp, reply: string) {.multisync.} =
## would need to call when using this module. One exception to ## would need to call when using this module. One exception to
## this is if you are implementing any ## this is if you are implementing any
## `SMTP extensions<https://en.wikipedia.org/wiki/Extended_SMTP>`_. ## `SMTP extensions<https://en.wikipedia.org/wiki/Extended_SMTP>`_.
var line = await smtp.debugRecv() var line = await smtp.debugRecv()
if not line.startsWith(reply): if not line.startsWith(reply):
await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line) await quitExcpt(smtp, "Expected " & reply & " reply, got: " & line)
@ -230,6 +225,22 @@ proc helo*(smtp: Smtp | AsyncSmtp) {.multisync.} =
await smtp.debugSend("HELO " & smtp.address & "\c\L") await smtp.debugSend("HELO " & smtp.address & "\c\L")
await smtp.checkReply("250") await smtp.checkReply("250")
proc recvEhlo(smtp: Smtp | AsyncSmtp): Future[bool] {.multisync.} =
## Skips "250-" lines, read until "250 " found.
## Return `true` if server supports `EHLO`, false otherwise.
while true:
var line = await smtp.sock.recvLine()
if smtp.debug:
echo("S:" & line)
if line.startsWith("250-"): continue
elif line.startsWith("250 "): return true # last line
else: return false
proc ehlo*(smtp: Smtp | AsyncSmtp): Future[bool] {.multisync.} =
## Sends EHLO request.
await smtp.debugSend("EHLO " & smtp.address & "\c\L")
return await smtp.recvEhlo()
proc connect*(smtp: Smtp | AsyncSmtp, proc connect*(smtp: Smtp | AsyncSmtp,
address: string, port: Port) {.multisync.} = address: string, port: Port) {.multisync.} =
## Establishes a connection with a SMTP server. ## Establishes a connection with a SMTP server.
@ -237,7 +248,9 @@ proc connect*(smtp: Smtp | AsyncSmtp,
smtp.address = address smtp.address = address
await smtp.sock.connect(address, port) await smtp.sock.connect(address, port)
await smtp.checkReply("220") await smtp.checkReply("220")
await smtp.helo() let speaksEsmtp = await smtp.ehlo()
if not speaksEsmtp:
await smtp.helo()
proc startTls*(smtp: Smtp | AsyncSmtp, sslContext: SslContext = nil) {.multisync.} = proc startTls*(smtp: Smtp | AsyncSmtp, sslContext: SslContext = nil) {.multisync.} =
## Put the SMTP connection in TLS (Transport Layer Security) mode. ## Put the SMTP connection in TLS (Transport Layer Security) mode.
@ -249,7 +262,9 @@ proc startTls*(smtp: Smtp | AsyncSmtp, sslContext: SslContext = nil) {.multisync
getSSLContext().wrapConnectedSocket(smtp.sock, handshakeAsClient) getSSLContext().wrapConnectedSocket(smtp.sock, handshakeAsClient)
else: else:
sslContext.wrapConnectedSocket(smtp.sock, handshakeAsClient) sslContext.wrapConnectedSocket(smtp.sock, handshakeAsClient)
await smtp.helo() let speaksEsmtp = await smtp.ehlo()
if not speaksEsmtp:
await smtp.helo()
else: else:
{.error: "SMTP module compiled without SSL support".} {.error: "SMTP module compiled without SSL support".}