parsav  Diff

Differences From Artifact [ed3d5ec62e]:

To Artifact [afa0417e30]:


     1      1   -- vim: ft=terra
     2      2   local util = dofile 'common.lua'
     3         -
            3  +local secmode = lib.enum { 'public', 'private', 'lockdown', 'isolate' }
     4      4   local struct srv
     5      5   local struct cfgcache {
     6      6   	secret: lib.mem.ptr(int8)
     7      7   	instance: lib.mem.ptr(int8)
     8      8   	overlord: &srv
            9  +	pol_sec: secmode.t
           10  +	pol_reg: bool
     9     11   }
    10     12   local struct srv {
    11     13   	sources: lib.mem.ptr(lib.store.source)
    12     14   	webmgr: lib.net.mg_mgr
    13     15   	webcon: &lib.net.mg_connection
    14     16   	cfg: cfgcache
    15     17   }
................................................................................
    68     70   
    69     71   local struct convo {
    70     72   	srv: &srv
    71     73   	con: &lib.net.mg_connection
    72     74   	msg: &lib.net.mg_http_message
    73     75   	aid: uint64 -- 0 if logged out
    74     76   	who: &lib.store.actor -- who we're logged in as, if aid ~= 0
           77  +	peer: lib.store.inet
           78  +	reqtype: lib.http.mime.t -- negotiated content type
           79  +-- cache
           80  +	navbar: lib.mem.ptr(int8)
           81  +-- private
           82  +	varbuf: lib.mem.ptr(int8)
           83  +	vbofs: &int8
    75     84   }
    76     85   
    77     86   -- this is unfortunately necessary to work around a terra bug
    78     87   -- it can't seem to handle forward-declarations of structs in C
    79     88   
    80     89   local getpeer
    81     90   do local struct strucheader {
................................................................................
    84     93   		peer: lib.net.mg_addr
    85     94   	}
    86     95   	terra getpeer(con: &lib.net.mg_connection)
    87     96   		return [&strucheader](con).peer
    88     97   	end
    89     98   end
    90     99   
          100  +terra convo:reroute_cookie(dest: rawstring, cookie: rawstring)
          101  +	var hdrs = array(
          102  +		lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' },
          103  +		lib.http.header { key = 'Location',     value = dest },
          104  +		lib.http.header { key = 'Set-Cookie',   value = cookie }
          105  +	)
          106  +
          107  +	var body = data.view.docskel {
          108  +		instance = self.srv.cfg.instance.ptr;
          109  +		title = 'rerouting';
          110  +		body = 'you are being redirected';
          111  +		class = 'error';
          112  +		navlinks = '';
          113  +	}
          114  +
          115  +	body:send(self.con, 303, [lib.mem.ptr(lib.http.header)] {
          116  +		ptr = &hdrs[0], ct = [hdrs.type.N] - lib.trn(cookie == nil,1,0)
          117  +	})
          118  +end
          119  +
          120  +terra convo:reroute(dest: rawstring) self:reroute_cookie(dest,nil) end
          121  + 
    91    122   terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
    92    123   	var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' })
    93    124   
    94    125   	var ti: lib.str.acc ti:compose('error :: ', title) defer ti:free()
          126  +	var bo: lib.str.acc bo:compose('<div class="message"><img class="icon" src="/s/warn.webp"><h1>error</h1><p>',msg,'</p></div>') defer bo:free()
    95    127   	var body = data.view.docskel {
    96    128   		instance = self.srv.cfg.instance.ptr;
    97    129   		title = ti.buf;
    98         -		body = msg;
          130  +		body = bo.buf;
    99    131   		class = 'error';
          132  +		navlinks = lib.coalesce(self.navbar.ptr, '');
   100    133   	}
   101    134   
   102    135   	if body.body == nil then
   103    136   		body.body = "i'm sorry, dave. i can't let you do that"
   104    137   	end
   105    138   
   106    139   	body:send(self.con, code, [lib.mem.ptr(lib.http.header)] {
   107    140   		ptr = &hdrs[0], ct = [hdrs.type.N]
   108    141   	})
   109    142   end
          143  +
          144  +-- CALL ONLY ONCE PER VAR
          145  +terra convo:postv(name: rawstring)
          146  +	if self.varbuf.ptr == nil then
          147  +		self.varbuf = lib.mem.heapa(int8, self.msg.body.len + self.msg.query.len)
          148  +		self.vbofs = self.varbuf.ptr
          149  +	end
          150  +	var o = lib.net.mg_http_get_var(&self.msg.body, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
          151  +	if o > 0 then
          152  +		var r = self.vbofs
          153  +		self.vbofs = self.vbofs + o
          154  +		return r, o
          155  +	else return nil, 0 end
          156  +end
          157  +
          158  +terra convo:getv(name: rawstring)
          159  +	if self.varbuf.ptr == nil then
          160  +		self.varbuf = lib.mem.heapa(int8, self.msg.query.len + self.msg.body.len)
          161  +		self.vbofs = self.varbuf.ptr
          162  +	end
          163  +	var o = lib.net.mg_http_get_var(&self.msg.query, name, self.vbofs, self.varbuf.ct - (self.vbofs - self.varbuf.ptr))
          164  +	if o > 0 then
          165  +		var r = self.vbofs
          166  +		self.vbofs = self.vbofs + o
          167  +		return r, o
          168  +	else return nil, 0 end
          169  +end
   110    170   
   111    171   local urimatch = macro(function(uri, ptn)
   112    172   	return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1)
   113    173   end)
   114    174   
   115    175   local route = {} -- these are defined in route.t, as they need access to renderers
   116    176   terra route.dispatch_http ::  {&convo, lib.mem.ptr(int8), lib.http.method.t} -> {}
          177  +
          178  +local mimetypes = {
          179  +	{'html', 'text/html'};
          180  +	{'json', 'application/json'};
          181  +	{'mkdown', 'text/markdown'};
          182  +	{'text', 'text/plain'};
          183  +	{'ansi', 'text/x-ansi'};
          184  +}
          185  +
          186  +local mimevar = symbol(lib.mem.ref(int8))
          187  +local mimeneg = `lib.http.mime.none
          188  +
          189  +for i, t in ipairs(mimetypes) do
          190  +	local name, mime = t[1], t[2]
          191  +	mimeneg = quote
          192  +		var ret: lib.http.mime.t
          193  +		if lib.str.ncmp(mimevar.ptr, mime, lib.math.biggest(mimevar.ct, [#mime])) == 0 then
          194  +			ret = [lib.http.mime[name]]
          195  +		else ret = [mimeneg] end
          196  +	in ret end
          197  +end
   117    198   
   118    199   local handle = {
   119    200   	http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
   120    201   		var server = [&srv](ext)
   121    202   		var mgpeer = getpeer(con)
   122    203   		var peer = lib.store.inet { port = mgpeer.port; }
   123    204   		if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end
................................................................................
   128    209   		end
   129    210   		-- the peer property is currently broken and there is precious
   130    211   		-- little i can do about this -- it always reports a peer v4 IP
   131    212   		-- of 0.0.0.0, altho the port seems to come through correctly.
   132    213   		-- for now i'm leaving it as is, but note that netmask restrictions
   133    214   		-- WILL NOT WORK until upstream gets its shit together. FIXME
   134    215   
          216  +		-- needs to check for an X-Forwarded-For header from nginx and
          217  +		-- use that instead of the peer iff peer is ::1/127.1 FIXME
          218  +		-- maybe also haproxy support?
          219  +
   135    220   		switch event do
   136    221   			case lib.net.MG_EV_HTTP_MSG then
   137    222   				lib.dbg('routing HTTP request')
   138    223   				var msg = [&lib.net.mg_http_message](p)
   139    224   				var co = convo {
   140    225   					con = con, srv = server, msg = msg;
   141         -					aid = 0, who = nil;
   142         -				}
          226  +					aid = 0, who = nil, peer = peer;
          227  +					reqtype = lib.http.mime.none;
          228  +				} co.varbuf.ptr = nil
          229  +				  co.navbar.ptr = nil
          230  +
          231  +				-- first, check for an accept header. if it's there, we need to
          232  +				-- iterate over the values and pick the highest-priority one
          233  +				do var acc = lib.http.findheader(msg, 'Accept')
          234  +					-- TODO handle q-value
          235  +					if acc.ptr ~= nil then
          236  +						var [mimevar] = [lib.mem.ref(int8)] { ptr = acc.ptr }
          237  +						var i = 0 while i < acc.ct do
          238  +							if acc.ptr[i] == @',' or acc.ptr[i] == @';' then
          239  +								mimevar.ct = (acc.ptr+i) - mimevar.ptr
          240  +								var t = [mimeneg]
          241  +								if t ~= lib.http.mime.none then
          242  +									co.reqtype = t
          243  +									goto foundtype
          244  +								end
          245  +
          246  +								if acc.ptr[i] == @';' then -- fast-forward over q
          247  +									for j=i+1,acc.ct do i=j
          248  +										if acc.ptr[j] == @',' then break end
          249  +									end
          250  +								end
          251  +								
          252  +								while i < acc.ct and -- fast-forward over ws
          253  +									acc.ptr[i+1] == @' ' or
          254  +									acc.ptr[i+1] == @'\t'
          255  +								do i=i+1 end
          256  +
          257  +								mimevar.ptr = acc.ptr + i + 1
          258  +							end
          259  +							i=i+1
          260  +						end
          261  +						if co.reqtype == lib.http.mime.none then
          262  +							mimevar.ct = acc.ct - (mimevar.ptr - acc.ptr)
          263  +							co.reqtype = [mimeneg]
          264  +							if co.reqtype == lib.http.mime.none then
          265  +								co.reqtype = lib.http.mime.html
          266  +							end
          267  +						end
          268  +					else co.reqtype = lib.http.mime.html end
          269  +				::foundtype::end
   143    270   
   144    271   				-- we need to check if there's any cookies sent with the request,
   145    272   				-- and if so, whether they contain any credentials. this will be
   146    273   				-- used to set the auth parameters in the http conversation
   147    274   				var cookies_p = lib.http.findheader(msg, 'Cookie')
   148    275   				if cookies_p ~= nil then
   149    276   					var cookies = cookies_p.ptr
................................................................................
   158    285   								key.ct = (cookies + i) - key.ptr
   159    286   								val.ptr = cookies + i + 1
   160    287   							end
   161    288   							i = i + 1
   162    289   						else
   163    290   							if cookies[i] == @';' then
   164    291   								val.ct = (cookies + i) - val.ptr
   165         -								if lib.str.ncmp(key.ptr, 'auth', key.ct) == 0 then
          292  +								if lib.str.ncmp(key.ptr, lib.session.cookiename, lib.math.biggest([#lib.session.cookiename],key.ct)) == 0 then
   166    293   									goto foundcookie
   167    294   								end
   168    295   
   169    296   								i = i + 1
   170    297   								i = lib.str.ffw(cookies + i, cookies_p.ct - i) - cookies
   171    298   								key.ptr = cookies + i
   172    299   								val.ptr = nil
   173    300   							else i = i + 1 end
   174    301   						end
   175    302   					end
   176    303   					if val.ptr == nil then goto nocookie end
   177    304   					val.ct = (cookies + i) - val.ptr
   178         -					if lib.str.ncmp(key.ptr, 'auth', key.ct) ~= 0 then
          305  +					if lib.str.ncmp(key.ptr, lib.session.cookiename, lib.math.biggest([#lib.session.cookiename], key.ct)) ~= 0 then
   179    306   						goto nocookie
   180    307   					end
   181    308   					::foundcookie:: do
   182    309   						var aid = lib.session.cookie_interpret(server.cfg.secret,
   183    310   							[lib.mem.ptr(int8)]{ptr=val.ptr,ct=val.ct},
   184    311   							lib.osclock.time(nil))
   185    312   						if aid ~= 0 then co.aid = aid end
................................................................................
   204    331   					end
   205    332   					uri.ct = msg.uri.len
   206    333   				else uri.ct = urideclen end
   207    334   				lib.dbg('routing URI ', {uri.ptr, uri.ct})
   208    335   				
   209    336   				if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then
   210    337   					route.dispatch_http(&co, uri, [lib.http.method.get])
          338  +				elseif lib.str.ncmp('POST', msg.method.ptr, msg.method.len) == 0 then
          339  +					route.dispatch_http(&co, uri, [lib.http.method.post])
          340  +				elseif lib.str.ncmp('HEAD', msg.method.ptr, msg.method.len) == 0 then
          341  +					route.dispatch_http(&co, uri, [lib.http.method.head])
          342  +				elseif lib.str.ncmp('OPTIONS', msg.method.ptr, msg.method.len) == 0 then
          343  +					route.dispatch_http(&co, uri, [lib.http.method.options])
   211    344   				else
   212    345   					co:complain(400,'unknown method','you have submitted an invalid http request')
   213    346   				end
   214    347   
   215    348   				if co.aid ~= 0 then lib.mem.heapf(co.who) end
          349  +				if co.varbuf.ptr ~= nil then co.varbuf:free() end
          350  +				if co.navbar.ptr ~= nil then co.navbar:free() end
   216    351   			end
   217    352   		end
   218    353   	end;
   219    354   }
   220    355   
   221    356   local terra cfg(s: &srv, befile: rawstring)
   222    357   	lib.report('configuring backends from ', befile)
................................................................................
   292    427   	if c.sz > 0 then
   293    428   		s.sources = c:crush()
   294    429   	else
   295    430   		s.sources.ptr = nil
   296    431   		s.sources.ct = 0
   297    432   	end
   298    433   end
          434  +
          435  +terra srv:actor_stats(uid: uint64)
          436  +	var stats = lib.store.actor_stats {
          437  +		posts = 0, mutuals = 0;
          438  +		follows = 0, followers = 0;
          439  +	}
          440  +	for i=0,self.sources.ct do
          441  +		var s = self.sources.ptr[i]:actor_stats(uid)
          442  +		stats.posts     = stats.posts     + s.posts
          443  +		stats.mutuals   = stats.mutuals   + s.mutuals
          444  +		stats.followers = stats.followers + s.followers
          445  +		stats.follows   = stats.follows   + s.follows
          446  +	end
          447  +	return stats
          448  +end
   299    449   
   300    450   terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring)
   301    451   	var cs: lib.store.credset cs:clear()
          452  +	var ok = false
   302    453   	for i=0,self.sources.ct do
   303         -		var set: lib.store.credset = self.sources.ptr[i]:actor_auth_how(ip, usn)
   304         -		cs = cs + set
          454  +		var set, iok = self.sources.ptr[i]:actor_auth_how(ip, usn)
          455  +		if iok then
          456  +			cs = cs + set
          457  +			ok = iok
          458  +		end
   305    459   	end
   306         -	return cs
          460  +	return cs, ok
   307    461   end
   308    462   
   309    463   terra cfgcache.methods.load :: {&cfgcache} -> {}
   310    464   terra cfgcache:init(o: &srv)
   311    465   	self.overlord = o
   312    466   	self:load()
   313    467   end
................................................................................
   336    490   		bind = dbbind.ptr
   337    491   	else bind = '[::]:10917' end
   338    492   
   339    493   	lib.report('binding to ', bind)
   340    494   	lib.net.mg_mgr_init(&self.webmgr)
   341    495   	self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, self)
   342    496   
   343         -	var buf: int8[lib.session.maxlen]
   344         -	var len = lib.session.cookie_gen(self.cfg.secret, 9139084444658983115ULL, lib.osclock.time(nil), &buf[0])
   345         -	buf[len] = 0
   346         -	
   347         -	var authid = lib.session.cookie_interpret(self.cfg.secret, [lib.mem.ptr(int8)] {ptr=buf, ct=len}, lib.osclock.time(nil))
   348         -	lib.io.fmt('generated cookie %s -- got authid %llu\n', buf, authid)
   349         -
   350    497   	if dbbind.ptr ~= nil then dbbind:free() end
   351    498   end
   352    499   
   353    500   srv.methods.poll = terra(self: &srv)
   354    501   	lib.net.mg_mgr_poll(&self.webmgr,1000)
   355    502   end
   356    503   
................................................................................
   362    509   	end
   363    510   	self.sources:free()
   364    511   end
   365    512   
   366    513   terra cfgcache:load()
   367    514   	self.instance = self.overlord:conf_get('instance-name')
   368    515   	self.secret = self.overlord:conf_get('server-secret')
          516  +
          517  +	self.pol_reg = false
          518  +	var sreg = self.overlord:conf_get('policy-self-register')
          519  +	if sreg.ptr ~= nil then
          520  +		if lib.str.cmp(sreg.ptr, 'on') == 0
          521  +			then self.pol_reg = true
          522  +			else self.pol_reg = false
          523  +		end
          524  +	end
          525  +	sreg:free()
          526  +	
          527  +	self.pol_sec = secmode.lockdown
          528  +	var smode = self.overlord:conf_get('policy-security')
          529  +	if smode.ptr ~= nil then
          530  +		if lib.str.cmp(smode.ptr, 'public') == 0 then
          531  +			self.pol_sec = secmode.public
          532  +		elseif lib.str.cmp(smode.ptr, 'private') == 0 then
          533  +			self.pol_sec = secmode.private
          534  +		elseif lib.str.cmp(smode.ptr, 'lockdown') == 0 then
          535  +			self.pol_sec = secmode.lockdown
          536  +		elseif lib.str.cmp(smode.ptr, 'isolate') == 0 then
          537  +			self.pol_sec = secmode.isolate
          538  +		end
          539  +	end
          540  +	smode:free()
   369    541   end
   370    542   
   371    543   return {
   372    544   	overlord = srv;
   373    545   	convo = convo;
   374    546   	route = route;
          547  +	secmode = secmode;
   375    548   }