Differences From
Artifact [0360541ecf]:
31 31 where id = $1::bigint
32 32 ]];
33 33 };
34 34
35 35 actor_fetch_xid = {
36 36 params = {rawstring}, sql = [[
37 37 select a.id, a.nym, a.handle, a.origin,
38 - a.bio, a.rank, a.quota, a.key,
38 + a.bio, a.rank, a.quota, a.key, $1::text,
39 39
40 40 coalesce(s.domain,
41 41 (select value from parsav_config
42 42 where key='domain' limit 1)) as domain
43 43
44 44 from parsav_actors as a
45 45 left join parsav_servers as s
................................................................................
46 46 on a.origin = s.id
47 47
48 48 where $1::text = (a.handle || '@' || domain) or
49 49 $1::text = ('@' || a.handle || '@' || domain) or
50 50 (a.origin is null and $1::text = ('@' || a.handle))
51 51 ]];
52 52 };
53 +
54 + actor_enum_local = {
55 + params = {}, sql = [[
56 + select id, nym, handle, origin,
57 + bio, rank, quota, key,
58 + handle ||'@'||
59 + (select value from parsav_config
60 + where key='domain' limit 1) as xid
61 + from parsav_actors where origin is null
62 + ]];
63 + };
64 +
65 + actor_enum = {
66 + params = {}, sql = [[
67 + select a.id, a.nym, a.handle, a.origin,
68 + a.bio, a.rank, a.quota, a.key,
69 + a.handle ||'@'||
70 + coalesce(s.domain,
71 + (select value from parsav_config
72 + where key='domain' limit 1)) as xid
73 + from parsav_actors a
74 + left join parsav_servers s on s.id = a.origin
75 + ]];
76 + };
53 77 }
54 78
55 79 local struct pqr {
56 80 sz: intptr
57 81 res: &lib.pq.PGresult
58 82 }
59 83 terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end
60 84 terra pqr:null(row: intptr, col: intptr)
61 85 return (lib.pq.PQgetisnull(self.res, row, col) == 1)
62 86 end
63 -terra pqr:string(row: intptr, col: intptr)
87 +terra pqr:len(row: intptr, col: intptr)
88 + return lib.pq.PQgetlength(self.res, row, col)
89 +end
90 +terra pqr:cols() return lib.pq.PQnfields(self.res) end
91 +terra pqr:string(row: intptr, col: intptr) -- not to be exported!!
92 + var v = lib.pq.PQgetvalue(self.res, row, col)
93 +-- var r: lib.mem.ptr(int8)
94 +-- r.ct = lib.str.sz(v)
95 +-- r.ptr = v
96 + return v
97 +end
98 +terra pqr:bin(row: intptr, col: intptr) -- not to be exported!! DO NOT FREE
99 + return [lib.mem.ptr(uint8)] {
100 + ptr = [&uint8](lib.pq.PQgetvalue(self.res, row, col));
101 + ct = lib.pq.PQgetlength(self.res, row, col);
102 + }
103 +end
104 +terra pqr:String(row: intptr, col: intptr) -- suitable to be exported
105 + var s = [lib.mem.ptr(int8)] { ptr = lib.str.dup(self:string(row,col)) }
106 + s.ct = lib.pq.PQgetlength(self.res, row, col)
107 + return s
108 +end
109 +terra pqr:bool(row: intptr, col: intptr)
110 + var v = lib.pq.PQgetvalue(self.res, row, col)
111 + if @v == 0x01 then return true else return false end
112 +end
113 +terra pqr:cidr(row: intptr, col: intptr)
64 114 var v = lib.pq.PQgetvalue(self.res, row, col)
65 - var r: lib.mem.ptr(int8)
66 - r.ct = lib.str.sz(v)
67 - r.ptr = lib.str.ndup(v, r.ct)
68 - return r
115 + var i: lib.store.inet
116 + if v[0] == 0x02 then i.pv = 4
117 + elseif v[0] == 0x03 then i.pv = 6
118 + else lib.bail('invalid CIDR type in stream') end
119 + i.fixbits = v[1]
120 + if v[2] ~= 0x1 then lib.bail('expected CIDR but got inet from stream') end
121 + if i.pv == 4 and v[3] ~= 0x04 or i.pv == 6 and v[3] ~= 0x10 then
122 + lib.bail('CIDR failed length sanity check') end
123 +
124 + var sz: intptr if i.pv == 4 then sz = 4 else sz = 16 end
125 + for j=0,sz do i.v6[j] = v[4 + j] end -- 😬
126 + return i
69 127 end
70 128 pqr.methods.int = macro(function(self, ty, row, col)
71 129 return quote
72 130 var i: ty:astype()
73 131 var v = lib.pq.PQgetvalue(self.res, row, col)
74 132 lib.math.netswap_ip(ty, v, &i)
75 133 in i end
76 134 end)
135 +
136 +local pqt = {
137 + [lib.store.inet] = function(cidr)
138 + local tycode = cidr and 0x01 or 0x00
139 + return terra(i: lib.store.inet, buf: &uint8)
140 + var sz: intptr
141 + if i.pv == 4 then sz = 4 else sz = 16 end
142 + if buf == nil then buf = [&uint8](lib.mem.heapa_raw(sz + 4)) end
143 + if i.pv == 4 then buf[0] = 0x02
144 + elseif i.pv == 6 then buf[0] = 0x03 end
145 + if cidr then -- our local 'inet' is not quite orthogonal to the
146 + -- postgres inet type; tweak it to match (ignore port)
147 + buf[1] = i.fixbits
148 + elseif i.pv == 6 then buf[1] = 128
149 + else buf[1] = 32 end
150 + buf[2] = tycode
151 + buf[3] = sz
152 + for j=0,sz do buf[4 + j] = i.v6[j] end -- 😬
153 + return buf
154 + end
155 + end;
156 +}
77 157
78 158 local con = symbol(&lib.pq.PGconn)
79 159 local prep = {}
160 +local sqlsquash = function(s) return s:gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1') end
80 161 for k,q in pairs(queries) do
81 - local qt = (q.sql):gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1')
162 + local qt = sqlsquash(q.sql)
82 163 local stmt = 'parsavpg_' .. k
83 164 prep[#prep + 1] = quote
84 165 var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil)
85 166 defer lib.pq.PQclear(res)
86 167 if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then
87 168 if res == nil then
88 169 lib.bail('grievous error occurred preparing ',k,' statement')
................................................................................
105 186 fixers[#fixers + 1] = quote
106 187 --lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]])
107 188 [args[i]] = lib.math.netswap(ty, [args[i]])
108 189 end
109 190 end
110 191 end
111 192
112 - q.exec = terra(src: &lib.store.source, [args])
193 + terra q.exec(src: &lib.store.source, [args])
113 194 var params = arrayof([&int8], [casts])
114 195 var params_sz = arrayof(int, [counters])
115 196 var params_ft = arrayof(int, [ft])
116 197 [fixers]
117 198 var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
118 199 [#args], params, params_sz, params_ft, 1)
119 200 if res == nil then
................................................................................
129 210 return pqr {0, nil}
130 211 else
131 212 return pqr {ct, res}
132 213 end
133 214 end
134 215 end
135 216
136 -local terra row_to_actor(r: &pqr, row: intptr): lib.store.actor
137 - var a = lib.store.actor {
138 - id = r:int(uint64, row, 0);
139 - nym = r:string(row, 1);
140 - handle = r:string(row, 2);
141 - bio = r:string(row, 4);
142 - key = r:string(row, 7);
143 - rights = lib.store.rights_default();
144 - }
145 - a.rights.rank = r:int(uint16, 0, 5);
146 - a.rights.quota = r:int(uint32, 0, 6);
147 - if r:null(0,3) then a.origin = 0
148 - else a.origin = r:int(uint64,0,3) end
217 +local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
218 + var a: lib.mem.ptr(lib.store.actor)
219 + if r:cols() >= 8 then
220 + a = [ lib.str.encapsulate(lib.store.actor, {
221 + nym = {`r:string(row, 1); `r:len(row,1) + 1};
222 + handle = {`r:string(row, 2); `r:len(row,2) + 1};
223 + bio = {`r:string(row, 4); `r:len(row,4) + 1};
224 + xid = {`r:string(row, 8); `r:len(row,8) + 1};
225 + }) ]
226 + else
227 + a = [ lib.str.encapsulate(lib.store.actor, {
228 + nym = {`r:string(row, 1); `r:len(row,1) + 1};
229 + handle = {`r:string(row, 2); `r:len(row,2) + 1};
230 + bio = {`r:string(row, 4); `r:len(row,4) + 1};
231 + }) ]
232 + a.ptr.xid = nil
233 + end
234 + a.ptr.id = r:int(uint64, row, 0);
235 + a.ptr.rights = lib.store.rights_default();
236 + a.ptr.rights.rank = r:int(uint16, row, 5);
237 + a.ptr.rights.quota = r:int(uint32, row, 6);
238 + if r:null(row,7) then
239 + a.ptr.key.ct = 0 a.ptr.key.ptr = nil
240 + else
241 + a.ptr.key = r:bin(row,7)
242 + end
243 + if r:null(row,3) then a.ptr.origin = 0
244 + else a.ptr.origin = r:int(uint64,row,3) end
149 245 return a
150 246 end
247 +
248 +local checksha = function(hnd, query, hash, origin, username, pw)
249 + local inet_buf = symbol(uint8[4 + 16])
250 + local validate = function(kind, cred, credlen)
251 + return quote
252 + var osz: intptr if origin.pv == 4 then osz = 4 else osz = 16 end
253 + var formats = arrayof([int], 1,1,1,1)
254 + var params = arrayof([&int8], username, kind,
255 + [&int8](&cred), [&int8](&inet_buf))
256 + var lens = arrayof(int, lib.str.sz(username), [#kind], credlen, osz + 4)
257 + var res = lib.pq.PQexecParams([&lib.pq.PGconn](hnd), query, 4, nil,
258 + params, lens, formats, 1)
259 + if res == nil then
260 + lib.bail('grievous failure checking pwhash')
261 + elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
262 + lib.warn('pwhash query failed: ', lib.pq.PQresultErrorMessage(res), '\n', query)
263 + else
264 + var r = pqr {
265 + sz = lib.pq.PQntuples(res);
266 + res = res;
267 + }
268 + if r.sz > 0 then -- found a record! stop here
269 + var aid = r:int(uint64, 0,0)
270 + r:free()
271 + return aid
272 + end
273 + end
274 + end
275 + end
276 +
277 + local out = symbol(uint8[64])
278 + local vdrs = {}
279 +
280 + local alg = lib.md['MBEDTLS_MD_SHA' .. tostring(hash)]
281 + vdrs[#vdrs+1] = quote
282 + if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(alg),
283 + [&uint8](pw), lib.str.sz(pw), out) ~= 0 then
284 + lib.bail('hashing failure!')
285 + end
286 + [ validate(string.format('pw-sha%u', hash), out, hash / 8) ]
287 + end
288 +
289 + return quote
290 + lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)])
291 + var [inet_buf]
292 + [pqt[lib.store.inet](false)](origin, inet_buf)
293 + var [out]
294 + [vdrs]
295 + lib.dbg(['could not find password hash'])
296 + end
297 +end
151 298
152 299 local b = `lib.store.backend {
153 300 id = "pgsql";
154 301 open = [terra(src: &lib.store.source): &opaque
155 302 lib.report('connecting to postgres database: ', src.string.ptr)
156 303 var [con] = lib.pq.PQconnectdb(src.string.ptr)
157 304 if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
................................................................................
158 305 lib.warn('postgres backend connection failed')
159 306 lib.pq.PQfinish(con)
160 307 return nil
161 308 end
162 309 var res = lib.pq.PQexec(con, [[
163 310 select pg_catalog.set_config('search_path', 'public', false)
164 311 ]])
165 - if res ~= nil then defer lib.pq.PQclear(res) end
166 - if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
312 + if res == nil then
313 + lib.warn('critical failure to secure postgres connection')
314 + lib.pq.PQfinish(con)
315 + return nil
316 + end
317 +
318 + defer lib.pq.PQclear(res)
319 + if lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
167 320 lib.warn('failed to secure postgres connection')
168 321 lib.pq.PQfinish(con)
169 322 return nil
170 323 end
171 324
172 325 [prep]
173 326 return con
................................................................................
174 327 end];
175 328 close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];
176 329
177 330 conf_get = [terra(src: &lib.store.source, key: rawstring)
178 331 var r = queries.conf_get.exec(src, key)
179 332 if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
180 333 defer r:free()
181 - return r:string(0,0)
334 + return r:String(0,0)
182 335 end
183 336 end];
184 337 conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring)
185 338 queries.conf_set.exec(src, key, val):free() end];
186 339 conf_reset = [terra(src: &lib.store.source, key: rawstring)
187 340 queries.conf_reset.exec(src, key):free() end];
188 341
189 342 actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
190 343 var r = queries.actor_fetch_uid.exec(src, uid)
191 344 if r.sz == 0 then
192 - return [lib.stat(lib.store.actor)] { ok = false, error = 1}
193 - else
194 - defer r:free()
195 - var a = [lib.stat(lib.store.actor)] { ok = true }
196 - a.val = row_to_actor(&r, 0)
197 - a.val.source = src
345 + return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
346 + else defer r:free()
347 + var a = row_to_actor(&r, 0)
348 + a.ptr.source = src
198 349 return a
199 350 end
200 351 end];
352 +
353 + actor_enum = [terra(src: &lib.store.source)
354 + var r = queries.actor_enum.exec(src)
355 + if r.sz == 0 then
356 + return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil }
357 + else defer r:free()
358 + var mem = lib.mem.heapa([&lib.store.actor], r.sz)
359 + for i=0,r.sz do mem.ptr[i] = row_to_actor(&r, i).ptr end
360 + return [lib.mem.ptr(&lib.store.actor)] { ct = r.sz, ptr = mem.ptr }
361 + end
362 + end];
363 +
364 + actor_enum_local = [terra(src: &lib.store.source)
365 + var r = queries.actor_enum_local.exec(src)
366 + if r.sz == 0 then
367 + return [lib.mem.ptr(&lib.store.actor)] { ct = 0, ptr = nil }
368 + else defer r:free()
369 + var mem = lib.mem.heapa([&lib.store.actor], r.sz)
370 + for i=0,r.sz do mem.ptr[i] = row_to_actor(&r, i).ptr end
371 + return [lib.mem.ptr(&lib.store.actor)] { ct = r.sz, ptr = mem.ptr }
372 + end
373 + end];
374 +
375 + actor_auth_how = [terra(
376 + src: &lib.store.source,
377 + ip: lib.store.inet,
378 + username: rawstring
379 + )
380 + var authview = src:conf_get('auth-source') defer authview:free()
381 + var a: lib.str.acc defer a:free()
382 + a:compose('with mts as (select a.kind from ',authview,[' ' .. sqlsquash [[as a
383 + left join parsav_actors as u on u.id = a.uid
384 + where (a.uid is null or u.handle = $1::text or (
385 + a.uid = 0 and a.name = $1::text
386 + )) and
387 + (a.netmask is null or a.netmask >> $2::inet) and
388 + blacklist = false)
389 +
390 + select
391 + (select count(*) from mts where kind like 'pw-%') > 0,
392 + (select count(*) from mts where kind like 'otp-%') > 0,
393 + (select count(*) from mts where kind like 'challenge-%') > 0,
394 + (select count(*) from mts where kind = 'trust') > 0 ]]]) -- cheat
395 + var cs: lib.store.credset cs:clear();
396 + var ipbuf: int8[20]
397 + ;[pqt[lib.store.inet](false)](ip, [&uint8](&ipbuf))
398 + var ipbl: intptr if ip.pv == 4 then ipbl = 8 else ipbl = 20 end
399 + var params = arrayof(rawstring, username, [&int8](&ipbuf))
400 + var params_sz = arrayof(int, lib.str.sz(username), ipbl)
401 + var params_ft = arrayof(int, 1, 1)
402 + var res = lib.pq.PQexecParams([&lib.pq.PGconn](src.handle), a.buf, 2, nil,
403 + params, params_sz, params_ft, 1)
404 + if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
405 + if res == nil then
406 + lib.bail('grievous error occurred checking for auth methods')
407 + end
408 + lib.bail('could not get auth methods for user ',username,':\n',lib.pq.PQresultErrorMessage(res))
409 + end
410 + var r = pqr { res = res, sz = lib.pq.PQntuples(res) }
411 + if r.sz == 0 then return cs end -- just in case
412 + (cs.pw << r:bool(0,0))
413 + (cs.otp << r:bool(0,1))
414 + (cs.challenge << r:bool(0,2))
415 + (cs.trust << r:bool(0,3))
416 + lib.pq.PQclear(res)
417 + return cs
418 + end];
419 +
420 + actor_auth_pw = [terra(
421 + src: &lib.store.source,
422 + ip: lib.store.inet,
423 + username: rawstring,
424 + cred: rawstring
425 + )
426 + var authview = src:conf_get('auth-source') defer authview:free()
427 + var a: lib.str.acc defer a:free()
428 + a:compose('select a.aid from ',authview,[' ' .. sqlsquash [[as a
429 + left join parsav_actors as u on u.id = a.uid
430 + where (a.uid is null or u.handle = $1::text or (
431 + a.uid = 0 and a.name = $1::text
432 + )) and
433 + (a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
434 + (a.netmask is null or a.netmask >> $4::inet)
435 + order by blacklist desc limit 1]]])
436 +
437 + [ checksha(`src.handle, `a.buf, 256, ip, username, cred) ] -- most common
438 + [ checksha(`src.handle, `a.buf, 512, ip, username, cred) ] -- most secure
439 + [ checksha(`src.handle, `a.buf, 384, ip, username, cred) ] -- weird
440 + [ checksha(`src.handle, `a.buf, 224, ip, username, cred) ] -- weirdest
441 +
442 + -- TODO: check pbkdf2-hmac
443 + -- TODO: check OTP
444 + return 0
445 + end];
201 446 }
202 447
203 448 return b