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