OpenWrt_Luci_Lua/1_1.Mi_Lua/xiaoqiang/util/XQNetUtil.lua
2015-06-13 22:22:49 +08:00

680 lines
23 KiB
Lua

module ("xiaoqiang.util.XQNetUtil", package.seeall)
local XQFunction = require("xiaoqiang.common.XQFunction")
local XQConfigs = require("xiaoqiang.common.XQConfigs")
local LuciProtocol = require("luci.http.protocol")
local XQHttpUtil = require("xiaoqiang.util.XQHttpUtil")
local DEFAULT_TOKEN = "8007236f-a2d6-4847-ac83-c49395ad6d65"
local V3_TOKEN
function getToken()
return V3_TOKEN
end
function getMacAddr()
local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil")
return XQLanWanUtil.getDefaultMacAddress()
end
function getSN()
local LuciUtil = require("luci.util")
local sn = LuciUtil.exec(XQConfigs.GET_NVRAM_SN)
if XQFunction.isStrNil(sn) then
return nil
else
sn = LuciUtil.trim(sn)
end
return sn
end
function getUserAgent()
local sn = getSN() or ""
return "miwifi-"..sn
end
function cryptUrl(serverUrl, subUrl, params, salt)
local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil")
if serverUrl == nil or params == nil then
return nil
end
local time = XQFunction.getTime()
table.insert(params,{"time",time})
table.sort(params, function(a, b) return a[1] < b[1] end)
local str = ""
table.foreach(params, function(k, v) str = str..v[1].."="..v[2].."&" end)
if salt ~= nil and salt ~= "" then
str = str .. salt
end
local md5 = XQCryptoUtil.md5Base64Str(str)
local token = getToken()
local isV2 = string.find(serverUrl..subUrl,"/v2/") ~= nil or string.find(serverUrl..subUrl,"/rs/") ~= nil
if XQFunction.isStrNil(token) or isV2 then
token = DEFAULT_TOKEN
end
local url = ""
if string.find(serverUrl..subUrl,"?") == nil then
url = serverUrl..subUrl.."?s="..md5.."&time="..time.."&token="..LuciProtocol.urlencode(token)
else
url = serverUrl..subUrl.."&s="..md5.."&time="..time.."&token="..LuciProtocol.urlencode(token)
end
return url
end
--
-- HBase
--
local HBASE_LOG_UPLOAD_URL = "https://data.gorouter.info/xiaoqiang_log/"
local HBASE_CONFIG_UPLOAD_URL = "https://data.gorouter.info/xiaoqiang_config/"
local SUB_KEY = "false-row-key"
local CURL_CMD = "curl -k -i -f -X PUT %s%s -H \"Content-Type: application/json\" --data @%s 2>/dev/null"
local CONFIG_KEY = "Qzo="
local LOG_TYPE = {
["M"] = "TTo=",
["B"] = "Qjo=",
["X"] = "WDo=",
["Y"] = "WTo=",
["Z"] = "Wjo="
}
--[[
M:Network detection log
B:System log
X:Not use
Y:Not use
Z:Not use
]]--
function uploadLogFile(logfile,logtype)
local Mime = require("mime")
local LuciJson = require("json")
local uuid = getMacAddr()
if uuid == nil then
return false
end
local logKey = LOG_TYPE[logtype]
if XQFunction.isStrNil(logKey) then
return false
end
local filebase64str = luci.util.exec("/usr/bin/base64 "..logfile)
local timestamp = string.format("%012d",os.time())
local key = Mime.b64(uuid.."-"..timestamp)
local blob = {["column"]=logKey, ["$"]=string.gsub(filebase64str,"\n","")}
local cell = {blob}
local item = {["key"]=key,["Cell"]=cell}
local data = {["Row"]=item}
local dataStr = LuciJson.encode(data)
local logJsonFile = io.open(XQConfigs.XQ_LOG_JSON_FILEPATH,"w")
if logJsonFile then
logJsonFile:write(dataStr)
logJsonFile:close()
end
local command = string.format(CURL_CMD,HBASE_LOG_UPLOAD_URL,SUB_KEY,XQConfigs.XQ_LOG_JSON_FILEPATH)
local result = luci.util.exec(command)
if result == nil or result == "" then
return false
else
if string.find(result,"OK") ~= nil then
return true
else
return false
end
end
end
function uploadConfigFile(configfile)
local Mime = require("mime")
local LuciJson = require("json")
local uuid = getMacAddr()
if uuid == nil then
return false
end
local filebase64str = luci.util.exec("/usr/bin/base64 "..configfile)
local key = Mime.b64(uuid)
local config = {["column"]=CONFIG_KEY, ["$"]=string.gsub(filebase64str,"\n","")}
local cell = {config}
local item = {["key"]=key,["Cell"]=cell}
local data = {["Row"]=item}
local dataStr = LuciJson.encode(data)
local configJsonFile = io.open(XQConfigs.XQ_CONFIG_JSON_FILEPATH,"w")
if configJsonFile then
configJsonFile:write(dataStr)
configJsonFile:close()
end
local command = string.format(CURL_CMD,HBASE_CONFIG_UPLOAD_URL,SUB_KEY,XQConfigs.XQ_CONFIG_JSON_FILEPATH)
local result = luci.util.exec(command)
if result == nil or result == "" then
return false
else
if string.find(result,"OK") ~= nil then
return true
else
return false
end
end
end
--[[
@param configfile: save path
unzip: invalid option -- t
]]--
function getConfigFile(configfile)
local Xml = require("slaxdom")
local SSLHttps = require("ssl.https")
local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil")
local uuid = getMacAddr()
if uuid == nil then
return false
end
local url = HBASE_CONFIG_UPLOAD_URL..uuid
local xmlstr = SSLHttps.request(url)
if xmlstr == nil or xmlstr == "" then
return false
end
if string.find(xmlstr,"html") ~= nil then
return false
end
local obj = Xml:dom(xmlstr)
local arr = obj.root.el[1].el
if #arr == 0 then
return false
end
local record = arr[1].kids[1].value
local decstr = XQCryptoUtil.binaryBase64Dec(record)
fd = io.open(configfile, "w")
fd:write(decstr)
fd:close()
local check = luci.util.exec("unzip -o "..configfile)
luci.util.exec("rm -rf /tmp/etc/")
if string.find(check,"invalid") ~= nil or string.find(check,"inflate error") ~= nil then
return false
end
return true
end
--
-- Xiaomi V3
--
local ACCOUNT_DOMAIN_ONLINE = "https://account.xiaomi.com/"
local ACCOUNT_DOMAIN_STAGING = "http://account.preview.n.xiaomi.net/"
local PASSPORT_LOGIN_PWD_URL = "pass/serviceLoginAuth"
local PASSPORT_LOGIN_PASSTOKEN_URL = "pass/serviceLogin?sid=xiaoqiang"
function xiaomiLogin(username,password)
local LuciJson = require("json")
local XQLog = require("xiaoqiang.XQLog")
local XQDBUtil = require("xiaoqiang.util.XQDBUtil")
local XQSysUtil = require("xiaoqiang.util.XQSysUtil")
local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil")
local userId,nonce,passToken,ssecurity,psecurity,location,domain
--
-- step1 get passtoken security and nonce /https
--
local params = {
{"user", username},
{"hash", string.upper(password)},
{"sid", "xiaoqiang"},
{"deviceId", getSN() or ""}
}
local url
if XQConfigs.SERVER_CONFIG == 1 then
url = ACCOUNT_DOMAIN_STAGING..PASSPORT_LOGIN_PWD_URL
else
url = ACCOUNT_DOMAIN_ONLINE..PASSPORT_LOGIN_PWD_URL
end
local data = ""
table.foreach(params, function(k, v) data = data..v[1].."="..v[2].."&" end)
local step1 = XQHttpUtil.httpGetRequest(url,string.sub(data,1,-2))
if step1.code == 302 then
local extensionPragma = step1.headers["extension-pragma"]
local setCookie = step1.headers["set-cookie"]
local extensionJson = LuciJson.decode(extensionPragma)
location = step1.headers["location"]
userId = setCookie:match('userId=(%d+);')
passToken = setCookie:match('passToken=(%S+);')
domain = setCookie:match('domain=(%S+);')
nonce = extensionPragma:match('%S+\"nonce\":(%d+),%S+')
ssecurity = extensionJson["ssecurity"]
psecurity = extensionJson["psecurity"]
XQLog.log(7,"XiaomiLogin Step1 Succeed:",step1)
--
-- step2 get xiaoqiang server token
--
local tobeSign = "nonce="..nonce.."&"..ssecurity
local sign = XQCryptoUtil.binaryBase64Enc(XQCryptoUtil.sha1Binary(tobeSign))
local queryStr = LuciProtocol.xq_urlencode_params({
["uuid"] = userId,
["clientSign"] = sign
})
local step2url = location.."&"..queryStr
local step2 = XQHttpUtil.httpGetRequest(step2url)
local stoken
if step2.code == 200 and type(step2.headers) == "table" then
local xqcookie = step2.headers["set-cookie"]
if xqcookie then
stoken = xqcookie:match('serviceToken=(%S+);')
end
elseif step2.code == 401 then
XQLog.log(3,"XiaomiLogin Step2 401 Failed:", step2url, step2)
return {code = 2}
end
local sid = ssecurity
if not XQFunction.isStrNil(userId) and
not XQFunction.isStrNil(passToken) and
not XQFunction.isStrNil(stoken) and
not XQFunction.isStrNil(sid) and
not XQFunction.isStrNil(ssecurity) then
XQLog.log(7,"XiaomiLogin Step2 succeed:"..userId)
XQDBUtil.savePassport(userId,passToken,stoken,sid,ssecurity)
local result = {
code = 0,
uuid = userId,
token = passToken,
stoken = stoken,
sid = sid,
ssecurity = ssecurity
}
return result
else
XQLog.log(3,"XiaomiLogin Step2 Failed:",{
step2url = step2url,
userId = userId or "",
passToken = passToken or "",
ssecurity = ssecurity or ""
})
return {code = 2}
end
elseif step1.code == 200 then
XQLog.log(3,"XiaomiLogin Step1 Username/Password Error:", params, step1)
return {code = 1}
else
XQLog.log(3,"XiaomiLogin Step1 Service Unreachable:", params, step1)
return {code = 3}
end
end
function getPassport(uuid)
local XQSysUtil = require("xiaoqiang.util.XQSysUtil")
local XQDBUtil = require("xiaoqiang.util.XQDBUtil")
if XQFunction.isStrNil(uuid) then
uuid = XQSysUtil.getBindUUID()
end
if XQFunction.isStrNil(uuid) then
return false
end
local passport = XQDBUtil.fetchPassport(uuid)[1]
if not passport then
return false
end
if XQFunction.isStrNil(passport.token) then
return false
end
V3_TOKEN = passport.token
return passport
end
function generateOrigIconUrl(iconUrl)
if XQFunction.isStrNil(iconUrl) then
return ""
else
return string.gsub(iconUrl,".jpg","_150.jpg")
end
end
local GET_USER_INFO = "http://api.account.xiaomi.com/pass/usersCard?ids="
function getUserInfo(uuid)
local LuciJson = require("json")
local XQSysUtil = require("xiaoqiang.util.XQSysUtil")
if XQFunction.isStrNil(uuid) then
uuid = XQSysUtil.getBindUUID()
if XQFunction.isStrNil(uuid) then
return false
end
end
local result = XQHttpUtil.httpGetRequest(GET_USER_INFO..uuid)
if result.code ~= 200 then
return false
end
local resultJson = LuciJson.decode(result.res)
if resultJson then
if string.upper(resultJson.result) == "OK" then
local data = resultJson.data.list
if data[1] then
local userInfo = {}
userInfo["aliasNick"] = data[1].aliasNick or ""
userInfo["miliaoNick"] = data[1].miliaoNick or ""
userInfo["userId"] = data[1].userId or ""
userInfo["miliaoIcon"] = data[1].miliaoIcon or ""
userInfo["miliaoIconOrig"] = generateOrigIconUrl(userInfo.miliaoIcon)
XQSysUtil.setBindUserInfo(userInfo)
return userInfo
end
end
end
return false
end
--
-- XiaoQiang
--
local XIAOQIANG_SERVER = XQConfigs.SERVER_CONFIG_ONLINE_URL
if XQConfigs.SERVER_CONFIG == 1 then
XIAOQIANG_SERVER = XQConfigs.SERVER_CONFIG_STAGING_URL
elseif XQConfigs.SERVER_CONFIG == 2 then
XIAOQIANG_SERVER = XQConfigs.SERVER_CONFIG_PREVIEW_URL
end
-- GET
local XIAOQIANG_UPGRADE = "/rs/grayupgrade"
local XIAOQIANG_RECOVERY_UPGRADE = "/rs/grayupgrade/recovery"
local XIAOQIANG_DEVICELIST = "/s/admin/deviceList"
local XIAOQIANG_ADMINLIST = "/s/device/adminList"
-- POST
local XIAOQIANG_REGISTER = "/s/register"
local XIAOQIANG_PROMOTE = "/s/admin/promote"
local XIAOQIANG_DISMISS = "/s/admin/dismiss"
local XIAOQIANG_PLUGIN_ENABLE = "/s/plugin/enable"
local XIAOQIANG_PLUGIN_DISABLE = "/s/plugin/disable"
local XIAOQIANG_PLUGIN_LIST = "/s/plugin/list"
local XIAOQIANG_PLUGIN_DETAIL = "/s/plugin/detail"
local XIAOQIANG_UPDATE_DEVICENAME = "/s/device/name"
function getDeviceId()
local LuciUtil = require("luci.util")
local deviceId = LuciUtil.exec(XQConfigs.XQ_DEVICE_ID)
if XQFunction.isStrNil(deviceId) then
deviceId = ""
end
return LuciUtil.trim(deviceId)
end
function checkUpgrade()
local LuciJson = require("json")
local XQSysUtil = require("xiaoqiang.util.XQSysUtil")
local XQPreference = require("xiaoqiang.XQPreference")
local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil")
local misc = XQSysUtil.getMiscHardwareInfo()
local isrecovery = misc.recovery == 1 and true or false
local params = {}
if isrecovery then
local configs = XQSysUtil.getNvramConfigs()
params = {
{"deviceID", ""},
{"rom", configs.rom_ver},
{"hardware", XQSysUtil.getHardware()},
{"cfe", configs.uboot},
{"linux", configs.linux},
{"ramfs", configs.ramfs},
{"sqafs", configs.sqafs},
{"rootfs", configs.rootfs},
{"channel", configs.rom_channel},
{"serialNumber", XQFunction.nvramGet("SN", "")}
}
else
params = {
{"deviceID", getDeviceId()},
{"rom", XQSysUtil.getRomVersion()},
{"hardware", XQSysUtil.getHardware()},
{"cfe", XQSysUtil.getCFEVersion()},
{"linux", XQSysUtil.getKernelVersion()},
{"ramfs", XQSysUtil.getRamFsVersion()},
{"sqafs", XQSysUtil.getSqaFsVersion()},
{"rootfs", XQSysUtil.getRootFsVersion()},
{"channel", XQSysUtil.getChannel()},
{"serialNumber", XQFunction.thrift_to_mqtt_get_sn()}
}
end
local query = {}
table.foreach(params, function(k, v) query[v[1]] = v[2] end)
local queryString = LuciProtocol.urlencode_params(query)
local subUrl = (isrecovery and XIAOQIANG_RECOVERY_UPGRADE or XIAOQIANG_UPGRADE).."?"..queryString
local requestUrl = cryptUrl(XIAOQIANG_SERVER, subUrl, params, DEFAULT_TOKEN)
local response = XQHttpUtil.httpGetRequest(requestUrl)
if response.code ~= 200 then
return false
end
local resultjson = LuciJson.decode(response.res)
if not resultjson then
return false
end
if tonumber(resultjson["code"]) == 0 then
local result = {}
if resultjson.data and resultjson.data.link then
local changeLog = XQFunction.parseEnter2br(luci.util.trim(resultjson.data.description))
local weight = tonumber(resultjson.data.weight)
result["needUpdate"] = 1
result["downloadUrl"] = resultjson.data.link
result["fullHash"] = resultjson.data.hash
result["fileSize"] = resultjson.data.size
result["version"] = resultjson.data.toVersion
result["weight"] = weight or 1
result["changelogUrl"] = resultjson.data.changelogUrl
result["changeLog"] = changeLog
else
result["needUpdate"] = 0
result["version"] = XQSysUtil.getRomVersion()
result["changeLog"] = XQFunction.parseEnter2br(XQSysUtil.getChangeLog())
end
return result
else
return false
end
end
function generateSignature(method,url,params,security)
local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil")
local str = ""
if params and #params > 0 then
table.sort(params, function(a, b) return a[1] < b[1] end)
table.foreach(params, function(k, v) str = str..v[1].."="..v[2].."&" end)
end
str = str..security
if not XQFunction.isStrNil(url) then
str = url.."&"..str
end
if not XQFunction.isStrNil(method) then
str = string.upper(method).."&"..str
end
return XQCryptoUtil.hash4SHA1(str)
end
function doRequest(method,url,param,uuid)
local LuciJson = require("json")
local XQCrypto = require("xqcrypto")
local LuciUtil = require("luci.util")
local XQLog = require("xiaoqiang.XQLog")
local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil")
local passport = getPassport(uuid)
method = string.upper(method)
if not passport then
XQLog.log(3,"XQRequest: Passport missing "..url)
return false
end
local nonce = XQCrypto.generateNonce()
local cookies = {}
local encrypt = {}
local plain = {}
local params = {}
cookies["userId"] = passport.uuid
cookies["serviceToken"] = passport.stoken
local sessionSecurity = XQCrypto.generateSessionSecurity(nonce,passport.ssecurity)
local paramStr = ""
if param and type(param) == "table" then
table.sort(param, function(a, b) return a[1] < b[1] end)
for _,item in ipairs(param) do
table.insert(plain,item)
end
else
param = {}
end
local rc4HashPre = generateSignature(method,url,plain,sessionSecurity)
table.insert(param,{"rc4_hash__",rc4HashPre})
table.sort(param, function(a, b) return a[1] < b[1] end)
for _,item in ipairs(param) do
paramStr = paramStr..item[2]..";;"
end
local paramEncStr = XQCrypto.encryptParams(sessionSecurity,string.sub(paramStr,1,-3))
local paramsEnc = LuciUtil.split(paramEncStr,";;")
if paramsEnc and #paramsEnc > 0 then
for i,item in ipairs(param) do
table.insert(encrypt,{item[1],paramsEnc[i]})
table.insert(params,{item[1],paramsEnc[i]})
end
end
local rc4Hash = ""
local signature = generateSignature(method,url,encrypt,sessionSecurity)
for _,item in ipairs(encrypt) do
if item[1] == "rc4_hash__" then
rc4Hash = item[2]
end
end
table.insert(params,{"signature",signature})
table.insert(params,{"_nonce",nonce})
table.insert(params,{"rc4_hash__",rc4Hash})
local query = {}
table.foreach(params, function(k, v) query[v[1]] = v[2] end)
local queryString = LuciProtocol.xq_urlencode_params(query)
local response
if method == "GET" then
response = XQHttpUtil.httpGetRequest(XIAOQIANG_SERVER..url, queryString, cookies)
elseif method == "POST" then
response = XQHttpUtil.httpPostRequest(XIAOQIANG_SERVER..url, queryString, cookies)
end
if response.code == 200 then
local jsonResult = XQCrypto.decryptResult(sessionSecurity,response.res)
if not XQFunction.isStrNil(jsonResult) then
jsonResult = string.gsub(jsonResult,"u201c","\"")
jsonResult = string.gsub(jsonResult,"u201d","\"")
XQLog.log(7,"XQRequest succeed:"..jsonResult)
return LuciJson.decode(jsonResult)
end
elseif response.code == 401 then
XQLog.log(3,"XQRequest 401:"..XIAOQIANG_SERVER..url,"QueryString:"..queryString,params)
return {code = 401}
end
XQLog.log(3,"XQRequest Failed:"..XIAOQIANG_SERVER..url,"QueryString:"..queryString,params)
return false
end
function routerRegister(uuid)
local XQSysUtil = require("xiaoqiang.util.XQSysUtil")
local param = {}
local d = XQFunction.thrift_to_mqtt_identify_device()
if XQFunction.isStrNil(d) then
return false
end
local deviceName = XQSysUtil.getRouterName()
table.insert(param,{"d",d})
table.insert(param,{"deviceName",deviceName})
local result = doRequest("POST",XIAOQIANG_REGISTER,param,uuid)
return result
end
function promoteAccount(uuid,account)
local param = {}
local deviceID = getDeviceId()
if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(account) then
return false
end
table.insert(param,{"account",account})
table.insert(param,{"deviceID",deviceID})
local result = doRequest("POST",XIAOQIANG_PROMOTE,param,uuid)
return result
end
function dismissAccount(uuid,account)
local param = {}
local deviceID = getDeviceId()
if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(account) then
return false
end
table.insert(param,{"account",account})
table.insert(param,{"deviceID",deviceID})
local result = doRequest("POST",XIAOQIANG_DISMISS,param,uuid)
return result
end
function getAdminList(uuid)
local param = {}
local deviceID = getDeviceId()
if XQFunction.isStrNil(deviceID) then
return false
end
table.insert(param,{"deviceID",deviceID})
local result = doRequest("GET",XIAOQIANG_ADMINLIST,param,uuid)
return result
end
function getUserDeviceList(uuid)
local result = doRequest("GET",XIAOQIANG_DEVICELIST,nil,uuid)
return result
end
function pluginEnable(uuid,pluginId)
local param = {}
local deviceID = getDeviceId()
if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(pluginId) then
return false
end
table.insert(param,{"deviceID",deviceID})
table.insert(param,{"pluginID",pluginId})
local result = doRequest("POST",XIAOQIANG_PLUGIN_ENABLE,param,uuid)
return result
end
function pluginDisable(uuid,pluginId)
local param = {}
local deviceID = getDeviceId()
if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(pluginId) then
return false
end
table.insert(param,{"deviceID",deviceID})
table.insert(param,{"pluginID",pluginId})
local result = doRequest("POST",XIAOQIANG_PLUGIN_DISABLE,param,uuid)
return result
end
function pluginList(uuid)
local XQSysUtil = require("xiaoqiang.util.XQSysUtil")
local param = {}
local deviceID = getDeviceId()
local romVersion = XQSysUtil.getRomVersion()
local hardwareNum = XQSysUtil.getHardware()
if XQFunction.isStrNil(deviceID) then
return false
end
table.insert(param,{"deviceID",deviceID})
table.insert(param,{"rom",romVersion})
table.insert(param,{"hardware",hardwareNum})
local result = doRequest("GET",XIAOQIANG_PLUGIN_LIST,param,uuid)
return result
end
function pluginDetail(uuid,pluginId)
local param = {}
local deviceID = getDeviceId()
if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(pluginId) then
return false
end
table.insert(param,{"pluginID",pluginId})
local result = doRequest("GET",XIAOQIANG_PLUGIN_DETAIL,param,uuid)
return result
end
function updateDeviceName(uuid,deviceName)
local param = {}
local deviceID = getDeviceId()
if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(deviceName) then
return false
end
table.insert(param,{"deviceID",deviceID})
table.insert(param,{"deviceName",deviceName})
local result = doRequest("POST",XIAOQIANG_UPDATE_DEVICENAME,param,uuid)
return result
end