PageRenderTime 97ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/nselib/mobileme.lua

https://github.com/prakashgamit/nmap
Lua | 228 lines | 135 code | 33 blank | 60 comment | 18 complexity | 50755380f77735a4317103f343f71ea9 MD5 | raw file
Possible License(s): BSD-3-Clause, GPL-2.0, LGPL-2.0, LGPL-2.1
  1. local http = require "http"
  2. local json = require "json"
  3. local package = require "package"
  4. local stdnse = require "stdnse"
  5. local table = require "table"
  6. _ENV = stdnse.module("mobileme", stdnse.seeall)
  7. ---
  8. -- A MobileMe web service client that allows discovering Apple devices
  9. -- using the "find my iPhone" functionality.
  10. --
  11. -- @author "Patrik Karlsson <patrik@cqure.net>"
  12. --
  13. MobileMe = {
  14. -- headers used in all requests
  15. headers = {
  16. ["Content-Type"] = "application/json; charset=utf-8",
  17. ["X-Apple-Find-Api-Ver"] = "2.0",
  18. ["X-Apple-Authscheme"] = "UserIdGuest",
  19. ["X-Apple-Realm-Support"] = "1.0",
  20. ["User-Agent"] = "Find iPhone/1.3 MeKit (iPad: iPhone OS/4.2.1)",
  21. ["X-Client-Name"] = "iPad",
  22. ["X-Client-UUID"] = "0cf3dc501ff812adb0b202baed4f37274b210853",
  23. ["Accept-Language"] = "en-us",
  24. ["Connection"] = "keep-alive"
  25. },
  26. -- Creates a MobileMe instance
  27. -- @param username string containing the Apple ID username
  28. -- @param password string containing the Apple ID password
  29. -- @return o new instance of MobileMe
  30. new = function(self, username, password)
  31. local o = {
  32. host = "fmipmobile.icloud.com",
  33. port = 443,
  34. username = username,
  35. password = password
  36. }
  37. setmetatable(o, self)
  38. self.__index = self
  39. return o
  40. end,
  41. -- Sends a message to an iOS device
  42. -- @param devid string containing the device id to which the message should
  43. -- be sent
  44. -- @param subject string containing the messsage subject
  45. -- @param message string containing the message body
  46. -- @param alarm boolean true if alarm should be sounded, false if not
  47. -- @return status true on success, false on failure
  48. -- @return err string containing the error message (if status is false)
  49. sendMessage = function(self, devid, subject, message, alarm)
  50. local data = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true},"sound":%s,"subject":"%s","text":"%s"}'
  51. data = data:format(devid, devid, tostring(alarm), subject, message)
  52. local url = ("/fmipservice/device/%s/sendMessage"):format(self.username)
  53. local auth = { username = self.username, password = self.password }
  54. local response = http.post(self.host, self.port, url, { header = self.headers, auth = auth, timeout = 10000 }, nil, data)
  55. if ( response.status == 200 ) then
  56. local status, resp = json.parse(response.body)
  57. if ( not(status) ) then
  58. stdnse.print_debug(2, "Failed to parse JSON response from server")
  59. return false, "Failed to parse JSON response from server"
  60. end
  61. if ( resp.statusCode ~= "200" ) then
  62. stdnse.print_debug(2, "Failed to send message to server")
  63. return false, "Failed to send message to server"
  64. end
  65. end
  66. return true
  67. end,
  68. -- Updates location information for all devices controlled by the Apple ID
  69. -- @return status true on success, false on failure
  70. -- @return json parsed json table or string containing an error message on
  71. -- failure
  72. update = function(self)
  73. local auth = {
  74. username = self.username,
  75. password = self.password
  76. }
  77. local url = ("/fmipservice/device/%s/initClient"):format(self.username)
  78. local data= '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}'
  79. local retries = 2
  80. local response
  81. repeat
  82. response = http.post(self.host, self.port, url, { header = self.headers, auth = auth }, nil, data)
  83. if ( response.header["x-apple-mme-host"] ) then
  84. self.host = response.header["x-apple-mme-host"]
  85. end
  86. if ( response.status == 401 ) then
  87. return false, "Authentication failed"
  88. elseif ( response.status ~= 200 and response.status ~= 330 ) then
  89. return false, "An unexpected error occured"
  90. end
  91. retries = retries - 1
  92. until ( 200 == response.status or 0 == retries)
  93. if ( response.status ~= 200 ) then
  94. return false, "Received unexpected response from server"
  95. end
  96. local status, parsed_json = json.parse(response.body)
  97. if ( not(status) or parsed_json.statusCode ~= "200" ) then
  98. return false, "Failed to parse JSON response from server"
  99. end
  100. -- cache the parsed_json.content as devices
  101. self.devices = parsed_json.content
  102. return true, parsed_json
  103. end,
  104. -- Get's a list of devices
  105. -- @return devices table containing a list of devices
  106. getDevices = function(self)
  107. if ( not(self.devices) ) then
  108. self:update()
  109. end
  110. return self.devices
  111. end
  112. }
  113. Helper = {
  114. -- Creates a Helper instance
  115. -- @param username string containing the Apple ID username
  116. -- @param password string containing the Apple ID password
  117. -- @return o new instance of Helper
  118. new = function(self, username, password)
  119. local o = {
  120. mm = MobileMe:new(username, password)
  121. }
  122. setmetatable(o, self)
  123. self.__index = self
  124. o.mm:update()
  125. return o
  126. end,
  127. -- Get's the geolocation from each device
  128. --
  129. -- @return status true on success, false on failure
  130. -- @return result table containing a table of device locations
  131. -- the table is indexed based on the name of the device and
  132. -- contains a location table with the following fields:
  133. -- * <code>longitude</code> - the GPS longitude
  134. -- * <code>latitude</code> - the GPS latitude
  135. -- * <code>accuracy</code> - the location accuracy
  136. -- * <code>timestamp</code> - the time the location was acquired
  137. -- * <code>postype</code> - the position type (GPS or WiFi)
  138. -- * <code>finished</code> -
  139. -- or string containing an error message on failure
  140. getLocation = function(self)
  141. -- do 3 tries, with a 5 second timeout to allow the location to update
  142. -- there are two attributes, locationFinished and isLocating that seem
  143. -- to be good candidates to monitor, but so far, I haven't had any
  144. -- success with that.
  145. local tries, timeout = 3, 5
  146. local result = {}
  147. repeat
  148. local status, response = self.mm:update()
  149. if ( not(status) or not(response) ) then
  150. return false, "Failed to retrieve response from server"
  151. end
  152. for _, device in ipairs(response.content) do
  153. if ( device.location ) then
  154. result[device.name] = {
  155. longitude = device.location.longitude,
  156. latitude = device.location.latitude,
  157. accuracy = device.location.horizontalAccuracy,
  158. timestamp = device.location.timeStamp,
  159. postype = device.location.positionType,
  160. finished = device.location.locationFinished,
  161. }
  162. end
  163. end
  164. tries = tries - 1
  165. if ( tries > 0 ) then
  166. stdnse.sleep(timeout)
  167. end
  168. until( tries == 0 )
  169. return true, result
  170. end,
  171. -- Gets a list of names and ids of devices associated with the Apple ID
  172. -- @return status true on success, false on failure
  173. -- @return table of devices containing the following fields:
  174. -- <code>name</code> and <code>id</code>
  175. getDevices = function(self)
  176. local devices = {}
  177. for _, dev in ipairs(self.mm:getDevices()) do
  178. table.insert(devices, { name = dev.name, id = dev.id })
  179. end
  180. return true, devices
  181. end,
  182. -- Send a message to an iOS Device
  183. --
  184. -- @param devid string containing the device id to which the message should
  185. -- be sent
  186. -- @param subject string containing the messsage subject
  187. -- @param message string containing the message body
  188. -- @param alarm boolean true if alarm should be sounded, false if not
  189. -- @return status true on success, false on failure
  190. -- @return err string containing the error message (if status is false)
  191. sendMessage = function(self, ...)
  192. return self.mm:sendMessage(...)
  193. end
  194. }
  195. return _ENV;