require'web.web'local t,w,O,l=require,ipairs,setmetatable,type
local s,c=tonumber,tostring
local _=pairs
local n=string.format
local o=string.untaint
local L=string.match
local P=math.random
local T=table.concat
local a=t'io'local e=ngx
local r=t'datamodel'local A=t'srp'local f=t'web.sessioncontrol'local t=t("tch.posix")local E=t.clock_gettime
local b=t.CLOCK_MONOTONIC
local i={}local v
local m
local d
local h
local t="/var/run/assistance/%s"local function p(e)local n={wanip="";wanport="";lanport="";enabled="0";password="";mode="0";ifname="";ifname6="";}local e=a.open(t:format(e),'r')if e then
for t in e:lines()do
local e,t=t:match('^%s*([^=%s]*)%s*=%s*([^%s]*)')if e then
n[e]=t
end
end
e:close()end
return n
end
i.loadState=p
local function u(i,o,l)local e=a.open(t:format(i),'w')if e then
for a,t in _(o)do
e:write(n("%s=%s\n",a,t))end
e:close()if not l then
local e=n("uci.web.assistance.@%s.active",i)r.set(e,o.enabled or"trigger")r.apply()end
end
end
local function R(e)local e=r.get(n('rpc.network.interface.@%s.ipaddr',e))if e and e[1]and(e[1].param=='ipaddr')then
return e[1].value
end
end
local function g(e)if e and e~=""then
local e=r.get(n('rpc.network.interface.@%s.ip6addr',e))if e and e[1]and(e[1].param=='ip6addr')then
return e[1].value and string.match(e[1].value,"^%S+")end
end
end
local function N(t,e)local n={}for a=1,t do
local t=P(#e)n[a]=e:sub(t,t)end
return T(n,'')end
local t={}t.__index=t
function t:enabled()return self._psw~=nil
end
function t:username()return self._user
end
function t:port()return self._port or''end
function t:URL()if self:enabled()then
local e=R(self._interface)local t=self._port
if e and(e~='')and t then
return n("https://%s:%d",e,t)end
end
end
function t:URL6()if self:enabled()then
local t=g(self._interface6)local e=self._port
if t and e then
return n("https://[%s]:%d",t,e)end
end
end
function t:password()return self._pswcfg==nil and self._psw or''end
function t:isPermanentMode()return self._permanent
end
function t:isRandomPassword()return self._pswcfg==nil
end
local function a(n,e)if e.salt and n.salt~=e.salt then
return true
end
if e.verifier and n.verifier~=e.verifier then
return true
end
end
local function I(e,n)if n then
e._config_update=nil
if(e._fromPort~=n.fromPort)or(e._toPort~=n.toPort)then
e._fromPort=n.fromPort
e._toPort=n.toPort
end
if e._interface~=n.interface then
e._interface=n.interface
end
if e._interface6~=n.interface6 then
e._interface6=n.interface6
end
end
return changed
end
local function V(e,o,n)local t=e._pswcfg
local r=l(t)=="table"and l(n)=="table"if r then
if a(t,n)then
return true
end
elseif(n~=false)and(t~=n)then
return true
end
if e._permanent~=(o or false)then
return true
end
if e._wanip~=(R(e._interface)or'')then
return true
end
if e._wanipv6~=g(e._interface6)then
return true
end
end
local function H(e,t,n)e._permanent=t
if l(n)=="table"then
e._pswcfg={salt=n.salt,verifier=n.verifier}elseif n==nil or l(n)=="string"then
e._pswcfg=n
end
local n=p(e._name)n.mode=e._permanent and"1"or"0"if e._pswcfg~=nil then
n.password=''elseif not e._pswcfg and n.password==''then
n.password='_DUMMY_PASSWORD_'end
n.ifname=e._interface
n.ifname6=e._interface6
u(e._name,n,true)return true
end
local function S(e)local e=r.get(e)if e then
local n={}for t,e in w(e)do
n[e.param]=e.value
end
return n
end
end
local function x(e)local t=e._name
local o="uci.web.assist_state.@state_%s.%s"local a=e:enabled()local i=e._mgr.users[e._user]if e._persistent then
r.add('uci.web.assist_state.',n('state_%s',t))else
a=false
end
local e={[o:format(t,'enabled')]=a and'1'or'0',[o:format(t,'port')]=c(e:port()),[o:format(t,'salt')]=a and i.srp_salt or'',[o:format(t,'verifier')]=a and i.srp_verifier or'',}r.set(e)end
local T
function t:enable(a,e,t)if self._reload_config and not self:enabled()then
I(self,T(self._name))self._reload_config=nil
end
local n=false
e=e or self._persistent
if V(self,e,t)and self:enabled()then
local e,t=m(self)if not e then
return nil,t
end
n=true
end
if not self:enabled()then
H(self,e,t)end
local e=true
if a then
if not self:enabled()then
e=v(self)n=true
end
else
if self:enabled()then
e=m(self)n=true
end
end
if n then
x(self)end
return e
end
function t:enable_with_reload(n,t,e)self._reload_config=true
return self:enable(n,t,e)end
local function x(t)if not t._persistent then
return
end
local e=S(n("uci.web.assist_state.@state_%s.",t._name))if(not e)or(e.enabled~='1')then
return
end
local n=s(o(e.port))if not n then
return
end
e.port=n
t._restore_state=e
t:enable(true)t._restore_state=nil
end
local function S(n,e)e._timerRunning=false
if n then
return
end
if not d(e)then
h(e)end
end
local function a(t,e)local n=e-t
local e=0
if n>0 then
e=P(n)-1
end
return t+e
end
function v(e)local t
local n=r.get("uci.web.assistance.@remote.port")if e._restore_state then
if n and n[1]and not n[1].value==e._restore_state.port then
t=n[1].value
else
t=e._restore_state.port
end
elseif n and n[1]and not(n[1].value=="")then
t=n[1].value
else
t=a(e._fromPort,e._toPort)end
local n=e._mgr.users[e._user]if n then
f.user_lock(n)local a,r
if(e._pswcfg==nil)and not e._restore_state then
a=N(10,e._pswchars)r=a
elseif l(e._pswcfg)=="string"then
a=e._pswcfg
end
if e._restore_state then
n.srp_salt=o(e._restore_state.salt)n.srp_verifier=o(e._restore_state.verifier)if not e._pswcfg then
e._pswcfg={salt=n.srp_salt,verifier=n.srp_verifier}end
e._psw=""elseif a then
local t,r=A.new_user(e._user,a)n.srp_salt=t
n.srp_verifier=r
e._psw=a
else
if e._pswcfg.salt then
n.srp_salt=e._pswcfg["salt"]n.srp_verifier=e._pswcfg["verifier"]end
e._psw="_DUMMY_PASSWORD_"end
t=c(o(t))e._port=t
e._wanip=R(e._interface)or''e._wanipv6=g(e._interface6)e:activity()u(e._name,{wanip=e._wanip;wanipv6=e._wanipv6;wanport=c(t);lanport=e._lanport;enabled="1";password=r or'';mode=e._permanent and"1"or"0";ifname=e._interface;ifname6=e._interface6})return true
end
return nil,"internal error: user disappeared"end
function t:getExternalAddress()return self._wanip,self._port
end
function m(n)local t=n._mgr.users[n._user]if t then
f.user_unlock(t)t.srp_verifier=''end
n._mgr:invalidateSessions()n._psw=nil
n._port=nil
n.timestamp=nil
e.log(e.INFO,"disabled assistant ")local e=p(n._name)e.enabled="0"u(n._name,e)return true
end
function h(n)if not n._timerRunning then
local a,t=e.timer.at(60,S,n)if not a then
e.log(e.ERR,"failed to create assistant timer ",t)else
n._timerrunning=true
end
end
end
function t:activity()if not o(e.var.request_uri):match("/ajax/")then
d(self)if self._psw then
self.timestamp=E(b)h(self)end
end
end
function d(e)local n=not e._permanent and
e.timestamp and(e.timestamp+e._timeout)<E(b)or
false
if n then
e:enable(false)end
return n
end
local function l(t)t.persistent=false
local a=s(t.timeout)if not a then
e.log(e.ERR,n("invalid timeout value (%s) for assistant %s",c(t.timeout),t._name))e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
if a<1 then
if a==-1 then
t.persistent=true
else
e.log(e.ERR,n("negative timeout value (%d) for assistant %s",a,t._name))e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
else
t.timeout=a*60
end
local a,r=t.port:match('^%s*(%d+)%s*-%s*(%d+)%s*$')if a then
a=s(a)r=s(r)else
a=s(t.port)r=a
end
if not(a and r)then
e.log(e.ERR,n("invalid port spec for assistant %s",t._name))e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
if r<a then
a,r=r,a
end
t.fromPort=a
t.toPort=r
t.port=nil
return t
end
local function c(e,n)e=l(e)local e={_name=e._name;_mgr=n;_user=e.user;_timeout=e.timeout,_interface=e.interface;_interface6=e.interface6;_lanport=f.mgrport(e.sessionmgr);_fromPort=e.fromPort;_toPort=e.toPort;_port=nil;_psw=nil;_pswchars=e.passwordchars;_persistent=e.persistent;_permanent=false;_pswcfg=nil;}return O(e,t)end
local function s(a,i)local e
local t=n("uci.web.assistance.@%s.",a)local n=r.get(t)if n then
e=i or{}for a,n in w(n)do
if(n.path==t)then
local t=o(n.value)if t~=''then
e[o(n.param)]=t
end
end
end
e._name=a
end
return e
end
local function t(e)return s(e,{interface="wan",timeout=30,port="55000-56000",passwordchars="23456789abcdefghijkmnpqrstuvwxyz!@#$%*.ABCDEFGHJKLMNPQRSTUVWXYZ"})end
T=function(e)local e=l(t(e))return{fromPort=e.fromPort,toPort=e.toPort,interface=e.interface,interface6=e.interface6,}end
local function l(a)local t=t(a)if not t then
e.log(e.INFO,n("%s assistance not enabled",a))return
end
local r=t.sessionmgr
if not r then
e.log(e.ERR,n("assistant %s has no sessionmgr assigned",a))e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
if not t.user then
e.log(e.ERR,n("assistant %s has no user assigned",a))e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
local a=f.loadmgr(r)if not a then
e.log(e.ERR,n("session manager %s does not exist",r))e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
local o=a.users[o(t.user or'')]if not o then
e.log(e.ERR,n("session manager %s has no user %s",r,t.user))e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
local e=a.assistant
if not e then
e=c(t,a)a.assistant=e
end
return e
end
local t={}function i.getAssistant(n)local e=t[n]if not e then
local a=l(n)e={assistant=a}t[n]=e
end
return e.assistant
end
function i.assistantNames()local e={}for n in _(t)do
e[#e+1]=n
end
return e
end
local n=false
function i.enable()if not n then
if e.get_phase()~="access"then
e.log(e.ERR,"web.assistance.enable() called outside access phase")e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
local e={}local t=r.get("uci.web.assistance.")if t then
for t,n in w(t)do
local n=L(o(n.path),'%.@([^.]*)%.')if n then
e[n]=true
end
end
end
for e,t in _(e)do
local e=i.getAssistant(e)if e then
x(e)end
end
n=true
end
end
return i