Differences From
Artifact [aed7239c9c]:
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 +}