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

/nselib/imap.lua

https://github.com/prakashgamit/nmap
Lua | 280 lines | 150 code | 36 blank | 94 comment | 35 complexity | 21dab9a33738cec133e47ad725713990 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-2.0, LGPL-2.1
  1. ---
  2. -- A library implementing a minor subset of the IMAP protocol, currently the
  3. -- CAPABILITY, LOGIN and AUTHENTICATE functions. The library was initially
  4. -- written by Brandon Enright and later extended and converted to OO-form by
  5. -- Patrik Karlsson <patrik@cqure.net>
  6. --
  7. -- The library consists of a <code>Helper</code>, class which is the main
  8. -- interface for script writers, and the <code>IMAP</code> class providing
  9. -- all protocol-level functionality.
  10. --
  11. -- The following example illustrates the reommended use of the library:
  12. -- <code>
  13. -- local helper = imap.Helper:new(host, port)
  14. -- helper:connect()
  15. -- helper:login("user","password","PLAIN")
  16. -- helper:close()
  17. -- </code>
  18. --
  19. -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
  20. -- @author = "Brandon Enright, Patrik Karlsson"
  21. -- Version 0.2
  22. -- Revised 07/15/2011 - v0.2 - added the IMAP and Helper classes
  23. -- added support for LOGIN and AUTHENTICATE
  24. -- <patrik@cqure.net>
  25. local base64 = require "base64"
  26. local comm = require "comm"
  27. local sasl = require "sasl"
  28. local stdnse = require "stdnse"
  29. local table = require "table"
  30. _ENV = stdnse.module("imap", stdnse.seeall)
  31. IMAP = {
  32. --- Creates a new instance of the IMAP class
  33. --
  34. -- @param host table as received by the script action method
  35. -- @param port table as received by the script action method
  36. -- @param options table containing options, currently
  37. -- <code>timeout<code> - number containing the seconds to wait for
  38. -- a response
  39. new = function(self, host, port, options)
  40. local o = {
  41. host = host,
  42. port = port,
  43. counter = 1,
  44. timeout = ( options and options.timeout ) or 10000
  45. }
  46. setmetatable(o, self)
  47. self.__index = self
  48. return o
  49. end,
  50. --- Receives a response from the IMAP server
  51. --
  52. -- @return status true on success, false on failure
  53. -- @return data string containing the received data
  54. receive = function(self)
  55. local data = ""
  56. repeat
  57. local status, tmp = self.socket:receive_buf("\r\n", false)
  58. if( not(status) ) then return false, tmp end
  59. data = data .. tmp
  60. until( tmp:match(("^A%04d"):format(self.counter - 1)) or tmp:match("^%+"))
  61. return true, data
  62. end,
  63. --- Sends a request to the IMAP server
  64. --
  65. -- @param cmd string containing the command to send to the server eg.
  66. -- eg. (AUTHENTICATE, LOGIN)
  67. -- @param params string containing the command parameters
  68. -- @return true on success, false on failure
  69. -- @return err string containing the error if status was false
  70. send = function(self, cmd, params)
  71. local data
  72. if ( not(params) ) then
  73. data = ("A%04d %s\r\n"):format(self.counter, cmd)
  74. else
  75. data = ("A%04d %s %s\r\n"):format(self.counter, cmd, params)
  76. end
  77. local status, err = self.socket:send(data)
  78. if ( not(status) ) then return false, err end
  79. self.counter = self.counter + 1
  80. return true
  81. end,
  82. --- Connect to the server
  83. --
  84. -- @return status true on success, false on failure
  85. -- @return banner string containing the server banner
  86. connect = function(self)
  87. local socket, banner, opt = comm.tryssl( self.host, self.port, "", { recv_before = true } )
  88. if ( not(socket) ) then return false, "ERROR: Failed to connect to server" end
  89. socket:set_timeout(self.timeout)
  90. if ( not(socket) or not(banner) ) then return false, "ERROR: Failed to connect to server" end
  91. self.socket = socket
  92. return true, banner
  93. end,
  94. --- Authenticate to the server (non PLAIN text mode)
  95. -- Currently supported algorithms are CRAM-MD5 and CRAM-SHA1
  96. --
  97. -- @param username string containing the username
  98. -- @param pass string containing the password
  99. -- @param mech string containing a authentication mechanism, currently
  100. -- CRAM-MD5 or CRAM-SHA1
  101. -- @return status true if login was successful, false on failure
  102. -- @return err string containing the error message if status was false
  103. authenticate = function(self, username, pass, mech)
  104. assert( mech == "NTLM" or
  105. mech == "DIGEST-MD5" or
  106. mech == "CRAM-MD5" or
  107. mech == "PLAIN",
  108. "Unsupported authentication mechanism")
  109. local status, err = self:send("AUTHENTICATE", mech)
  110. if( not(status) ) then return false, "ERROR: Failed to send data" end
  111. local status, data = self:receive()
  112. if( not(status) ) then return false, "ERROR: Failed to receive challenge" end
  113. if ( mech == "NTLM" ) then
  114. -- sniffed of the wire, seems to always be the same
  115. -- decodes to some NTLMSSP blob greatness
  116. status, data = self.socket:send("TlRMTVNTUAABAAAAB7IIogYABgA3AAAADwAPACgAAAAFASgKAAAAD0FCVVNFLUFJUi5MT0NBTERPTUFJTg==\r\n")
  117. if ( not(status) ) then return false, "ERROR: Failed to send NTLM packet" end
  118. status, data = self:receive()
  119. if ( not(status) ) then return false, "ERROR: Failed to receieve NTLM challenge" end
  120. end
  121. if ( data:match(("^A%04d "):format(self.counter-1)) ) then
  122. return false, "ERROR: Authentication mechanism not supported"
  123. end
  124. local digest, auth_data
  125. if ( not(data:match("^+")) ) then
  126. return false, "ERROR: Failed to receive proper response from server"
  127. end
  128. data = base64.dec(data:match("^+ (.*)"))
  129. -- All mechanisms expect username and pass
  130. -- add the otheronce for those who need them
  131. local mech_params = { username, pass, data, "imap" }
  132. auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params))
  133. auth_data = base64.enc(auth_data) .. "\r\n"
  134. status, data = self.socket:send(auth_data)
  135. if( not(status) ) then return false, "ERROR: Failed to send data" end
  136. status, data = self:receive()
  137. if( not(status) ) then return false, "ERROR: Failed to receive data" end
  138. if ( mech == "DIGEST-MD5" ) then
  139. local rspauth = data:match("^+ (.*)")
  140. if ( rspauth ) then
  141. rspauth = base64.dec(rspauth)
  142. status, data = self.socket:send("\r\n")
  143. status, data = self:receive()
  144. end
  145. end
  146. if ( data:match(("^A%04d OK"):format(self.counter - 1)) ) then
  147. return true
  148. end
  149. return false, "Login failed"
  150. end,
  151. --- Login to the server using PLAIN text authentication
  152. --
  153. -- @param username string containing the username
  154. -- @param password string containing the password
  155. -- @return status true on success, false on failure
  156. -- @return err string containing the error message if status was false
  157. login = function(self, username, password)
  158. local status, err = self:send("LOGIN", ("\"%s\" \"%s\""):format(username, password))
  159. if( not(status) ) then return false, "ERROR: Failed to send data" end
  160. local status, data = self:receive()
  161. if( not(status) ) then return false, "ERROR: Failed to receive data" end
  162. if ( data:match(("^A%04d OK"):format(self.counter - 1)) ) then
  163. return true
  164. end
  165. return false, "Login failed"
  166. end,
  167. --- Retrieves a list of server capabilities (eg. supported authentication
  168. -- mechanisms, QUOTA, UIDPLUS, ACL ...)
  169. --
  170. -- @return status true on success, false on failure
  171. -- @return capas array containing the capabilities that are supported
  172. capabilities = function(self)
  173. local capas = {}
  174. local proto = (self.port.version and self.port.version.service_tunnel == "ssl" and "ssl") or "tcp"
  175. local status, err = self:send("CAPABILITY")
  176. if( not(status) ) then return false, err end
  177. local status, line = self:receive()
  178. if (not(status)) then
  179. capas.CAPABILITY = false
  180. else
  181. while status do
  182. if ( line:match("^%*%s+CAPABILITY") ) then
  183. line = line:gsub("^%*%s+CAPABILITY", "")
  184. for capability in line:gmatch("[%w%+=-]+") do
  185. capas[capability] = true
  186. end
  187. break
  188. end
  189. status, line = self.socket:receive()
  190. end
  191. end
  192. return true, capas
  193. end,
  194. --- Closes the connection to the IMAP server
  195. -- @return true on success, false on failure
  196. close = function(self) return self.socket:close() end
  197. }
  198. -- The helper class, that servers as interface to script writers
  199. Helper = {
  200. -- @param host table as received by the script action method
  201. -- @param port table as received by the script action method
  202. -- @param options table containing options, currently
  203. -- <code>timeout<code> - number containing the seconds to wait for
  204. -- a response
  205. new = function(self, host, port, options)
  206. local o = { client = IMAP:new( host, port, options ) }
  207. setmetatable(o, self)
  208. self.__index = self
  209. return o
  210. end,
  211. --- Connects to the IMAP server
  212. -- @return status true on success, false on failure
  213. connect = function(self)
  214. return self.client:connect()
  215. end,
  216. --- Login to the server using eithe plain-text or using the authentication
  217. -- mechanism provided in the mech argument.
  218. --
  219. -- @param username string containing the username
  220. -- @param password string containing the password
  221. -- @param mech [optional] containing the authentication mechanism to use
  222. -- @return status true on success, false on failure
  223. login = function(self, username, password, mech)
  224. if ( not(mech) or mech == "LOGIN" ) then
  225. return self.client:login(username, password)
  226. else
  227. return self.client:authenticate(username, password, mech)
  228. end
  229. end,
  230. --- Retrieves a list of server capabilities (eg. supported authentication
  231. -- mechanisms, QUOTA, UIDPLUS, ACL ...)
  232. --
  233. -- @return status true on success, false on failure
  234. -- @return capas array containing the capabilities that are supported
  235. capabilities = function(self)
  236. return self.client:capabilities()
  237. end,
  238. --- Closes the connection to the IMAP server
  239. -- @return true on success, false on failure
  240. close = function(self)
  241. return self.client:close()
  242. end,
  243. }
  244. return _ENV;