~/bend-docscommunity

smtp.bend relies on unsafe/foreign

raw source on the hub · import 0x650cd729029c76d9f6151ee977dc0ae8/smtp.bend as Smtp

SMTP client (RFC 5321), with STARTTLS (RFC 3207), implicit TLS (RFC 8314), SIZE (RFC 1870), SMTPUTF8 (RFC 6531) and AUTH PLAIN, LOGIN, CRAM-MD5, XOAUTH2 and OAUTHBEARER (RFC 4954, 4616, 2195, 7628). One connection, then one transaction per message:

S: 220 greeting (implicit TLS: after the handshake) C: EHLO name S: 250 (a 5xx: HELO name, no extensions) C: STARTTLS S: 220 (STARTTLS mode; then the handshake and EHLO again, RFC 3207 4.2) C: AUTH ... S: 235 (with a user; only under TLS) C: MAIL FROM:<from> S: 250 C: RCPT TO:<to> S: 250 (once per To, Cc and Bcc address; a refused one is reported, not fatal) C: DATA S: 354 C: the message, then "." S: 250 (MAIL again for the next message; RSET first if one stopped midway) C: QUIT S: 221

A refusal before the first MAIL ends the talk with QUIT; one inside a message ends that message only. This file is the IO: it threads the socket and the read buffer through continuations. What to say, and what a reply means, is in core.bend (the message, the options, the commands, the plan), which is pure and is what the laws are about.

import ./core.bend as C import ./smtp.bend as S S.Smtp.send_mail(C.Opts.login(C.Opts.new("smtp.example.com"), user, pass), C.Mail.new("Me <me@example.com>", "Ana <ana@x.com>", "Hi", "text"))

10 imports
import Base
import ./text.bend as T
import ./reply.bend as R
import ./mime.bend as M
import ./net.bend as N
import ./idna.bend as I
import ./addr.bend as A
import ./md5.bend as D
import ./dkim.bend as Q
import ./core.bend as C

Definitions

def Smtp.drop source · line 41 · raw

@s:Socket -> @code:U32 -> @msg:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The connection is broken: close it and report.

def Smtp.bye.got source · line 46 · raw

@m:Pair(Socket, Result<&1, &1, Pair(U32, String), Maybe<&1, String>>) -> @r:0x650cd729029c76d9f6151ee977dc0ae8/core.Out -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.bye.sent source · line 53 · raw

@m:Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>) -> @r:0x650cd729029c76d9f6151ee977dc0ae8/core.Out -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.quit source · line 63 · raw

@s:Socket -> @r:0x650cd729029c76d9f6151ee977dc0ae8/core.Out -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

QUIT, a wait for the server's answer (up to 10 s), the close; then r, whatever the server said.

def Smtp.got source · line 69 · raw

@m:Pair(Socket, Result<&1, &1, Pair(U32, String), Maybe<&1, String>>) -> @buf:String -> @more:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

A poll's answer: more bytes on the buffer, or the end of the talk.

def Smtp.read source · line 87 · raw

@fuel:Nat -> @sp:0x650cd729029c76d9f6151ee977dc0ae8/reply.Split -> @s:Socket -> @+ms:U32 -> @k:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The next reply: from the buffer if it holds one, else after a poll of up to ms.

def Smtp.check source · line 102 · raw

@ok:Bool -> @r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.expect source · line 112 · raw

@+r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @+want:U32 -> @s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

Go on if the reply's class is the one wanted, else QUIT with it.

def Smtp.sent source · line 116 · raw

