summaryrefslogtreecommitdiffstats
path: root/nselib/mobileme.lua
diff options
context:
space:
mode:
Diffstat (limited to 'nselib/mobileme.lua')
-rw-r--r--nselib/mobileme.lua242
1 files changed, 242 insertions, 0 deletions
diff --git a/nselib/mobileme.lua b/nselib/mobileme.lua
new file mode 100644
index 0000000..34b4fec
--- /dev/null
+++ b/nselib/mobileme.lua
@@ -0,0 +1,242 @@
+local http = require "http"
+local json = require "json"
+local stdnse = require "stdnse"
+local table = require "table"
+_ENV = stdnse.module("mobileme", stdnse.seeall)
+
+---
+-- A MobileMe web service client that allows discovering Apple devices
+-- using the "find my iPhone" functionality.
+--
+-- @author Patrik Karlsson <patrik@cqure.net>
+--
+
+MobileMe = {
+
+ -- headers used in all requests
+ headers = {
+ ["Content-Type"] = "application/json; charset=utf-8",
+ ["X-Apple-Find-Api-Ver"] = "2.0",
+ ["X-Apple-Authscheme"] = "UserIdGuest",
+ ["X-Apple-Realm-Support"] = "1.0",
+ ["User-Agent"] = "Find iPhone/1.3 MeKit (iPad: iPhone OS/4.2.1)",
+ ["X-Client-Name"] = "iPad",
+ ["X-Client-UUID"] = "0cf3dc501ff812adb0b202baed4f37274b210853",
+ ["Accept-Language"] = "en-us",
+ ["Connection"] = "keep-alive"
+ },
+
+ -- Creates a MobileMe instance
+ -- @param username string containing the Apple ID username
+ -- @param password string containing the Apple ID password
+ -- @return o new instance of MobileMe
+ new = function(self, username, password)
+ local o = {
+ host = "fmipmobile.icloud.com",
+ port = 443,
+ username = username,
+ password = password
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+ end,
+
+ -- Sends a message to an iOS device
+ -- @param devid string containing the device id to which the message should
+ -- be sent
+ -- @param subject string containing the message subject
+ -- @param message string containing the message body
+ -- @param alarm boolean true if alarm should be sounded, false if not
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message (if status is false)
+ sendMessage = function(self, devid, subject, message, alarm)
+ local data = '{"clientContext":{"appName":"FindMyiPhone","appVersion":\z
+ "1.3","buildVersion":"145","deviceUDID":\z
+ "0000000000000000000000000000000000000000","inactiveTime":5911,\z
+ "osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s",\z
+ "shouldLocate":false},"device":"%s","serverContext":{\z
+ "callbackIntervalInMS":3000,"clientId":\z
+ "0000000000000000000000000000000000000000","deviceLoadStatus":"203",\z
+ "hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":\z
+ 60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":\z
+ 1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":\z
+ -25200000,"previousOffset":-28800000,"previousTransition":1268560799999,\z
+ "tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},\z
+ "validRegion":true},"sound":%s,"subject":"%s","text":"%s"}'
+ data = data:format(devid, devid, tostring(alarm), subject, message)
+
+ local url = ("/fmipservice/device/%s/sendMessage"):format(self.username)
+ local auth = { username = self.username, password = self.password }
+
+ local response = http.post(self.host, self.port, url, { header = self.headers, auth = auth, timeout = 10000 }, nil, data)
+
+ if ( response.status == 200 ) then
+ local status, resp = json.parse(response.body)
+ if ( not(status) ) then
+ stdnse.debug2("Failed to parse JSON response from server")
+ return false, "Failed to parse JSON response from server"
+ end
+
+ if ( resp.statusCode ~= "200" ) then
+ stdnse.debug2("Failed to send message to server")
+ return false, "Failed to send message to server"
+ end
+ end
+ return true
+ end,
+
+ -- Updates location information for all devices controlled by the Apple ID
+ -- @return status true on success, false on failure
+ -- @return json parsed json table or string containing an error message on
+ -- failure
+ update = function(self)
+
+ local auth = {
+ username = self.username,
+ password = self.password
+ }
+
+ local url = ("/fmipservice/device/%s/initClient"):format(self.username)
+ local data= '{"clientContext":{"appName":"FindMyiPhone","appVersion":\z
+ "1.3","buildVersion":"145","deviceUDID":\z
+ "0000000000000000000000000000000000000000","inactiveTime":2147483647,\z
+ "osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}'
+
+ local retries = 2
+
+ local response
+ repeat
+ response = http.post(self.host, self.port, url, { header = self.headers, auth = auth }, nil, data)
+ if ( response.header["x-apple-mme-host"] ) then
+ self.host = response.header["x-apple-mme-host"]
+ end
+
+ if ( response.status == 401 ) then
+ return false, "Authentication failed"
+ elseif ( response.status ~= 200 and response.status ~= 330 ) then
+ return false, "An unexpected error occurred"
+ end
+
+ retries = retries - 1
+ until ( 200 == response.status or 0 == retries)
+
+ if ( response.status ~= 200 ) then
+ return false, "Received unexpected response from server"
+ end
+
+ local status, parsed_json = json.parse(response.body)
+
+ if ( not(status) or parsed_json.statusCode ~= "200" ) then
+ return false, "Failed to parse JSON response from server"
+ end
+
+ -- cache the parsed_json.content as devices
+ self.devices = parsed_json.content
+
+ return true, parsed_json
+ end,
+
+ -- Gets a list of devices
+ -- @return devices table containing a list of devices
+ getDevices = function(self)
+ if ( not(self.devices) ) then
+ self:update()
+ end
+ return self.devices
+ end
+}
+
+
+Helper = {
+
+
+ -- Creates a Helper instance
+ -- @param username string containing the Apple ID username
+ -- @param password string containing the Apple ID password
+ -- @return o new instance of Helper
+ new = function(self, username, password)
+ local o = {
+ mm = MobileMe:new(username, password)
+ }
+ setmetatable(o, self)
+ self.__index = self
+ o.mm:update()
+ return o
+ end,
+
+ -- Gets the geolocation from each device
+ --
+ -- @return status true on success, false on failure
+ -- @return result table containing a table of device locations
+ -- the table is indexed based on the name of the device and
+ -- contains a location table with the following fields:
+ -- * <code>longitude</code> - the GPS longitude
+ -- * <code>latitude</code> - the GPS latitude
+ -- * <code>accuracy</code> - the location accuracy
+ -- * <code>timestamp</code> - the time the location was acquired
+ -- * <code>postype</code> - the position type (GPS or WiFi)
+ -- * <code>finished</code> -
+ -- or string containing an error message on failure
+ getLocation = function(self)
+ -- do 3 tries, with a 5 second timeout to allow the location to update
+ -- there are two attributes, locationFinished and isLocating that seem
+ -- to be good candidates to monitor, but so far, I haven't had any
+ -- success with that.
+ local tries, timeout = 3, 5
+ local result = {}
+
+ repeat
+ local status, response = self.mm:update()
+
+ if ( not(status) or not(response) ) then
+ return false, "Failed to retrieve response from server"
+ end
+ for _, device in ipairs(response.content) do
+ if ( device.location ) then
+ result[device.name] = {
+ longitude = device.location.longitude,
+ latitude = device.location.latitude,
+ accuracy = device.location.horizontalAccuracy,
+ timestamp = device.location.timeStamp,
+ postype = device.location.positionType,
+ finished = device.location.locationFinished,
+ }
+ end
+ end
+ tries = tries - 1
+ if ( tries > 0 ) then
+ stdnse.sleep(timeout)
+ end
+ until( tries == 0 )
+ return true, result
+ end,
+
+ -- Gets a list of names and ids of devices associated with the Apple ID
+ -- @return status true on success, false on failure
+ -- @return table of devices containing the following fields:
+ -- <code>name</code> and <code>id</code>
+ getDevices = function(self)
+ local devices = {}
+ for _, dev in ipairs(self.mm:getDevices()) do
+ table.insert(devices, { name = dev.name, id = dev.id })
+ end
+ return true, devices
+ end,
+
+ -- Send a message to an iOS Device
+ --
+ -- @param devid string containing the device id to which the message should
+ -- be sent
+ -- @param subject string containing the message subject
+ -- @param message string containing the message body
+ -- @param alarm boolean true if alarm should be sounded, false if not
+ -- @return status true on success, false on failure
+ -- @return err string containing the error message (if status is false)
+ sendMessage = function(self, ...)
+ return self.mm:sendMessage(...)
+ end
+
+}
+
+return _ENV;