local A,s,f,h,n,m=setmetatable,pairs,ipairs,tonumber,require,type
local e,b=ngx,tostring
local l=n("datamodel")local N=n("srp")local O=n("web.check_host")local r=n("web.web").printf
local v=n("web.web").get_cookies
local c=string
local g=c.format
local t=c.untaint
local T=c.gsub
local o=n("tch.posix")local i=o.clock_gettime
local a=o.CLOCK_MONOTONIC
local P=n("web.session")local d={}local o={}o.__index=o
local function E()return{count=0,ipcount={},}end
local function x(e,n)if e.maxsessions<=e._session_tracker.count then
return true
end
local n=e._session_tracker.ipcount[n]or 0
if e.maxsessions_per_ip<=n then
return true
end
end
local function u(n,e,o)local n=n._session_tracker.ipcount
if e then
n[e]=(n[e]or 0)+o
end
end
local function k(e,o,n)if not e.sessions[n]then
e.sessions[n]=o
e._session_tracker.count=e._session_tracker.count+1
u(e,o.remoteIP,1)end
end
local function _(e,n)if e.sessions[n]then
local o=e.sessions[n].remoteIP
e.sessions[n]=nil
e._session_tracker.count=e._session_tracker.count-1
u(e,o,-1)end
end
local function u(o,s,t,e,n)e=e or i(a)n=n or o.timeout
local e=(s.timestamp+n)-e
if e<=0 then
_(o,t)return true,0
end
return false,e
end
local function R(o,n)local n="sessionID="..n.."; Path="..o["cookiepath"].."; HttpOnly"if o.secure_cookie=="1"then
n=n.."; Secure"end
e.header['Set-Cookie']=n
end
local function p(n,t,o)local e=t.sessionid
if e~=o then
R(n,e)n.sessions[e]=t
n.sessions[o]=nil
return e
end
end
local function w(s)local e=t(v()["sessionID"])if m(e)=="table"then
local o=false
for t,n in f(e)do
if s.sessions[n]then
e=n
o=true
break
end
end
if not o then
e=nil
end
end
return e
end
local function v(o,t)local n=w(o)local e=n and o.sessions[n]if e then
if e.remoteIP~=t then
return
end
if u(o,e,n)then
return
end
local o=p(o,e,n)if o then
return e,o
end
return e,n
end
end
local function I(e,n)for o,e in s(e.sessions)do
if e.proxy==n then
return e
end
end
return nil
end
function o:authorizeRequest(o,n)n=t(n)if self.assistant and not self.assistant:enabled()then
return false,e.HTTP_NOT_FOUND
end
if n==self.authpath or n==self.passpath or n==self.loginpath then
return true
end
local n=self.ruleset[n]if not n then
return false,e.HTTP_NOT_FOUND
end
local t=o:getrole()if not n[t]then
return false,e.HTTP_FORBIDDEN
end
if not o:isUserAllowedByIP()then
return false,e.HTTP_FORBIDDEN
end
return true
end
local function w(e)return e:match("^([^:]+):?%d*$")or e:match("^%[(.+)%]:?%d*$")end
local function n(n)if n then
n=w(n)if not O.refersToUs(n)then
e.log(e.ERR,"HTTP Host header does not refer to us")e.exit(e.HTTP_UNAUTHORIZED)end
end
end
local function O(s,n,l)local o,n=s:authorizeRequest(n,e.var.uri)if not o then
if n==e.HTTP_NOT_FOUND then
e.exit(n)end
e.log(e.WARN,"Unauthorized request")local o="sessionID="..l
local n=t(e.var.http_cookie)if n then
local e
n,e=T(n,"sessionID=%x+",o)if e==0 then
n=n.."; "..o
end
else
n=o
end
e.req.set_header("Cookie",n)e.exec(s.loginpath)end
end
local function T()local e=e.var
return{remote=t(e.remote_addr),server=t(e.server_addr),http_host=t(e.http_host)}end
local function w(o,s)local t
local n
if x(o,s.remote)then
e.log(e.ERR,"Session limit reached")return
end
while not n or o.sessions[n]do
t=P.new(s,o)n=t.sessionid
end
k(o,t,n)R(o,n)return t,n
end
local function R(e,n)n.timestamp=i(a)if e.assistant then
e.assistant:activity()end
end
local function k(n)if not n then
e.log(e.ERR,"Service not available")e.exit(e.HTTP_SERVICE_UNAVAILABLE,"Service not available. Probably too much session open. Wait 20 seconds and retry.")end
end
local function P(n,o)if not o then
for o,t in s(n.sessions)do
local n=u(n,t,o,now,timeout)if n then
e.log(e.ERR,"Removed session "..o)end
end
end
end
local function H(n,t,s)local e,o=v(n,t.remote)if not e then
e,o=w(n,t)elseif not s then
R(n,e)end
return e,o
end
local function x(n)e.ctx.session=n.proxy
end
local function R(s,n)if n and n=="1"then
local o=t(e.var.server_port)local n=t(e.var.request_uri)if o=="80"then
e.redirect(g("https://%s%s",s.http_host,n))end
end
end
function o:checkrequest(n)local e=T()R(e,self.force_https)local e,n=H(self,e,n)P(self,e)k(e)O(self,e,n)x(e)end
local function g(n,e,o)local t=n.users[o]local o=e.sessionid
e:changeUser(t)p(n,e,o)end
local function T(o,e,n)local e=o.users[e]if e and not n:isUserAllowedByIP(e)then
e=nil
end
return e
end
local function n(n)local e=d[n]if not e then
e={count=0,lockTime=0}d[n]=e
end
return e
end
local function x(e,t)local o=h(e.maxwrongattempt)or 15
local e=n(t)e.count=e.count+1
if e.count>o then
e.count=o
end
e.lockTime=i(a)end
local function v(e)d[e]=nil
end
local d={linear=function(t,n,o,e)return math.floor(t/e)*10+n-o
end,exponential=function(o,e,n)return(2^(o)+e)-n
end,}local function R(e)return d[e]or d.exponential
end
local function d(e,o)local e=n(e)return R(o.backOffMethod)(e.count,e.lockTime,i(a),o.backOffRepeat)end
local function R(e,o)if e.bruteforce=="1"then
local n=n(o)if n.count>0 then
if e.relaxfirstattempt=="1"and n.count==1 then
return
end
local e=d(o,e)if e>0 then
r('{ "error": { "msg":"%s","waitTime":"%d","wrongCount":"%s" }}',"failed",e,n.count)return true
end
end
end
end
local function p(e,o,t)if e.bruteforce=="1"then
x(e,o)local n=n(o).count or 0
if n>0 then
if e.relaxfirstattempt=="1"and n==1 then
return
end
local e=d(o,e)r('{ "error": { "msg":"%s","waitTime":"%s","wrongCount":"%s" }}',t or"failed",e,n)return true
end
end
end
function o:handleAuth()local l=e.var.uri
if l~=self.authpath and l~=self.passpath then
return
end
if e.req.get_method()~="POST"then
e.exit(e.HTTP_FORBIDDEN)end
e.header.content_type="application/json"local s=e.req.get_post_args()local n=I(self,e.ctx.session)local o,a,i=t(s.I),t(s.A),t(s.M)if o and a and n then
if n.verifier then
n.verifier:destroy()n.verifier=nil
end
n.changeAllowed=nil
local t=T(self,o,n)if not t then
e.log(e.ERR,c.format("Failed logon attempt for user %s",s.I))e.print('{ "error":"failed" }')else
if not R(self,o)then
local s
n.verifier,s=N.Verifier(o,t.srp_salt,t.srp_verifier,a)if not s then
e.print('{ "error":"invalid arguments" }')else
r('{ "s":"%s", "B":"%s" }',t.srp_salt,s)end
end
end
e.exit(e.HTTP_OK)end
if i and n then
local o=n.verifier
if not o then
e.exit(e.HTTP_BAD_REQUEST)end
local s,t=o:verify(i)if s then
g(self,n,o:username())if l==self.passpath then
n.changeAllowed=true
end
v(o:username())r('{ "M":"%s" }',s)else
if not p(self,o:username(),t)then
e.log(e.ERR,"Failed logon attempt for user "..o:username())r('{ "error":"%s" }',t or"failed")end
end
o:destroy()n.verifier=nil
e.exit(e.HTTP_OK)end
local o,t,s=s.salt,s.verifier,s.cryptedpassword
if n and n.changeAllowed and o and t then
n.changeAllowed=nil
local t,o=self.sessioncontrol.changePassword(n.user,o,t,s)if t then
r('{ "success":"true" }')e.exit(e.HTTP_OK)end
e.log(e.ERR,"user '",n.user.name,"' could not be updated: ",o or"(unknown reason)")e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
e.exit(e.HTTP_BAD_REQUEST)end
function o:cleanup()local l=self.timeout
local n=0
local e
local o=i(a)for t,s in s(self.sessions)do
local t,o=u(self,s,t,o,l)if not t then
n=n+1
if(not e)or(o<e)then
e=o
end
end
end
return n,e
end
function o:invalidateSessions()for n,e in s(self.sessions)do
e:logout()_(self,n)end
end
function o:addUser(t)self.sessioncontrol.reloadUsers()local e=self.sessioncontrol.getuser(t)if not e or not e.name then
return nil,"Invalid user instance: "..b(t)end
if self.users[e.name]then
return nil,"A user with this username already exists"else
local o="uci.web.sessionmgr.@"..self.name..".users."local n=l.add(o)if n then
local s=o.."@"..n..".value"local t=l.set(s,t)if t then
self.users[e.name]=e
return true
else
l.del(o.."@"..n..".")return nil,"Unable to set user's value"end
else
return nil,"Unable to add a user to list of allowed users"end
end
end
function o:delUser(o)local e
for t,n in s(self.users)do
if n.sectionname==o then
e=n.name
end
end
if e then
local n="uci.web.sessionmgr.@"..self.name..".users."local n=l.get(n)if n then
for t,n in f(n)do
if n.value==o then
local n=l.del(n.path)if n then
self.users[e]=nil
return true
else
return nil,"Error while removing user"end
end
end
return nil,"Could not find user in datamodel"else
return nil,"Users list does not exist"end
else
return nil,"Could not find user in session manager"end
end
function o:reloadUsers()local e={}for o,n in s(self.users)do
e[#e+1]=n.sectionname
end
local n={}for o,e in f(e)do
local e=self.sessioncontrol.getuser(e)if e then
n[e.name]=e
end
end
self.users=n
end
function o:getExternalAddress()local e=self.assistant
if e and e:enabled()then
return e:getExternalAddress()else
return nil,self.public_port
end
end
local function t(e,n)local e=h(e)or n
if e>0 then
return e
elseif e==-1 then
return math.huge
end
return n
end
local t={ruleset=function(t,s,o,n)local n=n.getruleset(o)if not n then
e.log(e.ERR,"no config found for ruleset ",o)e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
t.ruleset=n
end,users=function(t,o,n,l)for o in s(n)do
local n=l.getuser(o)if not n then
e.log(e.ERR,"no config found for user ",o)e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
t.users[n.name]=n
end
end,default_user=function(t,s,n,o)if n~=""then
local o=o.getuser(n)if not o then
e.log(e.ERR,"no config found for default user ",n)e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
t[s]=o
end
end,maxsessions=function(e,o,n)e.maxsessions=t(n,e.maxsessions)end,maxsessions_per_ip=function(e,o,n)e.maxsessions_per_ip=t(n,e.maxsessions_per_ip)end,_default=function(o,n,e)if e~=""then
o[n]=e
end
end}local function r(n,o)if m(o)~="table"then
return
end
n.ruleset=nil
n.users={}local l=n.sessioncontrol
for e,s in s(o)do
local o=t[e]or t._default
o(n,e,s,l)end
if n.default_user and not n.users[n.default_user.name]then
e.log(e.ERR,"user list does not contain default user ",o.default_user)e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
end
function o:reloadConfig(e)r(self,e)end
function o:setDefaultUser(e)local n=e and e.sectionname or""local n,o=l.set("uci.web.sessionmgr.@"..self.name..".default_user",n)if not n then
return nil,o[1].errmsg
end
self.default_user=e
return true
end
function o:getUserCount()local e=0
for o,n in s(self.sessions)do
if n:getusername()~=""and not n:isdefaultuser()then
e=e+1
end
end
return e
end
local s={}function s.new(t,s,n)local n=A({name=t,sessioncontrol=n,sessions={},users={},ruleset={},maxsessions=50,maxsessions_per_ip=10,},o)r(n,s)local o=h(n.timeout)if not o or o<=0 then
e.log(e.ERR,"invalid timeout for sessionmgr ",t)e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
n.timeout=o*60
if(not n.default_user or n.default_user=="")and(not n.loginpath or n.loginpath=="")then
e.log(e.ERR,"must set the login page for sessionmgr ",t)e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
if not n.cookiepath or not n.authpath or not n.passpath or n.cookiepath==""or n.authpath==""or n.passpath==""then
e.log(e.ERR,"cookiepath, authpath or passpath not given for sessionmgr ",t)e.exit(e.HTTP_INTERNAL_SERVER_ERROR)end
n._session_tracker=E()return n
end
return s