PageRenderTime 49ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/nselib/smtp.lua

https://gitlab.com/g10h4ck/nmap-gsoc2015
Lua | 652 lines | 428 code | 64 blank | 160 comment | 60 complexity | 58b4532df83eab00ea7ac88d91742a20 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, Apache-2.0, LGPL-2.0, LGPL-2.1, MIT
  1. ---
  2. -- Simple Mail Transfer Protocol (SMTP) operations.
  3. --
  4. -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
  5. local base64 = require "base64"
  6. local comm = require "comm"
  7. local sasl = require "sasl"
  8. local stdnse = require "stdnse"
  9. local string = require "string"
  10. local table = require "table"
  11. _ENV = stdnse.module("smtp", stdnse.seeall)
  12. local ERROR_MESSAGES = {
  13. ["EOF"] = "connection closed",
  14. ["TIMEOUT"] = "connection timeout",
  15. ["ERROR"] = "failed to receive data"
  16. }
  17. local SMTP_CMD = {
  18. ["EHLO"] = {
  19. cmd = "EHLO",
  20. success = {
  21. [250] = "Requested mail action okay, completed",
  22. },
  23. errors = {
  24. [421] = "<domain> Service not available, closing transmission channel",
  25. [500] = "Syntax error, command unrecognised",
  26. [501] = "Syntax error in parameters or arguments",
  27. [504] = "Command parameter not implemented",
  28. [550] = "Not implemented",
  29. },
  30. },
  31. ["HELP"] = {
  32. cmd = "HELP",
  33. success = {
  34. [211] = "System status, or system help reply",
  35. [214] = "Help message",
  36. },
  37. errors = {
  38. [500] = "Syntax error, command unrecognised",
  39. [501] = "Syntax error in parameters or arguments",
  40. [502] = "Command not implemented",
  41. [504] = "Command parameter not implemented",
  42. [421] = "<domain> Service not available, closing transmission channel",
  43. },
  44. },
  45. ["AUTH"] = {
  46. cmd = "AUTH",
  47. success = {[334] = ""},
  48. errors = {
  49. [501] = "Authentication aborted",
  50. },
  51. },
  52. ["MAIL"] = {
  53. cmd = "MAIL",
  54. success = {
  55. [250] = "Requested mail action okay, completed",
  56. },
  57. errors = {
  58. [451] = "Requested action aborted: local error in processing",
  59. [452] = "Requested action not taken: insufficient system storage",
  60. [500] = "Syntax error, command unrecognised",
  61. [501] = "Syntax error in parameters or arguments",
  62. [421] = "<domain> Service not available, closing transmission channel",
  63. [552] = "Requested mail action aborted: exceeded storage allocation",
  64. },
  65. },
  66. ["RCPT"] = {
  67. cmd = "RCPT",
  68. success = {
  69. [250] = "Requested mail action okay, completed",
  70. [251] = "User not local; will forward to <forward-path>",
  71. },
  72. errors = {
  73. [450] = "Requested mail action not taken: mailbox unavailable",
  74. [451] = "Requested action aborted: local error in processing",
  75. [452] = "Requested action not taken: insufficient system storage",
  76. [500] = "Syntax error, command unrecognised",
  77. [501] = "Syntax error in parameters or arguments",
  78. [503] = "Bad sequence of commands",
  79. [521] = "<domain> does not accept mail [rfc1846]",
  80. [421] = "<domain> Service not available, closing transmission channel",
  81. },
  82. },
  83. ["DATA"] = {
  84. cmd = "DATA",
  85. success = {
  86. [250] = "Requested mail action okay, completed",
  87. [354] = "Start mail input; end with <CRLF>.<CRLF>",
  88. },
  89. errors = {
  90. [451] = "Requested action aborted: local error in processing",
  91. [554] = "Transaction failed",
  92. [500] = "Syntax error, command unrecognised",
  93. [501] = "Syntax error in parameters or arguments",
  94. [503] = "Bad sequence of commands",
  95. [421] = "<domain> Service not available, closing transmission channel",
  96. [552] = "Requested mail action aborted: exceeded storage allocation",
  97. [554] = "Transaction failed",
  98. [451] = "Requested action aborted: local error in processing",
  99. [452] = "Requested action not taken: insufficient system storage",
  100. },
  101. },
  102. ["STARTTLS"] = {
  103. cmd = "STARTTLS",
  104. success = {
  105. [220] = "Ready to start TLS"
  106. },
  107. errors = {
  108. [501] = "Syntax error (no parameters allowed)",
  109. [454] = "TLS not available due to temporary reason",
  110. },
  111. },
  112. ["RSET"] = {
  113. cmd = "RSET",
  114. success = {
  115. [200] = "nonstandard success response, see rfc876)",
  116. [250] = "Requested mail action okay, completed",
  117. },
  118. errors = {
  119. [500] = "Syntax error, command unrecognised",
  120. [501] = "Syntax error in parameters or arguments",
  121. [504] = "Command parameter not implemented",
  122. [421] = "<domain> Service not available, closing transmission channel",
  123. },
  124. },
  125. ["VRFY"] = {
  126. cmd = "VRFY",
  127. success = {
  128. [250] = "Requested mail action okay, completed",
  129. [251] = "User not local; will forward to <forward-path>",
  130. },
  131. errors = {
  132. [500] = "Syntax error, command unrecognised",
  133. [501] = "Syntax error in parameters or arguments",
  134. [502] = "Command not implemented",
  135. [504] = "Command parameter not implemented",
  136. [550] = "Requested action not taken: mailbox unavailable",
  137. [551] = "User not local; please try <forward-path>",
  138. [553] = "Requested action not taken: mailbox name not allowed",
  139. [421] = "<domain> Service not available, closing transmission channel",
  140. },
  141. },
  142. ["EXPN"] = {
  143. cmd = "EXPN",
  144. success = {
  145. [250] = "Requested mail action okay, completed",
  146. },
  147. errors = {
  148. [550] = "Requested action not taken: mailbox unavailable",
  149. [500] = "Syntax error, command unrecognised",
  150. [501] = "Syntax error in parameters or arguments",
  151. [502] = "Command not implemented",
  152. [504] = "Command parameter not implemented",
  153. [421] = "<domain> Service not available, closing transmission channel",
  154. },
  155. },
  156. }
  157. ---
  158. -- Returns a domain to be used in the SMTP commands that need it.
  159. --
  160. -- If the user specified one through the script argument
  161. -- <code>smtp.domain</code> this function will return it. Otherwise it will try
  162. -- to find the domain from the typed hostname and from the rDNS name. If it
  163. -- still can't find one it will return the nmap.scanme.org by default.
  164. --
  165. -- @param host The host table
  166. -- @return The hostname to be used by the different SMTP commands.
  167. get_domain = function(host)
  168. local nmap_domain = "nmap.scanme.org"
  169. -- Use the user provided options.
  170. local result = stdnse.get_script_args("smtp.domain")
  171. if not result then
  172. if type(host) == "table" then
  173. if host.targetname then
  174. result = host.targetname
  175. elseif (host.name and #host.name ~= 0) then
  176. result = host.name
  177. end
  178. end
  179. end
  180. return result or nmap_domain
  181. end
  182. --- Gets the authentication mechanisms that are listed in the response
  183. -- of the client's EHLO command.
  184. --
  185. -- @param response The response of the client's EHLO command.
  186. -- @return An array of authentication mechanisms on success, or nil
  187. -- when it can't find authentication.
  188. get_auth_mech = function(response)
  189. local list = {}
  190. for _, line in pairs(stdnse.strsplit("\r?\n", response)) do
  191. local authstr = line:match("%d+%-AUTH%s(.*)$")
  192. if authstr then
  193. for mech in authstr:gmatch("[^%s]+") do
  194. table.insert(list, mech)
  195. end
  196. return list
  197. end
  198. end
  199. return nil
  200. end
  201. --- Checks the SMTP server reply to see if it supports the previously
  202. -- sent SMTP command.
  203. --
  204. -- @param cmd The SMTP command that was sent to the server
  205. -- @param reply The SMTP server reply
  206. -- @return true if the reply indicates that the SMTP command was
  207. -- processed by the server correctly, or false on failures.
  208. -- @return message The reply returned by the server on success, or an
  209. -- error message on failures.
  210. check_reply = function(cmd, reply)
  211. local code, msg = string.match(reply, "^([0-9]+)%s*")
  212. if code then
  213. cmd = cmd:upper()
  214. code = tonumber(code)
  215. if SMTP_CMD[cmd] then
  216. if SMTP_CMD[cmd].success[code] then
  217. return true, reply
  218. end
  219. else
  220. stdnse.debug3(
  221. "SMTP: check_smtp_reply failed: %s not supported", cmd)
  222. return false, string.format("SMTP: %s %s", cmd, reply)
  223. end
  224. end
  225. stdnse.debug3(
  226. "SMTP: check_smtp_reply failed: %s %s", cmd, reply)
  227. return false, string.format("SMTP: %s %s", cmd, reply)
  228. end
  229. --- Queries the SMTP server for a specific service.
  230. --
  231. -- This is a low level function that can be used to have more control
  232. -- over the data exchanged. On network errors the socket will be closed.
  233. -- This function automatically adds <code>CRLF<code> at the end.
  234. --
  235. -- @param socket connected to the server
  236. -- @param cmd The SMTP cmd to send to the server
  237. -- @param data The data to send to the server
  238. -- @param lines The minimum number of lines to receive, default value: 1.
  239. -- @return true on success, or nil on failures.
  240. -- @return response The returned response from the server on success, or
  241. -- an error message on failures.
  242. query = function(socket, cmd, data, lines)
  243. if data then
  244. cmd = cmd.." "..data
  245. end
  246. local st, ret = socket:send(string.format("%s\r\n", cmd))
  247. if not st then
  248. socket:close()
  249. stdnse.debug3("SMTP: failed to send %s request.", cmd)
  250. return st, string.format("SMTP failed to send %s request.", cmd)
  251. end
  252. st, ret = socket:receive_lines(lines or 1)
  253. if not st then
  254. socket:close()
  255. stdnse.debug3("SMTP %s: failed to receive data: %s.",
  256. cmd, (ERROR_MESSAGES[ret] or 'unspecified error'))
  257. return st, string.format("SMTP %s: failed to receive data: %s",
  258. cmd, (ERROR_MESSAGES[ret] or 'unspecified error'))
  259. end
  260. return st, ret
  261. end
  262. --- Connects to the SMTP server based on the provided options.
  263. --
  264. -- @param host The host table
  265. -- @param port The port table
  266. -- @param opts The connection option table, possible options:
  267. -- ssl: try to connect using TLS
  268. -- timeout: generic timeout value
  269. -- recv_before: receive data before returning
  270. -- lines: a minimum number of lines to receive
  271. -- @return socket The socket descriptor, or nil on errors
  272. -- @return response The response received on success and when
  273. -- the recv_before is set, or the error message on failures.
  274. connect = function(host, port, opts)
  275. local socket, _, ret
  276. if opts.ssl then
  277. socket, _, _, ret = comm.tryssl(host, port, '', opts)
  278. else
  279. socket, _, ret = comm.opencon(host, port, nil, opts)
  280. end
  281. if not socket then
  282. return socket, (ERROR_MESSAGES[ret] or 'unspecified error')
  283. end
  284. return socket, ret
  285. end
  286. --- Switches the plain text connection to be protected by the TLS protocol
  287. -- by using the SMTP STARTTLS command.
  288. --
  289. -- The socket will be reconnected by using SSL. On network errors or if the
  290. -- SMTP command fails, the connection will be closed and the socket cleared.
  291. --
  292. -- @param socket connected to server.
  293. -- @return true on success, or nil on failures.
  294. -- @return message On success this will contain the SMTP server response
  295. -- to the client's STARTTLS command, or an error message on failures.
  296. starttls = function(socket)
  297. local st, reply, ret
  298. st, reply = query(socket, "STARTTLS")
  299. if not st then
  300. return st, reply
  301. end
  302. st, ret = check_reply('STARTTLS', reply)
  303. if not st then
  304. quit(socket)
  305. return st, ret
  306. end
  307. st, ret = socket:reconnect_ssl()
  308. if not st then
  309. socket:close()
  310. return st, ret
  311. end
  312. return true, reply
  313. end
  314. --- Sends the EHLO command to the SMTP server.
  315. --
  316. -- On network errors or if the SMTP command fails, the connection
  317. -- will be closed and the socket cleared.
  318. --
  319. -- @param socket connected to server
  320. -- @param domain to use in the EHLO command.
  321. -- @return true on success, or false on failures.
  322. -- @return response returned by the SMTP server on success, or an
  323. -- error message on failures.
  324. ehlo = function(socket, domain)
  325. local st, ret, response
  326. st, response = query(socket, "EHLO", domain)
  327. if not st then
  328. return st, response
  329. end
  330. st, ret = check_reply("EHLO", response)
  331. if not st then
  332. quit(socket)
  333. return st, ret
  334. end
  335. return st, response
  336. end
  337. --- Sends the HELP command to the SMTP server.
  338. --
  339. -- On network errors or if the SMTP command fails, the connection
  340. -- will be closed and the socket cleared.
  341. --
  342. -- @param socket connected to server
  343. -- @return true on success, or false on failures.
  344. -- @return response returned by the SMTP server on success, or an
  345. -- error message on failures.
  346. help = function(socket)
  347. local st, ret, response
  348. st, response = query(socket, "HELP")
  349. if not st then
  350. return st, response
  351. end
  352. st, ret = check_reply("HELP", response)
  353. if not st then
  354. quit(socket)
  355. return st, ret
  356. end
  357. return st, response
  358. end
  359. --- Sends the MAIL command to the SMTP server.
  360. --
  361. -- On network errors or if the SMTP command fails, the connection
  362. -- will be closed and the socket cleared.
  363. --
  364. -- @param socket connected to server.
  365. -- @param address of the sender.
  366. -- @param esmtp_opts The additional ESMTP options table, possible values:
  367. -- size: a decimal value to represent the message size in octets.
  368. -- ret: include the message in the DSN, should be 'FULL' or 'HDRS'.
  369. -- envid: envelope identifier, printable characters that would be
  370. -- transmitted along with the message and included in the
  371. -- failed DSN.
  372. -- transid: a globally unique case-sensitive value that identifies
  373. -- this particular transaction.
  374. -- @return true on success, or false on failures.
  375. -- @return response returned by the SMTP server on success, or an
  376. -- error message on failures.
  377. mail = function(socket, address, esmtp_opts)
  378. local st, ret, response
  379. if esmtp_opts and next(esmtp_opts) then
  380. local data = ""
  381. -- we do not check for strange values, read the NSEDoc.
  382. for k,v in pairs(esmtp_opts) do
  383. k = k:upper()
  384. data = string.format("%s %s=%s", data, k, v)
  385. end
  386. st, response = query(socket, "MAIL",
  387. string.format("FROM:<%s>%s",
  388. address, data))
  389. else
  390. st, response = query(socket, "MAIL",
  391. string.format("FROM:<%s>", address))
  392. end
  393. if not st then
  394. return st, response
  395. end
  396. st, ret = check_reply("MAIL", response)
  397. if not st then
  398. quit(socket)
  399. return st, ret
  400. end
  401. return st, response
  402. end
  403. --- Sends the RCPT command to the SMTP server.
  404. --
  405. -- On network errors or if the SMTP command fails, the connection
  406. -- will be closed and the socket cleared.
  407. --
  408. -- @param socket connected to server.
  409. -- @param address of the recipient.
  410. -- @return true on success, or false on failures.
  411. -- @return response returned by the SMTP server on success, or an
  412. -- error message on failures.
  413. recipient = function(socket, address)
  414. local st, ret, response
  415. st, response = query(socket, "RCPT",
  416. string.format("TO:<%s>", address))
  417. if not st then
  418. return st, response
  419. end
  420. st, ret = check_reply("RCPT", response)
  421. if not st then
  422. quit(socket)
  423. return st, ret
  424. end
  425. return st, response
  426. end
  427. --- Sends data to the SMTP server.
  428. --
  429. -- This function will automatically adds <code><CRLF>.<CRLF></code> at the
  430. -- end. On network errors or if the SMTP command fails, the connection
  431. -- will be closed and the socket cleared.
  432. --
  433. -- @param socket connected to server.
  434. -- @param data to be sent.
  435. -- @return true on success, or false on failures.
  436. -- @return response returned by the SMTP server on success, or an
  437. -- error message on failures.
  438. datasend = function(socket, data)
  439. local st, ret, response
  440. st, response = query(socket, "DATA")
  441. if not st then
  442. return st, response
  443. end
  444. st, ret = check_reply("DATA", response)
  445. if not st then
  446. quit(socket)
  447. return st, ret
  448. end
  449. if data then
  450. st, response = query(socket, data.."\r\n.")
  451. if not st then
  452. return st, response
  453. end
  454. st, ret = check_reply("DATA", response)
  455. if not st then
  456. quit(socket)
  457. return st, ret
  458. end
  459. end
  460. return st, response
  461. end
  462. --- Sends the RSET command to the SMTP server.
  463. --
  464. -- On network errors or if the SMTP command fails, the connection
  465. -- will be closed and the socket cleared.
  466. --
  467. -- @param socket connected to server.
  468. -- @return true on success, or false on failures.
  469. -- @return response returned by the SMTP server on success, or an
  470. -- error message on failures.
  471. reset = function(socket)
  472. local st, ret, response
  473. st, response = query(socket, "RSET")
  474. if not st then
  475. return st, response
  476. end
  477. st, ret = check_reply("RSET", response)
  478. if not st then
  479. quit(socket)
  480. return st, ret
  481. end
  482. return st, response
  483. end
  484. --- Sends the VRFY command to verify the validity of a mailbox.
  485. --
  486. -- On network errors or if the SMTP command fails, the connection
  487. -- will be closed and the socket cleared.
  488. --
  489. -- @param socket connected to server.
  490. -- @param mailbox to verify.
  491. -- @return true on success, or false on failures.
  492. -- @return response returned by the SMTP server on success, or an
  493. -- error message on failures.
  494. verify = function(socket, mailbox)
  495. local st, ret, response
  496. st, response = query(socket, "VRFY", mailbox)
  497. st, ret = check_reply("VRFY", response)
  498. if not st then
  499. quit(socket)
  500. return st, ret
  501. end
  502. return st, response
  503. end
  504. --- Sends the QUIT command to the SMTP server, and closes the socket.
  505. --
  506. -- @param socket connected to server.
  507. quit = function(socket)
  508. stdnse.debug3("SMTP: sending 'QUIT'.")
  509. socket:send("QUIT\r\n")
  510. socket:close()
  511. end
  512. --- Attempts to authenticate with the SMTP server. The supported authentication
  513. -- mechanisms are: LOGIN, PLAIN, CRAM-MD5, DIGEST-MD5 and NTLM.
  514. --
  515. -- @param socket connected to server.
  516. -- @param username SMTP username.
  517. -- @param password SMTP password.
  518. -- @param mech Authentication mechanism.
  519. -- @return true on success, or false on failures.
  520. -- @return response returned by the SMTP server on success, or an
  521. -- error message on failures.
  522. login = function(socket, username, password, mech)
  523. assert(mech == "LOGIN" or mech == "PLAIN" or mech == "CRAM-MD5"
  524. or mech == "DIGEST-MD5" or mech == "NTLM",
  525. ("Unsupported authentication mechanism (%s)"):format(mech or "nil"))
  526. local status, response = query(socket, "AUTH", mech)
  527. if ( not(status) ) then
  528. return false, "ERROR: Failed to send AUTH to server"
  529. end
  530. if ( mech == "LOGIN" ) then
  531. local tmp = response:match("334 (.*)")
  532. if ( not(tmp) ) then
  533. return false, "ERROR: Failed to decode LOGIN response"
  534. end
  535. tmp = base64.dec(tmp):lower()
  536. if ( not(tmp:match("^username")) ) then
  537. return false, ("ERROR: Expected \"Username\", but received (%s)"):format(tmp)
  538. end
  539. status, response = query(socket, base64.enc(username))
  540. if ( not(status) ) then
  541. return false, "ERROR: Failed to read LOGIN response"
  542. end
  543. tmp = response:match("334 (.*)")
  544. if ( not(tmp) ) then
  545. return false, "ERROR: Failed to decode LOGIN response"
  546. end
  547. tmp = base64.dec(tmp):lower()
  548. if ( not(tmp:match("^password")) ) then
  549. return false, ("ERROR: Expected \"password\", but received (%s)"):format(tmp)
  550. end
  551. status, response = query(socket, base64.enc(password))
  552. if ( not(status) ) then
  553. return false, "ERROR: Failed to read LOGIN response"
  554. end
  555. if ( response:match("^235") ) then
  556. return true, "Login success"
  557. end
  558. return false, response
  559. end
  560. if ( mech == "NTLM" ) then
  561. -- sniffed of the wire, seems to always be the same
  562. -- decodes to some NTLMSSP blob greatness
  563. status, response = query(socket, "TlRMTVNTUAABAAAAB7IIogYABgA3AAAADwAPACgAAAAFASgKAAAAD0FCVVNFLUFJUi5MT0NBTERPTUFJTg==")
  564. if ( not(status) ) then return false, "ERROR: Failed to receive NTLM challenge" end
  565. end
  566. local chall = response:match("^334 (.*)")
  567. chall = (chall and base64.dec(chall))
  568. if (not(chall)) then return false, "ERROR: Failed to retrieve challenge" end
  569. -- All mechanisms expect username and pass
  570. -- add the otheronce for those who need them
  571. local mech_params = { username, password, chall, "smtp" }
  572. local auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params))
  573. auth_data = base64.enc(auth_data)
  574. status, response = query(socket, auth_data)
  575. if ( not(status) ) then
  576. return false, ("ERROR: Failed to authenticate using SASL %s"):format(mech)
  577. end
  578. if ( mech == "DIGEST-MD5" ) then
  579. local rspauth = response:match("^334 (.*)")
  580. if ( rspauth ) then
  581. rspauth = base64.dec(rspauth)
  582. status, response = query(socket,"")
  583. end
  584. end
  585. if ( response:match("^235") ) then return true, "Login success" end
  586. return false, response
  587. end
  588. return _ENV;