1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
|
#
#
# Nimrod's Runtime Library
# (c) Copyright 2012 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements the SMTP client protocol as specified by RFC 5321,
## this can be used to send mail to any SMTP Server.
##
## This module also implements the protocol used to format messages,
## as specified by RFC 2822.
##
## Example gmail use:
##
##
## .. code-block:: Nimrod
## var msg = createMessage("Hello from Nimrod's SMTP",
## "Hello!.\n Is this awesome or what?",
## @["foo@gmail.com"])
## var smtp = connect("smtp.gmail.com", 465, True, True)
## smtp.auth("username", "password")
## smtp.sendmail("username@gmail.com", @["foo@gmail.com"], $msg)
##
##
## For SSL support this module relies on OpenSSL. If you want to
## enable SSL, compile with ``-d:ssl``.
when not defined(ssl):
{.error: "The SMTP module should be compiled with SSL support. Compile with -d:ssl."}
import sockets, strutils, strtabs, base64, os
type
TSMTP* {.final.} = object
sock: TSocket
debug: Bool
TMessage* {.final.} = object
msgTo: seq[string]
msgCc: seq[string]
msgSubject: string
msgOtherHeaders: PStringTable
msgBody: string
EInvalidReply* = object of EIO
proc debugSend(smtp: TSMTP, cmd: string) =
if smtp.debug:
echo("C:" & cmd)
smtp.sock.send(cmd)
proc debugRecv(smtp: var TSMTP): TaintedString =
var line = TaintedString""
var ret = False
ret = smtp.sock.recvLine(line)
if ret:
if smtp.debug:
echo("S:" & line.string)
return line
else:
OSError()
return TaintedString""
proc quitExcpt(smtp: TSMTP, msg: string) =
smtp.debugSend("QUIT")
raise newException(EInvalidReply, msg)
proc checkReply(smtp: var TSMTP, reply: string) =
var line = smtp.debugRecv()
if not line.string.startswith(reply):
quitExcpt(smtp, "Expected " & reply & " reply, got: " & line.string)
proc connect*(address: string, port = 25,
ssl = false, debug = false): TSMTP =
## Establishes a connection with a SMTP server.
## May fail with EInvalidReply or with a socket error.
result.sock = socket()
if ssl:
when defined(ssl):
let ctx = newContext(verifyMode = CVerifyNone)
ctx.wrapSocket(result.sock)
else:
raise newException(ESystem,
"SMTP module compiled without SSL support")
result.sock.connect(address, TPort(port))
result.debug = debug
result.checkReply("220")
result.debugSend("HELO " & address & "\c\L")
result.checkReply("250")
proc auth*(smtp: var TSMTP, username, password: string) =
## Sends an AUTH command to the server to login as the `username`
## using `password`.
## May fail with EInvalidReply.
smtp.debugSend("AUTH LOGIN\c\L")
smtp.checkReply("334") # TODO: Check whether it's asking for the "Username:"
# i.e "334 VXNlcm5hbWU6"
smtp.debugSend(encode(username) & "\c\L")
smtp.checkReply("334") # TODO: Same as above, only "Password:" (I think?)
smtp.debugSend(encode(password) & "\c\L")
smtp.checkReply("235") # Check whether the authentification was successful.
proc sendmail*(smtp: var TSMTP, fromaddr: string,
toaddrs: seq[string], msg: string) =
## Sends `msg` from `fromaddr` to `toaddr`.
## Messages may be formed using ``createMessage`` by converting the
## TMessage into a string.
smtp.debugSend("MAIL FROM:<" & fromaddr & ">\c\L")
smtp.checkReply("250")
for address in items(toaddrs):
smtp.debugSend("RCPT TO:<" & address & ">\c\L")
smtp.checkReply("250")
# Send the message
smtp.debugSend("DATA " & "\c\L")
smtp.checkReply("354")
smtp.debugSend(msg & "\c\L")
smtp.debugSend(".\c\L")
smtp.checkReply("250")
proc close*(smtp: TSMTP) =
## Disconnects from the SMTP server and closes the socket.
smtp.debugSend("QUIT\c\L")
smtp.sock.close()
proc createMessage*(mSubject, mBody: string, mTo, mCc: seq[string],
otherHeaders: openarray[tuple[name, value: string]]): TMessage =
## Creates a new MIME compliant message.
result.msgTo = mTo
result.msgCc = mCc
result.msgSubject = mSubject
result.msgBody = mBody
result.msgOtherHeaders = newStringTable()
for n, v in items(otherHeaders):
result.msgOtherHeaders[n] = v
proc createMessage*(mSubject, mBody: string, mTo,
mCc: seq[string] = @[]): TMessage =
## Alternate version of the above.
result.msgTo = mTo
result.msgCc = mCc
result.msgSubject = mSubject
result.msgBody = mBody
result.msgOtherHeaders = newStringTable()
proc `$`*(msg: TMessage): string =
## stringify for ``TMessage``.
result = ""
if msg.msgTo.len() > 0:
result = "TO: " & msg.msgTo.join(", ") & "\c\L"
if msg.msgCc.len() > 0:
result.add("CC: " & msg.msgCc.join(", ") & "\c\L")
# TODO: Folding? i.e when a line is too long, shorten it...
result.add("Subject: " & msg.msgSubject & "\c\L")
for key, value in pairs(msg.msgOtherHeaders):
result.add(key & ": " & value & "\c\L")
result.add("\c\L")
result.add(msg.msgBody)
when isMainModule:
#var msg = createMessage("Test subject!",
# "Hello, my name is dom96.\n What\'s yours?", @["dominik@localhost"])
#echo(msg)
#var smtp = connect("localhost", 25, False, True)
#smtp.sendmail("root@localhost", @["dominik@localhost"], $msg)
#echo(decode("a17sm3701420wbe.12"))
var msg = createMessage("Hello from Nimrod'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()
|