PageRenderTime 42ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/nselib/smtp.lua

https://github.com/prakashgamit/nmap
Lua | 675 lines | 448 code | 68 blank | 159 comment | 64 complexity | 624f8f41bf500d0a6a438c434b2dc320 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-2.0, LGPL-2.1
  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 nmap = require "nmap"
  8. local sasl = require "sasl"
  9. local stdnse = require "stdnse"
  10. local string = require "string"
  11. local table = require "table"
  12. _ENV = stdnse.module("smtp", stdnse.seeall)
  13. local ERROR_MESSAGES = {
  14. ["EOF"] = "connection closed",
  15. ["TIMEOUT"] = "connection timeout",
  16. ["ERROR"] = "failed to receive data"
  17. }
  18. local SMTP_CMD = {
  19. ["EHLO"] = {
  20. cmd = "EHLO",
  21. success = {
  22. [250] = "Requested mail action okay, completed",
  23. },
  24. errors = {
  25. [421] = "<domain> Service not available, closing transmission channel",
  26. [500] = "Syntax error, command unrecognised",
  27. [501] = "Syntax error in parameters or arguments",
  28. [504] = "Command parameter not implemented",
  29. [550] = "Not implemented",
  30. },
  31. },
  32. ["HELP"] = {
  33. cmd = "HELP",
  34. success = {
  35. [211] = "System status, or system help reply",
  36. [214] = "Help message",
  37. },
  38. errors = {
  39. [500] = "Syntax error, command unrecognised",
  40. [501] = "Syntax error in parameters or arguments",
  41. [502] = "Command not implemented",
  42. [504] = "Command parameter not implemented",
  43. [421] = "<domain> Service not available, closing transmission channel",
  44. },
  45. },
  46. ["AUTH"] = {
  47. cmd = "AUTH",
  48. success = {[334] = ""},
  49. errors = {
  50. [501] = "Authentication aborted",
  51. },
  52. },
  53. ["MAIL"] = {
  54. cmd = "MAIL",
  55. success = {
  56. [250] = "Requested mail action okay, completed",
  57. },
  58. errors = {
  59. [451] = "Requested action aborted: local error in processing",
  60. [452] = "Requested action not taken: insufficient system storage",
  61. [500] = "Syntax error, command unrecognised",
  62. [501] = "Syntax error in parameters or arguments",
  63. [421] = "<domain> Service not available, closing transmission channel",
  64. [552] = "Requested mail action aborted: exceeded storage allocation",
  65. },
  66. },
  67. ["RCPT"] = {
  68. cmd = "RCPT",
  69. success = {
  70. [250] = "Requested mail action okay, completed",
  71. [251] = "User not local; will forward to <forward-path>",
  72. },
  73. errors = {
  74. [450] = "Requested mail action not taken: mailbox unavailable",
  75. [451] = "Requested action aborted: local error in processing",
  76. [452] = "Requested action not taken: insufficient system storage",
  77. [500] = "Syntax error, command unrecognised",
  78. [501] = "Syntax error in parameters or arguments",
  79. [503] = "Bad sequence of commands",
  80. [521] = "<domain> does not accept mail [rfc1846]",
  81. [421] = "<domain> Service not available, closing transmission channel",
  82. },
  83. },
  84. ["DATA"] = {
  85. cmd = "DATA",
  86. success = {
  87. [250] = "Requested mail action okay, completed",
  88. [354] = "Start mail input; end with <CRLF>.<CRLF>",
  89. },
  90. errors = {
  91. [451] = "Requested action aborted: local error in processing",
  92. [554] = "Transaction failed",
  93. [500] = "Syntax error, command unrecognised",
  94. [501] = "Syntax error in parameters or arguments",
  95. [503] = "Bad sequence of commands",
  96. [421] = "<domain> Service not available, closing transmission channel",
  97. [552] = "Requested mail action aborted: exceeded storage allocation",
  98. [554] = "Transaction failed",
  99. [451] = "Requested action aborted: local error in processing",
  100. [452] = "Requested action not taken: insufficient system storage",
  101. },
  102. },
  103. ["STARTTLS"] = {
  104. cmd = "STARTTLS",
  105. success = {
  106. [220] = "Ready to start TLS"
  107. },
  108. errors = {
  109. [501] = "Syntax error (no parameters allowed)",
  110. [454] = "TLS not available due to temporary reason",
  111. },
  112. },
  113. ["RSET"] = {
  114. cmd = "RSET",
  115. success = {
  116. [200] = "nonstandard success response, see rfc876)",
  117. [250] = "Requested mail action okay, completed",
  118. },
  119. errors = {
  120. [500] = "Syntax error, command unrecognised",
  121. [501] = "Syntax error in parameters or arguments",
  122. [504] = "Command parameter not implemented",
  123. [421] = "<domain> Service not available, closing transmission channel",
  124. },
  125. },
  126. ["VRFY"] = {
  127. cmd = "VRFY",
  128. success = {
  129. [250] = "Requested mail action okay, completed",
  130. [251] = "User not local; will forward to <forward-path>",
  131. },
  132. errors = {
  133. [500] = "Syntax error, command unrecognised",
  134. [501] = "Syntax error in parameters or arguments",
  135. [502] = "Command not implemented",
  136. [504] = "Command parameter not implemented",
  137. [550] = "Requested action not taken: mailbox unavailable",
  138. [551] = "User not local; please try <forward-path>",
  139. [553] = "Requested action not taken: mailbox name not allowed",
  140. [421] = "<domain> Service not available, closing transmission channel",
  141. },
  142. },
  143. ["EXPN"] = {
  144. cmd = "EXPN",
  145. success = {
  146. [250] = "Requested mail action okay, completed",
  147. },
  148. errors = {
  149. [550] = "Requested action not taken: mailbox unavailable",
  150. [500] = "Syntax error, command unrecognised",
  151. [501] = "Syntax error in parameters or arguments",
  152. [502] = "Command not implemented",
  153. [504] = "Command parameter not implemented",
  154. [421] = "<domain> Service not available, closing transmission channel",
  155. },
  156. },
  157. }
  158. ---
  159. -- Returns a domain to be used in the SMTP commands that need it. If the
  160. -- user specified one through the script argument <code>smtp.domain</code>
  161. -- this function will return it. Otherwise it will try to find the domain
  162. -- from the typed hostname and from the rDNS name. If it still can't find
  163. -- 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.print_debug(3,
  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.print_debug(3,
  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.print_debug(3, "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.print_debug(3, "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. if opts.ssl then
  276. local socket, _, _, ret = comm.tryssl(host, port, '', opts)
  277. if not socket then
  278. return socket, (ERROR_MESSAGES[ret] or 'unspecified error')
  279. end
  280. return socket, ret
  281. else
  282. local timeout, recv, lines
  283. local socket = nmap.new_socket()
  284. if opts then
  285. recv = opts.recv_before
  286. timeout = opts.timeout
  287. lines = opts.lines
  288. end
  289. socket:set_timeout(timeout or 8000)
  290. local st, ret = socket:connect(host, port, port.protocol)
  291. if not st then
  292. socket:close()
  293. return st, (ERROR_MESSAGES[ret] or 'unspecified error')
  294. end
  295. if recv then
  296. st, ret = socket:receive_lines(lines or 1)
  297. if not st then
  298. socket:close()
  299. return st, (ERROR_MESSAGES[ret] or 'unspecified error')
  300. end
  301. end
  302. return socket, ret
  303. end
  304. end
  305. --- Switches the plain text connection to be protected by the TLS protocol
  306. -- by using the SMTP STARTTLS command.
  307. --
  308. -- The socket will be reconnected by using SSL. On network errors or if the
  309. -- SMTP command fails, the connection will be closed and the socket cleared.
  310. --
  311. -- @param socket connected to server.
  312. -- @return true on success, or nil on failures.
  313. -- @return message On success this will contain the SMTP server response
  314. -- to the client's STARTTLS command, or an error message on failures.
  315. starttls = function(socket)
  316. local st, reply, ret
  317. st, reply = query(socket, "STARTTLS")
  318. if not st then
  319. return st, reply
  320. end
  321. st, ret = check_reply('STARTTLS', reply)
  322. if not st then
  323. quit(socket)
  324. return st, ret
  325. end
  326. st, ret = socket:reconnect_ssl()
  327. if not st then
  328. socket:close()
  329. return st, ret
  330. end
  331. return true, reply
  332. end
  333. --- Sends the EHLO command to the SMTP server.
  334. --
  335. -- On network errors or if the SMTP command fails, the connection
  336. -- will be closed and the socket cleared.
  337. --
  338. -- @param socket connected to server
  339. -- @param domain to use in the EHLO command.
  340. -- @return true on sucess, or false on failures.
  341. -- @return response returned by the SMTP server on success, or an
  342. -- error message on failures.
  343. ehlo = function(socket, domain)
  344. local st, ret, response
  345. st, response = query(socket, "EHLO", domain)
  346. if not st then
  347. return st, response
  348. end
  349. st, ret = check_reply("EHLO", response)
  350. if not st then
  351. quit(socket)
  352. return st, ret
  353. end
  354. return st, response
  355. end
  356. --- Sends the HELP command to the SMTP server.
  357. --
  358. -- On network errors or if the SMTP command fails, the connection
  359. -- will be closed and the socket cleared.
  360. --
  361. -- @param socket connected to server
  362. -- @return true on success, or false on failures.
  363. -- @return response returned by the SMTP server on success, or an
  364. -- error message on failures.
  365. help = function(socket)
  366. local st, ret, response
  367. st, response = query(socket, "HELP")
  368. if not st then
  369. return st, response
  370. end
  371. st, ret = check_reply("HELP", response)
  372. if not st then
  373. quit(socket)
  374. return st, ret
  375. end
  376. return st, response
  377. end
  378. --- Sends the MAIL command to the SMTP server.
  379. --
  380. -- On network errors or if the SMTP command fails, the connection
  381. -- will be closed and the socket cleared.
  382. --
  383. -- @param socket connected to server.
  384. -- @param address of the sender.
  385. -- @param esmtp_opts The additional ESMTP options table, possible values:
  386. -- size: a decimal value to represent the message size in octets.
  387. -- ret: include the message in the DSN, should be 'FULL' or 'HDRS'.
  388. -- envid: envelope identifier, printable characters that would be
  389. -- transmitted along with the message and included in the
  390. -- failed DSN.
  391. -- transid: a globally unique case-sensitive value that identifies
  392. -- this particular transaction.
  393. -- @return true on success, or false on failures.
  394. -- @return response returned by the SMTP server on success, or an
  395. -- error message on failures.
  396. mail = function(socket, address, esmtp_opts)
  397. local st, ret, response
  398. if esmtp_opts and next(esmtp_opts) then
  399. local data = ""
  400. -- we do not check for strange values, read the NSEDoc.
  401. for k,v in pairs(esmtp_opts) do
  402. k = k:upper()
  403. data = string.format("%s %s=%s", data, k, v)
  404. end
  405. st, response = query(socket, "MAIL",
  406. string.format("FROM:<%s>%s",
  407. address, data))
  408. else
  409. st, response = query(socket, "MAIL",
  410. string.format("FROM:<%s>", address))
  411. end
  412. if not st then
  413. return st, response
  414. end
  415. st, ret = check_reply("MAIL", response)
  416. if not st then
  417. quit(socket)
  418. return st, ret
  419. end
  420. return st, response
  421. end
  422. --- Sends the RCPT command to the SMTP server.
  423. --
  424. -- On network errors or if the SMTP command fails, the connection
  425. -- will be closed and the socket cleared.
  426. --
  427. -- @param socket connected to server.
  428. -- @param address of the recipient.
  429. -- @return true on success, or false on failures.
  430. -- @return response returned by the SMTP server on success, or an
  431. -- error message on failures.
  432. recipient = function(socket, address)
  433. local st, ret, response
  434. st, response = query(socket, "RCPT",
  435. string.format("TO:<%s>", address))
  436. if not st then
  437. return st, response
  438. end
  439. st, ret = check_reply("RCPT", response)
  440. if not st then
  441. quit(socket)
  442. return st, ret
  443. end
  444. return st, response
  445. end
  446. --- Sends data to the SMTP server.
  447. --
  448. -- This function will automatically adds <code><CRLF>.<CRLF></code> at the
  449. -- end. On network errors or if the SMTP command fails, the connection
  450. -- will be closed and the socket cleared.
  451. --
  452. -- @param socket connected to server.
  453. -- @param data to be sent.
  454. -- @return true on success, or false on failures.
  455. -- @return response returned by the SMTP server on success, or an
  456. -- error message on failures.
  457. datasend = function(socket, data)
  458. local st, ret, response
  459. st, response = query(socket, "DATA")
  460. if not st then
  461. return st, response
  462. end
  463. st, ret = check_reply("DATA", response)
  464. if not st then
  465. quit(socket)
  466. return st, ret
  467. end
  468. if data then
  469. st, response = query(socket, data.."\r\n.")
  470. if not st then
  471. return st, response
  472. end
  473. st, ret = check_reply("DATA", response)
  474. if not st then
  475. quit(socket)
  476. return st, ret
  477. end
  478. end
  479. return st, response
  480. end
  481. --- Sends the RSET command to the SMTP server.
  482. --
  483. -- On network errors or if the SMTP command fails, the connection
  484. -- will be closed and the socket cleared.
  485. --
  486. -- @param socket connected to server.
  487. -- @return true on success, or false on failures.
  488. -- @return response returned by the SMTP server on success, or an
  489. -- error message on failures.
  490. reset = function(socket)
  491. local st, ret, response
  492. st, response = query(socket, "RSET")
  493. if not st then
  494. return st, response
  495. end
  496. st, ret = check_reply("RSET", response)
  497. if not st then
  498. quit(socket)
  499. return st, ret
  500. end
  501. return st, response
  502. end
  503. --- Sends the VRFY command to verify the validity of a mailbox.
  504. --
  505. -- On network errors or if the SMTP command fails, the connection
  506. -- will be closed and the socket cleared.
  507. --
  508. -- @param socket connected to server.
  509. -- @param mailbox to verify.
  510. -- @return true on success, or false on failures.
  511. -- @return response returned by the SMTP server on success, or an
  512. -- error message on failures.
  513. verify = function(socket, mailbox)
  514. local st, ret, response
  515. st, response = query(socket, "VRFY", mailbox)
  516. st, ret = check_reply("VRFY", response)
  517. if not st then
  518. quit(socket)
  519. return st, ret
  520. end
  521. return st, response
  522. end
  523. --- Sends the QUIT command to the SMTP server, and closes the socket.
  524. --
  525. -- @param socket connected to server.
  526. quit = function(socket)
  527. stdnse.print_debug(3, "SMTP: sending 'QUIT'.")
  528. socket:send("QUIT\r\n")
  529. socket:close()
  530. end
  531. --- Attempts to authenticate with the SMTP server. The supported authentication
  532. -- mechanisms are: LOGIN, PLAIN, CRAM-MD5, DIGEST-MD5 and NTLM.
  533. --
  534. -- @param socket connected to server.
  535. -- @param username SMTP username.
  536. -- @param password SMTP password.
  537. -- @param mech Authentication mechanism.
  538. -- @return true on success, or false on failures.
  539. -- @return response returned by the SMTP server on success, or an
  540. -- error message on failures.
  541. login = function(socket, username, password, mech)
  542. assert(mech == "LOGIN" or mech == "PLAIN" or mech == "CRAM-MD5"
  543. or mech == "DIGEST-MD5" or mech == "NTLM",
  544. ("Unsupported authentication mechanism (%s)"):format(mech or "nil"))
  545. local status, response = query(socket, "AUTH", mech)
  546. if ( not(status) ) then
  547. return false, "ERROR: Failed to send AUTH to server"
  548. end
  549. if ( mech == "LOGIN" ) then
  550. local tmp = response:match("334 (.*)")
  551. if ( not(tmp) ) then
  552. return false, "ERROR: Failed to decode LOGIN response"
  553. end
  554. tmp = base64.dec(tmp):lower()
  555. if ( not(tmp:match("^username")) ) then
  556. return false, ("ERROR: Expected \"Username\", but received (%s)"):format(tmp)
  557. end
  558. status, response = query(socket, base64.enc(username))
  559. if ( not(status) ) then
  560. return false, "ERROR: Failed to read LOGIN response"
  561. end
  562. tmp = response:match("334 (.*)")
  563. if ( not(tmp) ) then
  564. return false, "ERROR: Failed to decode LOGIN response"
  565. end
  566. tmp = base64.dec(tmp):lower()
  567. if ( not(tmp:match("^password")) ) then
  568. return false, ("ERROR: Expected \"password\", but received (%s)"):format(tmp)
  569. end
  570. status, response = query(socket, base64.enc(password))
  571. if ( not(status) ) then
  572. return false, "ERROR: Failed to read LOGIN response"
  573. end
  574. if ( response:match("^235") ) then
  575. return true, "Login success"
  576. end
  577. return false, response
  578. end
  579. if ( mech == "NTLM" ) then
  580. -- sniffed of the wire, seems to always be the same
  581. -- decodes to some NTLMSSP blob greatness
  582. status, response = query(socket, "TlRMTVNTUAABAAAAB7IIogYABgA3AAAADwAPACgAAAAFASgKAAAAD0FCVVNFLUFJUi5MT0NBTERPTUFJTg==")
  583. if ( not(status) ) then return false, "ERROR: Failed to receieve NTLM challenge" end
  584. end
  585. local chall = response:match("^334 (.*)")
  586. chall = (chall and base64.dec(chall))
  587. if (not(chall)) then return false, "ERROR: Failed to retrieve challenge" end
  588. -- All mechanisms expect username and pass
  589. -- add the otheronce for those who need them
  590. local mech_params = { username, password, chall, "smtp" }
  591. local auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params))
  592. auth_data = base64.enc(auth_data)
  593. status, response = query(socket, auth_data)
  594. if ( not(status) ) then
  595. return false, ("ERROR: Failed to authenticate using SASL %s"):format(mech)
  596. end
  597. if ( mech == "DIGEST-MD5" ) then
  598. local rspauth = response:match("^334 (.*)")
  599. if ( rspauth ) then
  600. rspauth = base64.dec(rspauth)
  601. status, response = query(socket,"")
  602. end
  603. end
  604. if ( response:match("^235") ) then return true, "Login success" end
  605. return false, response
  606. end
  607. return _ENV;