SMTP - 503 Bad sequence of commands when using nim's std/smtp

1.3k Views Asked by At

I am building a web application in the nim programming language and recently wanted to start implementing features regarding sending mails.

Nim has a library std/smtp for this that comes with some pretty simple examples for using both starttls and ssl.

For that purpose I created a dummy mail address on a local mail service provider to play around a bit and can't get it to work, despite following the examples as closely as I could.

Here the code I used:

import std/[smtp]

let smtpServerName = "smtp.web.de"
let startTlsportNumber = 587
let un = "dummymail" # replace with actual username
let pw = "dummypw" # replace with actual pw
let target = "[email protected]"

#StartTLS code block - Have either this or the SSL code block commented in, not both
let smtpConn = newSmtp(debug=true)
smtpConn.connect(smtpServerName, Port startTlsportNumber)
smtpConn.startTls()


#SSL code block - Have either this or the startTLS code block commented in, not both
# let sslPortNumber = 465
# let smtpConn = newSmtp(useSsl = true, debug=true)
# smtpConn.connect(smtpServerName, Port sslPortNumber)


var msg = createMessage(mSubject = "Hello from Nim's SMTP",
                        mBody = "Hello!.\n Is this awesome or what?",
                        mTo = @[target])
smtpConn.auth(un, pw)
smtpConn.sendmail(un, @[target], $msg)

With startTLS this causes a runtime error as the server sends back a 503 response on smtpConn.startTls:

S:220 web.de (mrweb105) Nemesis ESMTP Service ready
C:HELO smtp.web.de

S:250 web.de Hello smtp.web.de [46.183.103.17]
C:STARTTLS

S:503 Bad sequence of commands
C:QUIT
/home/philipp/dev/playground/src/playground.nim(11) playground
/usr/lib/nim/pure/smtp.nim(246) startTls
/usr/lib/nim/pure/smtp.nim(226) checkReply
/usr/lib/nim/pure/smtp.nim(110) quitExcpt
Error: unhandled exception: Expected 220 reply, got: 503 Bad sequence of commands [ReplyError]

With SSL this causes a runtime error as the server sends back a 503 response on smtpConn.connect:

S:220 web.de (mrweb005) Nemesis ESMTP Service ready
C:HELO smtp.web.de

S:503 Bad sequence of commands
C:QUIT
/home/philipp/dev/playground/src/playground.nim(15) playground
/usr/lib/nim/pure/smtp.nim(240) connect
/usr/lib/nim/pure/smtp.nim(231) helo
/usr/lib/nim/pure/smtp.nim(226) checkReply
/usr/lib/nim/pure/smtp.nim(110) quitExcpt
Error: unhandled exception: Expected 250 reply, got: 503 Bad sequence of commands [ReplyError]

I've triple checked the ports, the service provider states explicitly that 587 is the port for startTls and 465 for SSL. I compile on Arch Linux with -d:ssl in both cases, so that shouldn't be an issue either.

Trying the same with google-mail it all worked out, but I am slightly annoyed that this didn't work with my original mail provider.

Googling the error for a bit and looking at other questions of other programming languages, the error seems to be related to authentication? Which is weird, because I thought authentication starts after I made the connection secure with startTls. This looks to me like I'm using the std/smtp library wrong, but I don't quite see where. Does anyone here see the issue?

1

There are 1 best solutions below

0
xbello On

The server doesn't like the HELO that std/smtp uses, it needs EHLO:

$ telnet smtp.web.de 587
[ ... ]
220 web.de (mrweb105) Nemesis ESMTP Service ready
HELO smtp.web.de
250 web.de Hello smtp.web.de [92.191.80.254]
STARTTLS
503 Bad sequence of commands

$ telnet smtp.web.de 587
[ ... ]
220 web.de (mrweb006) Nemesis ESMTP Service ready
EHLO smtp.web.de
250-web.de Hello smtp.web.de [92.191.80.254]
250-8BITMIME
250-SIZE 141557760
250 STARTTLS
STARTTLS
220 OK

So you can get the Nim dev version with this patch, or copy-paste the patch code into your program while it doesn't reach the stable:

import std / [net, strutils, asyncnet, asyncdispatch]


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()
    echo("S:" & line) # Comment out this if you aren't debugging
    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 the EHLO request
  await smtp.debugSend("EHLO " & smtp.address & "\c\L")
  return await smtp.recvEhlo()

proc connect*(smtp: Smtp | AsyncSmtp, address: string, port: Port) {.multisync.} =
  smtp.address = address
  await smtp.sock.connect(address, port)
  await smtp.checkReply("220")
  let speaksEsmtp = await smtp.ehlo()
  if speaksEsmtp:
    await smtp.debugSend("STARTTLS\c\L")
    await smtp.checkReply("220")

let smtpConn = newSmtp(debug=true)
smtpConn.connect("smtp.web.de", Port 587)

Notice that you need to avoid the call to startTls because the unpatched version does a HELO that would raise another 503, so we send the STARTTLS at the end of our modified connect.

I've noticed that you need to modify your local smtp.nim file to make sock field for SmtpBase object public for this to work. The file is at nim-1.6.6/lib/pure/smtp.nim and you need to change:

62    sock: SocketType

to

62    sock*: SocketType

So it might be better to just patch the whole smtp.nim local file with the code linked above.