parsav  Diff

Differences From Artifact [aed7239c9c]:

To Artifact [ed3d5ec62e]:


     1      1   -- vim: ft=terra
     2      2   local util = dofile 'common.lua'
            3  +
            4  +local struct srv
            5  +local struct cfgcache {
            6  +	secret: lib.mem.ptr(int8)
            7  +	instance: lib.mem.ptr(int8)
            8  +	overlord: &srv
            9  +}
     3     10   local struct srv {
     4     11   	sources: lib.mem.ptr(lib.store.source)
     5     12   	webmgr: lib.net.mg_mgr
     6     13   	webcon: &lib.net.mg_connection
           14  +	cfg: cfgcache
     7     15   }
     8     16   
     9         -local handle = {
    10         -	http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
    11         -		switch event do
    12         -			case lib.net.MG_EV_HTTP_MSG then
    13         -				lib.dbg('routing HTTP request')
    14         -				var msg = [&lib.net.mg_http_message](p)
    15         -
    16         -			end
    17         -		end
    18         -	end;
    19         -}
    20         -local char = macro(function(ch) return `[string.byte(ch:asvalue())] end)
    21         -local terra cfg(s: &srv, befile: rawstring)
    22         -	lib.report('configuring backends from ', befile)
    23         -
    24         -	var fr = lib.file.open(befile, [lib.file.mode.read])
    25         -	if fr.ok == false then
    26         -		lib.bail('could not open configuration file ', befile)
    27         -	end
    28         -
    29         -	var f = fr.val
    30         -	var c: lib.mem.vec(lib.store.source) c:init(8)
    31         -	var text: lib.str.acc text:init(64)
    32         -	do var buf: int8[64]
    33         -		while true do
    34         -			var ct = f:read(buf, [buf.type.N])
    35         -			if ct == 0 then break end
    36         -			text:push(buf, ct)
    37         -		end
    38         -	end
    39         -	f:close()
    40         -
    41         -	var cur = text.buf
    42         -	var segs: tuple(&int8, &int8)[3] = array(
    43         -		{[&int8](0),[&int8](0)},
    44         -		{[&int8](0),[&int8](0)},
    45         -		{[&int8](0),[&int8](0)}
    46         -	)
    47         -	var segdup = [terra(s: {rawstring, rawstring})
    48         -		var sz = s._1 - s._0
    49         -		var str = s._0
    50         -		return [lib.mem.ptr(int8)] {
    51         -			ptr = lib.str.ndup(str, sz);
    52         -			ct = sz;
    53         -		}
    54         -	end]
    55         -	var fld = 0
    56         -	while (cur - text.buf) < text.sz do
    57         -		if segs[fld]._0 == nil then
    58         -			if not (@cur == char(' ') or @cur == char('\t') or @cur == char('\n')) then
    59         -				segs[fld] = {cur, nil}
    60         -			end
    61         -		else
    62         -			if fld < 2 and @cur == char(' ') or @cur == char('\t') then
    63         -				segs[fld]._1 = cur
    64         -				fld = fld + 1
    65         -				segs[fld] = {nil, nil}
    66         -			elseif @cur == char('\n') or cur == text.buf + (text.sz-1) then
    67         -				if fld < 2 then lib.bail('incomplete backend line in ', befile) else
    68         -					segs[fld]._1 = cur
    69         -					var src = c:new()
    70         -					src.id = segdup(segs[0])
    71         -					src.string = segdup(segs[2])
    72         -					src.backend = nil
    73         -					for i = 0,[lib.store.backends.type.N] do
    74         -						if lib.str.ncmp(segs[1]._0, lib.store.backends[i].id, segs[1]._1 - segs[1]._0) == 0 then
    75         -							src.backend = &lib.store.backends[i]
    76         -							break
    77         -						end
    78         -					end
    79         -					if src.backend == nil then
    80         -						lib.bail('unknown backend in ', befile)
    81         -					end
    82         -					src.handle = nil
    83         -					fld = 0
    84         -					segs[0] = {nil, nil}
    85         -				end
    86         -			end
    87         -		end
    88         -		cur = cur + 1
    89         -	end
    90         -	text:free()
    91         -
    92         -	s.sources = c:crush()
           17  +terra cfgcache:free() -- :/
           18  +	self.secret:free()
           19  +	self.instance:free()
    93     20   end
    94     21   
    95         ---srv.methods.conf_set = terra(self: &srv, key: rawstring, val:rawstring)
    96         ---	self.sources.ptr[0]:conf_set(key, val)
    97         ---end
    98         -
    99         -terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring)
   100         -	var cs: lib.store.credset cs:clear()
   101         -	for i=0,self.sources.ct do
   102         -		var set: lib.store.credset = self.sources.ptr[i]:actor_auth_how(ip, usn)
   103         -		cs = cs + set
   104         -	end
   105         -	return cs
   106         -end
   107     22   srv.metamethods.__methodmissing = macro(function(meth, self, ...)
   108     23   	local primary, ptr, stat, simple, oid = 0,1,2,3,4
   109     24   	local tk, rt = primary
   110     25   	local expr = {...}
   111     26   	for _,f in pairs(lib.store.backend.entries) do
   112     27   		local fn = f.field or f[1]
   113     28   		local ft = f.type or f[2]
................................................................................
   146     61   					if [ok] then break
   147     62   						else r = empty end
   148     63   				end
   149     64   			end
   150     65   		in r end
   151     66   	end
   152     67   end)
           68  +
           69  +local struct convo {
           70  +	srv: &srv
           71  +	con: &lib.net.mg_connection
           72  +	msg: &lib.net.mg_http_message
           73  +	aid: uint64 -- 0 if logged out
           74  +	who: &lib.store.actor -- who we're logged in as, if aid ~= 0
           75  +}
           76  +
           77  +-- this is unfortunately necessary to work around a terra bug
           78  +-- it can't seem to handle forward-declarations of structs in C
           79  +
           80  +local getpeer
           81  +do local struct strucheader {
           82  +		next: &lib.net.mg_connection
           83  +		mgr: &lib.net.mg_mgr
           84  +		peer: lib.net.mg_addr
           85  +	}
           86  +	terra getpeer(con: &lib.net.mg_connection)
           87  +		return [&strucheader](con).peer
           88  +	end
           89  +end
           90  +
           91  +terra convo:complain(code: uint16, title: rawstring, msg: rawstring)
           92  +	var hdrs = array(lib.http.header { key = 'Content-Type', value = 'text/html; charset=UTF-8' })
           93  +
           94  +	var ti: lib.str.acc ti:compose('error :: ', title) defer ti:free()
           95  +	var body = data.view.docskel {
           96  +		instance = self.srv.cfg.instance.ptr;
           97  +		title = ti.buf;
           98  +		body = msg;
           99  +		class = 'error';
          100  +	}
          101  +
          102  +	if body.body == nil then
          103  +		body.body = "i'm sorry, dave. i can't let you do that"
          104  +	end
          105  +
          106  +	body:send(self.con, code, [lib.mem.ptr(lib.http.header)] {
          107  +		ptr = &hdrs[0], ct = [hdrs.type.N]
          108  +	})
          109  +end
          110  +
          111  +local urimatch = macro(function(uri, ptn)
          112  +	return `lib.net.mg_globmatch(ptn, [#ptn], uri.ptr, uri.ct+1)
          113  +end)
          114  +
          115  +local route = {} -- these are defined in route.t, as they need access to renderers
          116  +terra route.dispatch_http ::  {&convo, lib.mem.ptr(int8), lib.http.method.t} -> {}
          117  +
          118  +local handle = {
          119  +	http = terra(con: &lib.net.mg_connection, event: int, p: &opaque, ext: &opaque)
          120  +		var server = [&srv](ext)
          121  +		var mgpeer = getpeer(con)
          122  +		var peer = lib.store.inet { port = mgpeer.port; }
          123  +		if mgpeer.is_ip6 then peer.pv = 6 else peer.pv = 4 end
          124  +		if peer.pv == 6 then
          125  +			for i = 0, 16 do peer.v6[i] = mgpeer.ip6[i] end
          126  +		else -- v4
          127  +			@[&uint32](&peer.v4) = mgpeer.ip
          128  +		end
          129  +		-- the peer property is currently broken and there is precious
          130  +		-- little i can do about this -- it always reports a peer v4 IP
          131  +		-- of 0.0.0.0, altho the port seems to come through correctly.
          132  +		-- for now i'm leaving it as is, but note that netmask restrictions
          133  +		-- WILL NOT WORK until upstream gets its shit together. FIXME
          134  +
          135  +		switch event do
          136  +			case lib.net.MG_EV_HTTP_MSG then
          137  +				lib.dbg('routing HTTP request')
          138  +				var msg = [&lib.net.mg_http_message](p)
          139  +				var co = convo {
          140  +					con = con, srv = server, msg = msg;
          141  +					aid = 0, who = nil;
          142  +				}
          143  +
          144  +				-- we need to check if there's any cookies sent with the request,
          145  +				-- and if so, whether they contain any credentials. this will be
          146  +				-- used to set the auth parameters in the http conversation
          147  +				var cookies_p = lib.http.findheader(msg, 'Cookie')
          148  +				if cookies_p ~= nil then
          149  +					var cookies = cookies_p.ptr
          150  +					var key = [lib.mem.ref(int8)] {ptr = cookies, ct = 0}
          151  +					var val = [lib.mem.ref(int8)] {ptr = nil, ct = 0}
          152  +					var i = 0 while i < cookies_p.ct    and
          153  +					                cookies[i] ~= 0     and
          154  +					                cookies[i] ~= @'\r' and
          155  +									cookies[i] ~= @'\n' do -- cover all the bases
          156  +						if val.ptr == nil then
          157  +							if cookies[i] == @'=' then
          158  +								key.ct = (cookies + i) - key.ptr
          159  +								val.ptr = cookies + i + 1
          160  +							end
          161  +							i = i + 1
          162  +						else
          163  +							if cookies[i] == @';' then
          164  +								val.ct = (cookies + i) - val.ptr
          165  +								if lib.str.ncmp(key.ptr, 'auth', key.ct) == 0 then
          166  +									goto foundcookie
          167  +								end
          168  +
          169  +								i = i + 1
          170  +								i = lib.str.ffw(cookies + i, cookies_p.ct - i) - cookies
          171  +								key.ptr = cookies + i
          172  +								val.ptr = nil
          173  +							else i = i + 1 end
          174  +						end
          175  +					end
          176  +					if val.ptr == nil then goto nocookie end
          177  +					val.ct = (cookies + i) - val.ptr
          178  +					if lib.str.ncmp(key.ptr, 'auth', key.ct) ~= 0 then
          179  +						goto nocookie
          180  +					end
          181  +					::foundcookie:: do
          182  +						var aid = lib.session.cookie_interpret(server.cfg.secret,
          183  +							[lib.mem.ptr(int8)]{ptr=val.ptr,ct=val.ct},
          184  +							lib.osclock.time(nil))
          185  +						if aid ~= 0 then co.aid = aid end
          186  +					end ::nocookie::;
          187  +				end
          188  +
          189  +				if co.aid ~= 0 then
          190  +					var sess, usr = co.srv:actor_session_fetch(co.aid, peer)
          191  +					if sess.ok == false then co.aid = 0 else co.who = usr.ptr end
          192  +				end
          193  +
          194  +				var uridec = lib.mem.heapa(int8, msg.uri.len) defer uridec:free()
          195  +				var urideclen = lib.net.mg_url_decode(msg.uri.ptr, msg.uri.len, uridec.ptr, uridec.ct, 1)
          196  +
          197  +				var uri = uridec
          198  +				if urideclen == -1 then
          199  +					for i = 0,msg.uri.len do
          200  +						if msg.uri.ptr[i] == @'+'
          201  +							then uri.ptr[i] = @' '
          202  +							else uri.ptr[i] = msg.uri.ptr[i]
          203  +						end
          204  +					end
          205  +					uri.ct = msg.uri.len
          206  +				else uri.ct = urideclen end
          207  +				lib.dbg('routing URI ', {uri.ptr, uri.ct})
          208  +				
          209  +				if lib.str.ncmp('GET', msg.method.ptr, msg.method.len) == 0 then
          210  +					route.dispatch_http(&co, uri, [lib.http.method.get])
          211  +				else
          212  +					co:complain(400,'unknown method','you have submitted an invalid http request')
          213  +				end
          214  +
          215  +				if co.aid ~= 0 then lib.mem.heapf(co.who) end
          216  +			end
          217  +		end
          218  +	end;
          219  +}
          220  +
          221  +local terra cfg(s: &srv, befile: rawstring)
          222  +	lib.report('configuring backends from ', befile)
          223  +
          224  +	var fr = lib.file.open(befile, [lib.file.mode.read])
          225  +	if fr.ok == false then
          226  +		lib.bail('could not open configuration file ', befile)
          227  +	end
          228  +
          229  +	var f = fr.val
          230  +	var c: lib.mem.vec(lib.store.source) c:init(8)
          231  +	var text: lib.str.acc text:init(64)
          232  +	do var buf: int8[64]
          233  +		while true do
          234  +			var ct = f:read(buf, [buf.type.N])
          235  +			if ct == 0 then break end
          236  +			text:push(buf, ct)
          237  +		end
          238  +	end
          239  +	f:close()
          240  +
          241  +	var cur = text.buf
          242  +	var segs: tuple(&int8, &int8)[3] = array(
          243  +		{[&int8](0),[&int8](0)},
          244  +		{[&int8](0),[&int8](0)},
          245  +		{[&int8](0),[&int8](0)}
          246  +	)
          247  +	var segdup = [terra(s: {rawstring, rawstring})
          248  +		var sz = s._1 - s._0
          249  +		var str = s._0
          250  +		return [lib.mem.ptr(int8)] {
          251  +			ptr = lib.str.ndup(str, sz);
          252  +			ct = sz;
          253  +		}
          254  +	end]
          255  +	var fld = 0
          256  +	while (cur - text.buf) < text.sz do
          257  +		if segs[fld]._0 == nil then
          258  +			if not (@cur == @' ' or @cur == @'\t' or @cur == @'\n') then
          259  +				segs[fld] = {cur, nil}
          260  +			end
          261  +		else
          262  +			if fld < 2 and @cur == @' ' or @cur == @'\t' then
          263  +				segs[fld]._1 = cur
          264  +				fld = fld + 1
          265  +				segs[fld] = {nil, nil}
          266  +			elseif @cur == @'\n' or cur == text.buf + (text.sz-1) then
          267  +				if fld < 2 then lib.bail('incomplete backend line in ', befile) else
          268  +					segs[fld]._1 = cur
          269  +					var src = c:new()
          270  +					src.id = segdup(segs[0])
          271  +					src.string = segdup(segs[2])
          272  +					src.backend = nil
          273  +					for i = 0,[lib.store.backends.type.N] do
          274  +						if lib.str.ncmp(segs[1]._0, lib.store.backends[i].id, segs[1]._1 - segs[1]._0) == 0 then
          275  +							src.backend = &lib.store.backends[i]
          276  +							break
          277  +						end
          278  +					end
          279  +					if src.backend == nil then
          280  +						lib.bail('unknown backend in ', befile)
          281  +					end
          282  +					src.handle = nil
          283  +					fld = 0
          284  +					segs[0] = {nil, nil}
          285  +				end
          286  +			end
          287  +		end
          288  +		cur = cur + 1
          289  +	end
          290  +	text:free()
          291  +
          292  +	if c.sz > 0 then
          293  +		s.sources = c:crush()
          294  +	else
          295  +		s.sources.ptr = nil
          296  +		s.sources.ct = 0
          297  +	end
          298  +end
          299  +
          300  +terra srv:actor_auth_how(ip: lib.store.inet, usn: rawstring)
          301  +	var cs: lib.store.credset cs:clear()
          302  +	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
          305  +	end
          306  +	return cs
          307  +end
          308  +
          309  +terra cfgcache.methods.load :: {&cfgcache} -> {}
          310  +terra cfgcache:init(o: &srv)
          311  +	self.overlord = o
          312  +	self:load()
          313  +end
   153    314   
   154    315   srv.methods.start = terra(self: &srv, befile: rawstring)
   155    316   	cfg(self, befile)
   156    317   	var success = false
          318  +	if self.sources.ct == 0 then lib.bail('no data sources specified') end
   157    319   	for i=0,self.sources.ct do var src = self.sources.ptr + i
   158    320   		lib.report('opening data source ', src.id.ptr, '(', src.backend.id, ')')
   159    321   		src.handle = src.backend.open(src)
   160    322   		if src.handle ~= nil then success = true end
   161    323   	end
   162    324   	if not success then
   163    325   		lib.bail('could not connect to any data sources!')
   164    326   	end
          327  +
          328  +	self.cfg:init(self)
   165    329   
   166    330   	var dbbind = self:conf_get('bind')
   167    331   	var envbind = lib.proc.getenv('parsav_bind')
   168    332   	var bind: rawstring
   169    333   	if envbind ~= nil then
   170    334   		bind = envbind
   171    335   	elseif dbbind.ptr ~= nil then
   172    336   		bind = dbbind.ptr
   173    337   	else bind = '[::]:10917' end
   174    338   
   175    339   	lib.report('binding to ', bind)
   176    340   	lib.net.mg_mgr_init(&self.webmgr)
   177         -	self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, nil)
          341  +	self.webcon = lib.net.mg_http_listen(&self.webmgr, bind, handle.http, self)
   178    342   
          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)
   179    349   
   180    350   	if dbbind.ptr ~= nil then dbbind:free() end
   181    351   end
   182    352   
   183    353   srv.methods.poll = terra(self: &srv)
   184    354   	lib.net.mg_mgr_poll(&self.webmgr,1000)
   185    355   end
................................................................................
   189    359   	for i=0,self.sources.ct do var src = self.sources.ptr + i
   190    360   		lib.report('closing data source ', src.id.ptr, '(', src.backend.id, ')')
   191    361   		src:close()
   192    362   	end
   193    363   	self.sources:free()
   194    364   end
   195    365   
   196         -return srv
          366  +terra cfgcache:load()
          367  +	self.instance = self.overlord:conf_get('instance-name')
          368  +	self.secret = self.overlord:conf_get('server-secret')
          369  +end
          370  +
          371  +return {
          372  +	overlord = srv;
          373  +	convo = convo;
          374  +	route = route;
          375  +}