/nselib/mobileme.lua
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
- local http = require "http"
- local json = require "json"
- local package = require "package"
- 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 messsage 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":"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"}'
- 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.print_debug(2, "Failed to parse JSON response from server")
- return false, "Failed to parse JSON response from server"
- end
-
- if ( resp.statusCode ~= "200" ) then
- stdnse.print_debug(2, "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":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"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 occured"
- 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,
-
- -- Get's 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,
-
- -- Get's 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 messsage 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;