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