@m:Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>) -> @k:(@_:Socket -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.send source · line 126 · raw

@s:Socket -> @line:String -> @k:(@_:Socket -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.talk source · line 132 · raw

@s:Socket -> @buf:String -> @line:String -> @want:U32 -> @ms:U32 -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

Send a line, read the reply (up to ms), go on if its class is want.

def Smtp.helo.go source · line 140 · raw

@name:String -> @s:Socket -> @buf:String -> @next:(@_:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.helo.named source · line 147 · raw

@literal:Bool -> @name:String -> @s:Socket -> @buf:String -> @next:(@_:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

HELO takes a domain, never an address literal (RFC 5321 4.1.1.1): a literal EHLO name gives way to the host's own name.

def Smtp.hello.got source · line 157 · raw

@ok:Bool -> @old:Bool -> @r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @s:Socket -> @buf:String -> @+name:String -> @next:(@_:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.hello.named source · line 169 · raw

@m:Pair(Socket, String) -> @lmtp:Bool -> @buf:String -> @next:(@_:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.hello.name source · line 177 · raw

@ask:Bool -> @name:String -> @s:Socket -> IO(Pair(Socket, String))

def Smtp.hello source · line 186 · raw

@+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @s:Socket -> @buf:String -> @next:(@_:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

EHLO, or HELO when the server refuses EHLO with a 5xx (RFC 5321 3.2): then there are no extensions. next gets the capabilities.

def Smtp.ask source · line 197 · raw

@s:Socket -> @buf:String -> @line:String -> @ms:U32 -> @k:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

Send a line and read its reply (up to ms), whatever it says.

def Smtp.ask2 source · line 202 · raw

@piped:Bool -> @s:Socket -> @buf:String -> @line:String -> @ms:U32 -> @k:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

A reply to a command: sent now, or already sent with the others.

def Smtp.rcpt.got source · line 212 · raw

@ok:Bool -> @closing:Bool -> @+r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @+a:String -> @s:Socket -> @buf:String -> @good:List<&2, String> -> @bad:List<&2, String> -> @k:(@_:Socket -> @_:String -> @_:List<&2, String> -> @_:List<&2, String> -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

A RCPT's reply: the address kept when taken; noted when refused, and the talk goes on; a 421 (the server is closing) ends it.

def Smtp.rcpts source · line 225 · raw

@to:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Rc> -> @+piped:Bool -> @s:Socket -> @buf:String -> @good:List<&2, String> -> @bad:List<&2, String> -> @k:(@_:Socket -> @_:String -> @_:List<&2, String> -> @_:List<&2, String> -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.reset source · line 241 · raw

@s:Socket -> @buf:String -> @x:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

A message stopped inside its transaction: RSET clears it (RFC 5321 4.1.1.5), so the next message starts clean; then on with x.

def Smtp.end.got source · line 246 · raw

@ok:Bool -> @closing:Bool -> @r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @bad:List<&2, String> -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The reply to the message itself: taken, or refused whole.

def Smtp.final.got source · line 259 · raw

@ok:Bool -> @+r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @a:String -> @s:Socket -> @buf:String -> @+n:U32 -> @bad:List<&2, String> -> @k:(@_:Socket -> @_:String -> @_:U32 -> @_:List<&2, String> -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.finals source · line 270 · raw

@good:List<&2, String> -> @s:Socket -> @buf:String -> @+n:U32 -> @bad:List<&2, String> -> @k:(@_:Socket -> @_:String -> @_:U32 -> @_:List<&2, String> -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

LMTP answers the message once per recipient taken, in RCPT's order (RFC 2033 4.2); n counts the ones that got it.

def Smtp.lmtp.end source · line 281 · raw

@none:Bool -> @bad:List<&2, String> -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.outcome source · line 291 · raw

@lmtp:Bool -> @good:List<&2, String> -> @bad:List<&2, String> -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

How the message went, once its bytes are out: one reply (SMTP), or one per recipient (LMTP).

def Smtp.put source · line 304 · raw

@text:String -> @lmtp:Bool -> @good:List<&2, String> -> @bad:List<&2, String> -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The message's bytes, then its outcome.

def Smtp.data.got source · line 309 · raw

@ok:Bool -> @closing:Bool -> @r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @+p:0x650cd729029c76d9f6151ee977dc0ae8/core.Plan -> @good:List<&2, String> -> @bad:List<&2, String> -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.undata.got source · line 322 · raw

@go:Bool -> @s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.undata source · line 332 · raw

@s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

A piped DATA still to be answered, for a message that will not go: if the server says 354 anyway, an empty message ends it (RFC 2920 3.1).

def Smtp.kept source · line 339 · raw

@x:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> @io:IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The outcome of io with x kept: if the clean-up after a refused message loses the connection, the refusal is still the message's outcome.

def Smtp.empty source · line 343 · raw

@pending:Bool -> @+x:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The message will not go: clear what is pending, then on with x.

def Smtp.body source · line 353 · raw

@none:Bool -> @bdat:Bool -> @+p:0x650cd729029c76d9f6151ee977dc0ae8/core.Plan -> @good:List<&2, String> -> @bad:List<&2, String> -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

With no recipient taken there is nothing to send; else the message, after DATA's 354 or as a BDAT chunk.

def Smtp.skip source · line 368 · raw

@n:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Rc> -> @s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

Replies to skip: the ones to commands piped after one that failed.

def Smtp.refused source · line 379 · raw

@piped:Bool -> @+p:0x650cd729029c76d9f6151ee977dc0ae8/core.Plan -> @+x:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

MAIL was refused. Alone, that is all; piped, the RCPTs' and DATA's replies are still to come.

def Smtp.mail.got source · line 389 · raw

@ok:Bool -> @closing:Bool -> @r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @+p:0x650cd729029c76d9f6151ee977dc0ae8/core.Plan -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

MAIL's reply: on to the recipients, or the message is refused here.

def Smtp.deliver source · line 403 · raw

@piped:Bool -> @+p:0x650cd729029c76d9f6151ee977dc0ae8/core.Plan -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.sized source · line 416 · raw

@big:Bool -> @+p:0x650cd729029c76d9f6151ee977dc0ae8/core.Plan -> @+size:U32 -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.utf8.ok source · line 426 · raw

@lacking:Bool -> @+p:0x650cd729029c76d9f6151ee977dc0ae8/core.Plan -> @+size:U32 -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.one source · line 439 · raw

@+j:0x650cd729029c76d9f6151ee977dc0ae8/core.Job -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+c:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @s:Socket -> @buf:String -> @next:(@_:Socket -> @_:String -> @_:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

One message: MAIL (not even tried when SIZE says it is too big, or when it needs SMTPUTF8 and the server lacks it), RCPT, DATA, the message. next gets how it went; only a broken or closing connection ends here.

def Smtp.more source · line 445 · raw

@x:0x650cd729029c76d9f6151ee977dc0ae8/core.Sent -> @rest:IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.batch source · line 451 · raw

@js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+c:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @s:Socket -> @buf:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The messages, one transaction each on the same connection, then QUIT. Each one's outcome is put in front of what the rest returns, so an error later still reports the messages already sent.

def Smtp.sasl.end source · line 460 · raw

@m:Pair(Socket, Result<&1, &1, Pair(U32, String), Maybe<&1, String>>) -> @+why:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.sasl.got source · line 470 · raw

@ok:Bool -> @more:Bool -> @+r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @+mech:String -> @s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The server's answer to the last response: 2xx goes on; a 334 is an error in base64 (OAuth's JSON), answered with the cancel line, then the 535; anything else is the failure itself.

def Smtp.sasl.last source · line 486 · raw

@s:Socket -> @buf:String -> @line:String -> @+mech:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

Send the last response and read how it went.

def Smtp.sasl.at source · line 492 · raw

@fits:Bool -> @line:String -> @+mech:String -> @resp:String -> @s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.sasl source · line 503 · raw

@+mech:String -> @+resp:String -> @s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

AUTH with an initial response; past 512 octets the response follows the server's 334 instead (RFC 4954 4, RFC 5321 4.5.3.1.4).

def Smtp.cram source · line 509 · raw

@ok:Bool -> @r:0x650cd729029c76d9f6151ee977dc0ae8/reply.Reply -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @s:Socket -> @buf:String -> @k:(@_:Socket -> @_:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

CRAM-MD5's challenge, answered (RFC 2195 2).

def Smtp.auth.with source · line 519 · raw

@mech:0x650cd729029c76d9f6151ee977dc0ae8/core.Mech -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @+c:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @s:Socket -> @buf:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.auth source · line 544 · raw

@none:Bool -> @secure:Bool -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @+c:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @s:Socket -> @buf:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.login source · line 557 · raw

@+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @secure:Bool -> @+c:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @s:Socket -> @buf:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

AUTH when there is a user, and only over TLS (RFC 4954 4).

def Smtp.tls.done source · line 564 · raw

@m:Pair(Socket, Result<&1, &1, Pair(U32, String), Unit>) -> @k:(@_:Socket -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.tls source · line 575 · raw

@+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @s:Socket -> @k:(@_:Socket -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)) -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

The TLS handshake for the host, then k.

def Smtp.starttls source · line 585 · raw

@clean:Bool -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @s:Socket -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

After STARTTLS's 220, nothing may wait in the buffer: bytes sent before the handshake would be taken as sent under it (RFC 3207 4.2, the STARTTLS injection attack).

def Smtp.upgrade source · line 594 · raw

@offered:Bool -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @s:Socket -> @buf:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.secure source · line 605 · raw

@mode:0x650cd729029c76d9f6151ee977dc0ae8/core.Mode -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @+c:0x650cd729029c76d9f6151ee977dc0ae8/core.Caps -> @s:Socket -> @buf:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

After the first EHLO: STARTTLS and EHLO again in StartTls mode (with no fallback to plain text), then AUTH and the envelope.

def Smtp.greet source · line 619 · raw

@+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @s:Socket -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.opened source · line 624 · raw

@mode:0x650cd729029c76d9f6151ee977dc0ae8/core.Mode -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> @s:Socket -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.connected source · line 632 · raw

@r:Result<&1, &1, Pair(U32, String), Socket> -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+js:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job> -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.dial.at source · line 640 · raw

@direct:Bool -> @kind:U32 -> @ph:String -> @pp:U32 -> @user:String -> @pass:String -> @host:String -> @port:U32 -> IO(Result<&1, &1, Pair(U32, String), Socket>)

def Smtp.dial source · line 650 · raw

@p:0x650cd729029c76d9f6151ee977dc0ae8/core.Proxy -> @host:String -> @port:U32 -> IO(Result<&1, &1, Pair(U32, String), Socket>)

The connection: direct, or through the proxy.

def Smtp.must source · line 656 · raw

@r:Result<&1, &1, Pair(U32, String), String> -> IO(String)

A signing step's result, or the end of the program (exit 1) with why.

def Smtp.sign.end source · line 664 · raw

@+tags:String -> @+hr:Bool -> @+fs:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/dkim.Field> -> @key:String -> @m:0x650cd729029c76d9f6151ee977dc0ae8/core.Mail -> @msg:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Job)

def Smtp.sign.cut source · line 672 · raw

@c:0x650cd729029c76d9f6151ee977dc0ae8/dkim.Cut -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @t:U32 -> @m:0x650cd729029c76d9f6151ee977dc0ae8/core.Mail -> @msg:String -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Job)

def Smtp.sign source · line 690 · raw

@dkim:Bool -> @j:0x650cd729029c76d9f6151ee977dc0ae8/core.Job -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @t:U32 -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Job)

The message with its DKIM-Signature in front, when a key was given. A key that cannot be read or cannot sign ends the program: nothing goes out unsigned by accident.

def Smtp.job.io source · line 698 · raw

@+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @m:0x650cd729029c76d9f6151ee977dc0ae8/core.Mail -> @+t:U32 -> @a:U32 -> @b:U32 -> @c:U32 -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Job)

def Smtp.jobs source · line 703 · raw

@ms:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Mail> -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> IO(List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Job>)

Each message's text, with its own Date, Message-ID and boundary, and signed if asked.

def Smtp.go source · line 717 · raw

@+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @ms:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Mail> -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.checked source · line 725 · raw

@problem:String -> @+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @ms:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Mail> -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

def Smtp.send_many source · line 734 · raw

@+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @+ms:List<&2, 0x650cd729029c76d9f6151ee977dc0ae8/core.Mail> -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

Send the messages over one connection, as o says: one transaction each, in order. Nothing is sent if any of them is malformed.

def Smtp.send_mail source · line 741 · raw

@+o:0x650cd729029c76d9f6151ee977dc0ae8/core.Opts -> @m:0x650cd729029c76d9f6151ee977dc0ae8/core.Mail -> IO(0x650cd729029c76d9f6151ee977dc0ae8/core.Out)

Send one message as o says.