parsav  Diff

Differences From Artifact [0360541ecf]:

To Artifact [2e402a5b93]:


    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