parsav  Check-in [a64461061f]

Overview
Comment:add privilege control verbs
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: a64461061f49614172eea9ab72b4f428a5af9d34d6d4602125f343f3cf885bb3
User & Date: lexi on 2020-12-29 00:57:00
Other Links: manifest | tags
Context
2020-12-29
14:35
add ipc backbone check-in: 87731d4007 user: lexi tags: trunk
00:57
add privilege control verbs check-in: a64461061f user: lexi tags: trunk
2020-12-28
23:42
vastly improve the setup process check-in: d228cd7fcb user: lexi tags: trunk
Changes

Modified backend/pgsql.t from [0fdc39456b] to [f0f9593494].

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
...
198
199
200
201
202
203
204








205
206
207
208
209
210
211
...
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
...
568
569
570
571
572
573
574











































575
576
577
578
579
580
581
...
653
654
655
656
657
658
659

660
661
662
663
664
665
666
667
668
669
670

671
672
673
674
675
676
677
...
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
				to_timestamp($4::bigint),
				$5::bigint, $6::bigint, $7::bytea,
				$8::text, $9::smallint, $10::integer
			) returning id
		]];
	};


	actor_auth_pw = {
		params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
			select a.aid, a.uid, a.name from parsav_auth as a
				left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
................................................................................
	actor_power_insert = {
		params = {uint64,lib.mem.ptr(int8),uint16}, cmd = true, sql = [[
			insert into parsav_rights (actor, key, allow) values (
				$1::bigint, $2::text, ($3::smallint)::integer::bool
			)
		]]
	};









	auth_create_pw = {
		params = {uint64, lib.mem.ptr(uint8)}, cmd = true, sql = [[
			insert into parsav_auth (uid, name, kind, cred) values (
				$1::bigint,
				(select handle from parsav_actors where id = $1::bigint),
				'pw-sha256', $2::bytea
................................................................................
		a.ptr.key = r:bin(row,8)
	end
	if r:null(row,3) then a.ptr.origin = 0
	else a.ptr.origin = r:int(uint64,row,3) end
	return a
end

local privmap = {}
do local struct pt { name:pstring, priv:lib.store.powerset }
for k,v in pairs(lib.store.powerset.members) do
	privmap[#privmap + 1] = quote
		var ps: lib.store.powerset ps:clear()
		(ps.[v] << true)
	in pt {name = lib.str.plit(v), priv = ps} end
end end

local checksha = function(src, hash, origin, username, pw)
	local validate = function(kind, cred, credlen)
		return quote 
			var r = queries.actor_auth_pw.exec(
				[&lib.store.source](src),
				username,
................................................................................
		[vdrs]
		lib.dbg(['could not find password hash'])
	end
end

local schema = sqlsquash(lib.util.ingest('backend/schema/pgsql.sql'))
local obliterator = sqlsquash(lib.util.ingest('backend/schema/pgsql-drop.sql'))












































local b = `lib.store.backend {
	id = "pgsql";
	open = [terra(src: &lib.store.source): &opaque
		lib.report('connecting to postgres database: ', src.string.ptr)
		var [con] = lib.pq.PQconnectdb(src.string.ptr)
		if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
................................................................................
	
	actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
		var r = queries.actor_fetch_uid.exec(src, uid)
		if r.sz == 0 then
			return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)

			a.ptr.source = src
			return a
		end
	end];

	actor_fetch_xid = [terra(src: &lib.store.source, xid: lib.mem.ptr(int8))
		var r = queries.actor_fetch_xid.exec(src, xid)
		if r.sz == 0 then
			return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)

			a.ptr.source = src
			return a
		end
	end];

	actor_enum = [terra(src: &lib.store.source)
		var r = queries.actor_enum.exec(src)
................................................................................
		
		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
		for i=0,r.sz do ret.ptr[i] = row_to_post(&r, i) end -- MUST FREE ALL

		return ret
	end];

	actor_powers_fetch = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.store.powerset
		var powers = lib.store.rights_default().powers
		var map = array([privmap])
		var r = queries.actor_powers_fetch.exec(src, uid)

		for i=0, r.sz do
			for j=0, [map.type.N] do
				var pn = r:_string(i,0)
				if map[j].name:cmp(pn) then
					if r:bool(i,1)
						then powers = powers + map[j].priv
						else powers = powers - map[j].priv
					end
				end
			end
		end

		return powers
	end];

	actor_create = [terra(
		src: &lib.store.source,
		ac: &lib.store.actor
	): uint64
		var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.epithet, ac.rights.rank, ac.rights.quota)
		if r.sz == 0 then lib.bail('failed to create actor!') end
		var uid = r:int(uint64,0,0)

		-- check against default rights, insert records for wherever powers differ
		lib.dbg('created new actor, establishing powers')
		var pdef = lib.store.rights_default().powers
		var map = array([privmap])
		for i=0, [map.type.N] do
			var d = pdef and map[i].priv
			var u = ac.rights.powers and map[i].priv
			if d:sz() > 0 and u:sz() == 0 then
				lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct})
				queries.actor_power_insert.exec(src, uid, map[i].name, 0)
			elseif d:sz() == 0 and u:sz() > 0 then
				lib.dbg('granting power ', {map[i].name.ptr, map[i].name.ct})
				queries.actor_power_insert.exec(src, uid, map[i].name, 1)
			end
		end

		lib.dbg('powers established')
		return uid
	end];

	auth_create_pw = [terra(
		src: &lib.store.source,
		uid: uint64,
		reset: bool,
		pw: lib.mem.ptr(int8)







<







 







>
>
>
>
>
>
>
>







 







|
<
<
<
<
<
<
<







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>











>







 







|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







|



|
<
<
<
<
<
<
<
<
<
<
<
<


|







81
82
83
84
85
86
87

88
89
90
91
92
93
94
...
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
...
525
526
527
528
529
530
531
532







533
534
535
536
537
538
539
...
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
...
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
...
851
852
853
854
855
856
857
858
859




















860
861
862
863
864
865
866
867
868
869
870
871












872
873
874
875
876
877
878
879
880
881
				to_timestamp($4::bigint),
				$5::bigint, $6::bigint, $7::bytea,
				$8::text, $9::smallint, $10::integer
			) returning id
		]];
	};


	actor_auth_pw = {
		params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
			select a.aid, a.uid, a.name from parsav_auth as a
				left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
................................................................................
	actor_power_insert = {
		params = {uint64,lib.mem.ptr(int8),uint16}, cmd = true, sql = [[
			insert into parsav_rights (actor, key, allow) values (
				$1::bigint, $2::text, ($3::smallint)::integer::bool
			)
		]]
	};

	actor_power_delete = {
		params = {uint64,lib.mem.ptr(int8)}, cmd = true, sql = [[
			delete from parsav_rights where
				actor = $1::bigint and
				key = $2::text
		]]
	};

	auth_create_pw = {
		params = {uint64, lib.mem.ptr(uint8)}, cmd = true, sql = [[
			insert into parsav_auth (uid, name, kind, cred) values (
				$1::bigint,
				(select handle from parsav_actors where id = $1::bigint),
				'pw-sha256', $2::bytea
................................................................................
		a.ptr.key = r:bin(row,8)
	end
	if r:null(row,3) then a.ptr.origin = 0
	else a.ptr.origin = r:int(uint64,row,3) end
	return a
end

local privmap = lib.store.privmap








local checksha = function(src, hash, origin, username, pw)
	local validate = function(kind, cred, credlen)
		return quote 
			var r = queries.actor_auth_pw.exec(
				[&lib.store.source](src),
				username,
................................................................................
		[vdrs]
		lib.dbg(['could not find password hash'])
	end
end

local schema = sqlsquash(lib.util.ingest('backend/schema/pgsql.sql'))
local obliterator = sqlsquash(lib.util.ingest('backend/schema/pgsql-drop.sql'))

local privupdate = terra(
	src: &lib.store.source,
	ac: &lib.store.actor
): {}
	var pdef = lib.store.rights_default().powers
	var map = array([privmap])
	for i=0, [map.type.N] do
		var d = pdef and map[i].priv
		var u = ac.rights.powers and map[i].priv
		queries.actor_power_delete.exec(src, ac.id, map[i].name)
		if d:sz() > 0 and u:sz() == 0 then
			lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct})
			queries.actor_power_insert.exec(src, ac.id, map[i].name, 0)
		elseif d:sz() == 0 and u:sz() > 0 then
			lib.dbg('granting power ', {map[i].name.ptr, map[i].name.ct})
			queries.actor_power_insert.exec(src, ac.id, map[i].name, 1)
		end
	end
end

local getpow = terra(
	src: &lib.store.source,
	uid: uint64
): lib.store.powerset
	var powers = lib.store.rights_default().powers
	var map = array([privmap])
	var r = queries.actor_powers_fetch.exec(src, uid)

	for i=0, r.sz do
		for j=0, [map.type.N] do
			var pn = r:_string(i,0)
			if map[j].name:cmp(pn) then
				if r:bool(i,1)
					then powers = powers + map[j].priv
					else powers = powers - map[j].priv
				end
			end
		end
	end

	return powers
end

local b = `lib.store.backend {
	id = "pgsql";
	open = [terra(src: &lib.store.source): &opaque
		lib.report('connecting to postgres database: ', src.string.ptr)
		var [con] = lib.pq.PQconnectdb(src.string.ptr)
		if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
................................................................................
	
	actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
		var r = queries.actor_fetch_uid.exec(src, uid)
		if r.sz == 0 then
			return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)
			a.ptr.rights.powers = getpow(src, uid)
			a.ptr.source = src
			return a
		end
	end];

	actor_fetch_xid = [terra(src: &lib.store.source, xid: lib.mem.ptr(int8))
		var r = queries.actor_fetch_xid.exec(src, xid)
		if r.sz == 0 then
			return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)
			a.ptr.rights.powers = getpow(src, a.ptr.id)
			a.ptr.source = src
			return a
		end
	end];

	actor_enum = [terra(src: &lib.store.source)
		var r = queries.actor_enum.exec(src)
................................................................................
		
		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
		for i=0,r.sz do ret.ptr[i] = row_to_post(&r, i) end -- MUST FREE ALL

		return ret
	end];

	actor_powers_fetch = getpow;
	actor_save_privs = privupdate;





















	actor_create = [terra(
		src: &lib.store.source,
		ac: &lib.store.actor
	): uint64
		var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.epithet, ac.rights.rank, ac.rights.quota)
		if r.sz == 0 then lib.bail('failed to create actor!') end
		ac.id = r:int(uint64,0,0)

		-- check against default rights, insert records for wherever powers differ
		lib.dbg('created new actor, establishing powers')
		privupdate(src,ac)













		lib.dbg('powers established')
		return ac.id
	end];

	auth_create_pw = [terra(
		src: &lib.store.source,
		uid: uint64,
		reset: bool,
		pw: lib.mem.ptr(int8)

Modified mgtool.t from [293667feb7] to [54eca1a845].

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
..
81
82
83
84
85
86
87


















88
89
90
91
92
93
94
...
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
	{ 'db extract (<artifact>|<post>/<attachment number>)', 'extracts an attachment artifact from the database and prints it to standard out' };
	{ 'db excise <artifact>', 'extracts an attachment artifact from the database and prints it to standard out' };
	{ 'db obliterate', 'completely purge all parsav-related content and structure from the database, destroying all user content (requires confirmation)' };
	{ 'db insert', 'reads a file from standard in and inserts it into the attachment database, printing the resulting ID' };
	{ 'mkroot <handle>', 'establish a new root user with the given handle' };
	{ 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' };
	{ 'user <handle> auth <type> reset', '(where applicable, managed auth only) delete all of a user\'s authentication tokens of the given type and issue a new one' };
	{ 'user <handle> auth purge-credentials [<type>]', 'delete all credentials that would allow this user to log in (where possible)' };
	{ 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' };
	{ 'user <handle> emasculate', 'strip all administrative powers from a user' };
	{ 'user <handle> suspend [<timespec>]', '(e.g. \27[1muser jokester suspend 5d 6h 7m 3s\27[m to suspend "jokester" for five days, six hours, seven minutes, and three seconds) suspend a user'};
	{ 'actor <xid> purge-all', 'remove all traces of a user from the database (except local user credentials -- use \27[1mauth purge-credentials\27[m to prevent a user from accessing the instance)' };
	{ 'actor <xid> create', 'instantiate a new actor' };
	{ 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' };
	{ 'conf set <setting> <value>', 'add or a change a server configuration parameter to the database' };
	{ 'conf get <setting>', 'report the value of a server setting' };
	{ 'conf reset <setting>', 'reset a server setting to its default value' };
	{ 'conf refresh', 'instruct an instance to refresh its configuration cache' };
	{ 'conf chsec', 'reset the server secret, invalidating all authentication cookies' };
................................................................................

local terra gensec(sdest: rawstring)
	var dest = [&uint8](sdest)
	lib.crypt.spray(dest,64)
	for i=0,64 do dest[i] = dest[i] % (0x7e - 0x20) + 0x20 end
	dest[64] = 0
end



















local terra entry_mgtool(argc: int, argv: &rawstring): int
	if argc < 1 then lib.bail('bad invocation!') end

	lib.noise_init(2)
	[lib.init]

................................................................................
					root.epithet = epithets[lib.crypt.random(intptr,0,[epithets.type.N])]
					root.rights.powers:fill() -- grant omnipotence
					root.rights.rank = 1
					var ruid = dlg:actor_create(&root)
					dlg:conf_set('master',root.handle)
					lib.report('created new administrator')
					if mg then
						lib.dbg('generating temporary password')
						var tmppw: uint8[33]

						lib.crypt.spray(&tmppw[0],32) tmppw[32] = 0
						for i=0,32 do

							tmppw[i] = tmppw[i] % (10 + 26*2)
							if tmppw[i] >= 36 then
								tmppw[i] = tmppw[i] + (0x61 - 36)
							elseif tmppw[i] >= 10 then
								tmppw[i] = tmppw[i] + (0x41 - 10)
							else tmppw[i] = tmppw[i] + 0x30 end
						end
						lib.dbg('assigning temporary password')
						dlg:auth_create_pw(ruid, false, pstr {
							ptr = [rawstring](&tmppw[0]), ct = 32
						})
						lib.report('temporary root pw: ', {[rawstring](&tmppw[0]), 32})







					end






























				else goto cmderr end
			elseif lib.str.cmp(mode.arglist(0),'user') == 0 then


			elseif lib.str.cmp(mode.arglist(0),'actor') == 0 then
			elseif lib.str.cmp(mode.arglist(0),'tl') == 0 then
			elseif lib.str.cmp(mode.arglist(0),'serv') == 0 then
			else goto cmderr end
		end
	end

	do return 0 end
	::cmderr:: lib.bail('invalid command') return 2
end

return entry_mgtool







|



|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







<
|
>
|
<
>
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>








|



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
..
81
82
83
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
...
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
277
278
279
280
281
282
283
284
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
	{ 'db extract (<artifact>|<post>/<attachment number>)', 'extracts an attachment artifact from the database and prints it to standard out' };
	{ 'db excise <artifact>', 'extracts an attachment artifact from the database and prints it to standard out' };
	{ 'db obliterate', 'completely purge all parsav-related content and structure from the database, destroying all user content (requires confirmation)' };
	{ 'db insert', 'reads a file from standard in and inserts it into the attachment database, printing the resulting ID' };
	{ 'mkroot <handle>', 'establish a new root user with the given handle' };
	{ 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' };
	{ 'user <handle> auth <type> reset', '(where applicable, managed auth only) delete all of a user\'s authentication tokens of the given type and issue a new one' };
	{ 'user <handle> auth (<type>|all) purge', 'delete all credentials that would allow this user to log in (where possible)' };
	{ 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' };
	{ 'user <handle> emasculate', 'strip all administrative powers from a user' };
	{ 'user <handle> suspend [<timespec>]', '(e.g. \27[1muser jokester suspend 5d 6h 7m 3s\27[m to suspend "jokester" for five days, six hours, seven minutes, and three seconds) suspend a user'};
	{ 'actor <xid> purge-all', 'remove all traces of a user from the database (except local user credentials -- use \27[1mauth all purge\27[m to prevent a user from accessing the instance)' };
	{ 'actor <xid> create', 'instantiate a new actor' };
	{ 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' };
	{ 'conf set <setting> <value>', 'add or a change a server configuration parameter to the database' };
	{ 'conf get <setting>', 'report the value of a server setting' };
	{ 'conf reset <setting>', 'reset a server setting to its default value' };
	{ 'conf refresh', 'instruct an instance to refresh its configuration cache' };
	{ 'conf chsec', 'reset the server secret, invalidating all authentication cookies' };
................................................................................

local terra gensec(sdest: rawstring)
	var dest = [&uint8](sdest)
	lib.crypt.spray(dest,64)
	for i=0,64 do dest[i] = dest[i] % (0x7e - 0x20) + 0x20 end
	dest[64] = 0
end

local terra pwset(dlg: idelegate, buf: &(int8[33]), uid: uint64, reset: bool)
	lib.dbg('generating temporary password')
	var tmppw = [&uint8](&(buf[0]))
	lib.crypt.spray(tmppw,32) tmppw[32] = 0
	for i=0,32 do
		tmppw[i] = tmppw[i] % (10 + 26*2)
		if tmppw[i] >= 36 then
			tmppw[i] = tmppw[i] + (0x61 - 36)
		elseif tmppw[i] >= 10 then
			tmppw[i] = tmppw[i] + (0x41 - 10)
		else tmppw[i] = tmppw[i] + 0x30 end
	end
	lib.dbg('assigning temporary password')
	dlg:auth_create_pw(uid, reset, pstr {
		ptr = [rawstring](tmppw), ct = 32
	})
end

local terra entry_mgtool(argc: int, argv: &rawstring): int
	if argc < 1 then lib.bail('bad invocation!') end

	lib.noise_init(2)
	[lib.init]

................................................................................
					root.epithet = epithets[lib.crypt.random(intptr,0,[epithets.type.N])]
					root.rights.powers:fill() -- grant omnipotence
					root.rights.rank = 1
					var ruid = dlg:actor_create(&root)
					dlg:conf_set('master',root.handle)
					lib.report('created new administrator')
					if mg then

						var tmppw: int8[33]
						pwset(dlg, &tmppw, ruid, false)
						lib.report('temporary root pw: ', {&tmppw[0], 32})

					end
				else goto cmderr end
			elseif lib.str.cmp(mode.arglist(0),'user') == 0 then
				var umode: pbasic umode:parse(mode.arglist.ct, &mode.arglist(0))
				if umode.help then
					[ lib.emit(false, 1, 'usage: ', `argv[0], ' user ', umode.type.helptxt.flags, ' <handle> <cmd> [<args>…]', umode.type.helptxt.opts) ]
					return 1
				end
				if umode.arglist.ct >= 3 then
					var grant = lib.str.cmp(umode.arglist(1),'grant') == 0
					var handle = umode.arglist(0)
					var usr = dlg:actor_fetch_xid(pstr {ptr=handle, ct=lib.str.sz(handle)})
					if not usr then lib.bail('unknown handle') end
					if grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then
						var newprivs = usr.ptr.rights.powers
						var map = array([lib.store.privmap])
						if umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(2),'all') == 0 then
							if grant
								then newprivs:fill()
								else newprivs:clear()
							end
						else
							for i=2,umode.arglist.ct do
								var priv = umode.arglist(i)
								for j=0,[map.type.N] do
									var p = map[j]
									if p.name:cmp_raw(priv) then
										if grant then
											lib.dbg('enabling power ', {p.name.ptr,p.name.ct})
											newprivs = newprivs + p.priv
										else
											lib.dbg('disabling power ', {p.name.ptr,p.name.ct})
											newprivs = newprivs - p.priv
										end
										break
									end
								end
							end
						end

						usr.ptr.rights.powers = newprivs
						dlg:actor_save_privs(usr.ptr)
					elseif lib.str.cmp(umode.arglist(1),'auth') == 0 and umode.arglist.ct == 4 then
						var reset = lib.str.cmp(umode.arglist(3),'reset') == 0
						if reset or lib.str.cmp(umode.arglist(3),'new') == 0 then
							if lib.str.cmp(umode.arglist(2),'pw') == 0 then
								var tmppw: int8[33]
								pwset(dlg, &tmppw, usr.ptr.id, reset)
								lib.report('new temporary password for ',usr.ptr.handle,': ', {&tmppw[0], 32})
							else lib.bail('unknown credential type') end
						elseif lib.str.cmp(umode.arglist(3),'purge') == 0 then
						else goto cmderr end

					else goto cmderr end
				else goto cmderr end
			elseif lib.str.cmp(mode.arglist(0),'actor') == 0 then
			elseif lib.str.cmp(mode.arglist(0),'tl') == 0 then
			elseif lib.str.cmp(mode.arglist(0),'serv') == 0 then
			else goto cmderr end
		end
	end

	do return 0 end
	::cmderr:: lib.bail('invalid command')
end

return entry_mgtool

Modified parsav.md from [4b27db126a] to [409d9b6b60].

49
50
51
52
53
54
55


56
57
58
59
60
61
62

    master   pgsql   host=localhost dbname=parsav
	tweets   pgsql   host=420.69.dread.cloud dbname=content

the form the configuration string takes depends on the specific backend.

once you've set up a backend and confirmed parsav can connect succesfully to it, you can initialize the database with the command `parsav db init <domain>`, where `<domain>` is the name of the domain name you will be hosting `parsav` from. this will install all necessary structures and functions in the target and create all necessary files. it will not, however, create any users. you can create an initial administrative user with the `parsav mkroot <handle>` command, where `<handle>` is the handle you want to use on the server. this will also assign a temporary password for the user if possible. you should now be able to log in and administer the server.



by default, parsav binds to [::1]:10917. if you want to change this (to run it on a different port, or make it directly accessible to other servers on the network), you can use the command `parsav conf set bind <address>`, where `address` is a binding specification like `0.0.0.0:80`. it is recommended, however, that `parsavd` be kept accessible only from localhost, and that connections be forwarded to it from nginx, haproxy, or a similar reverse proxy. (this can also be changed with the online configuration UI)

### postgresql backend

a database will need to be created for `parsav`'s use before `parsav db init` will work. this can be accomplished with a command like `$ createdb parsav`. you'll also of course need to set up some way for `parsavd` to authenticate itself to `postgres`. peer auth is the most secure option, and this is what you should use if postgres and `parsavd` are running on the same box. specify the database name to the backend the usual way, with a clause like `dbname=parsav` in your connection string.








>
>







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

    master   pgsql   host=localhost dbname=parsav
	tweets   pgsql   host=420.69.dread.cloud dbname=content

the form the configuration string takes depends on the specific backend.

once you've set up a backend and confirmed parsav can connect succesfully to it, you can initialize the database with the command `parsav db init <domain>`, where `<domain>` is the name of the domain name you will be hosting `parsav` from. this will install all necessary structures and functions in the target and create all necessary files. it will not, however, create any users. you can create an initial administrative user with the `parsav mkroot <handle>` command, where `<handle>` is the handle you want to use on the server. this will also assign a temporary password for the user if possible. you should now be able to log in and administer the server.

if something goes awry with your administrative account, don't fret! you can get your powers themselves back with the command `parsav user <handle> grant all`, and if you're having difficulties logging in, the command `parsav user <handle> auth pw reset` will give you a fresh password. if all else fails, you can always run `mkroot` again to create a new root account, and try to repair the damage from there.

by default, parsav binds to [::1]:10917. if you want to change this (to run it on a different port, or make it directly accessible to other servers on the network), you can use the command `parsav conf set bind <address>`, where `address` is a binding specification like `0.0.0.0:80`. it is recommended, however, that `parsavd` be kept accessible only from localhost, and that connections be forwarded to it from nginx, haproxy, or a similar reverse proxy. (this can also be changed with the online configuration UI)

### postgresql backend

a database will need to be created for `parsav`'s use before `parsav db init` will work. this can be accomplished with a command like `$ createdb parsav`. you'll also of course need to set up some way for `parsavd` to authenticate itself to `postgres`. peer auth is the most secure option, and this is what you should use if postgres and `parsavd` are running on the same box. specify the database name to the backend the usual way, with a clause like `dbname=parsav` in your connection string.

Modified route.t from [4fbd6ed0a5] to [4ebb6db558].

129
130
131
132
133
134
135




136
137
138
139
140
141
142
143
144
145
146
147
148
	else
		::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
	end
	return
end

terra http.post_compose(co: &lib.srv.convo, meth: method.t)




	if meth == method.get then
		lib.render.compose(co, nil)
	elseif meth == method.post then
		if co.who.rights.powers.post() == false then
			co:complain(401,'insufficient privileges','you lack the <strong>post</strong> power and cannot perform this action') return
		end
		var text, textlen = co:postv("post")
		var acl, acllen = co:postv("acl")
		var subj, subjlen = co:postv("subject")
		if text == nil or acl == nil then
			co:complain(405, 'invalid post', 'every post must have at least body text and an ACL')
			return
		end







>
>
>
>



<
<
<







129
130
131
132
133
134
135
136
137
138
139
140
141
142



143
144
145
146
147
148
149
	else
		::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
	end
	return
end

terra http.post_compose(co: &lib.srv.convo, meth: method.t)
	if not co:assertpow('post') then return end
	--if co.who.rights.powers.post() == false then
		--co:complain(403,'insufficient privileges','you lack the <strong>post</strong> power and cannot perform this action')

	if meth == method.get then
		lib.render.compose(co, nil)
	elseif meth == method.post then



		var text, textlen = co:postv("post")
		var acl, acllen = co:postv("acl")
		var subj, subjlen = co:postv("subject")
		if text == nil or acl == nil then
			co:complain(405, 'invalid post', 'every post must have at least body text and an ACL')
			return
		end

Modified srv.t from [7234d58b59] to [7c204f248d].

179
180
181
182
183
184
185










186
187
188
189
190
191
192
	body:send(self.con, code, [lib.mem.ptr(lib.http.header)] {
		ptr = &hdrs[0], ct = [hdrs.type.N]
	})

	body.title:free()
	body.body:free()
end











struct convo.page {
	title: pstring
	body: pstring
	class: pstring
}








>
>
>
>
>
>
>
>
>
>







179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
	body:send(self.con, code, [lib.mem.ptr(lib.http.header)] {
		ptr = &hdrs[0], ct = [hdrs.type.N]
	})

	body.title:free()
	body.body:free()
end

convo.methods.assertpow = macro(function(self, pow)
	return quote
		var ok = true
		if self.aid == 0 or self.who.rights.powers.[pow:asvalue()]() == false then
			ok = false
			self:complain(403,'insufficient privileges',['you lack the <strong>'..pow:asvalue()..'</strong> power and cannot perform this action'])
		end
	in ok end
end)

struct convo.page {
	title: pstring
	body: pstring
	class: pstring
}

Modified store.t from [71684bc451] to [3a79c99b1d].

28
29
30
31
32
33
34









35
36
37
38
39
40
41
...
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
		'cred', 'elevate', 'demote', 'rebrand', -- modify site's brand identity
		'herald' -- grant serverwide epithets
	};
	prepmode = lib.enum {
		'full','conf','admin'
	}
}










terra m.powerset:affect_users()
	return self.purge() or self.censor() or self.suspend() or
	       self.elevate() or self.demote() or self.cred()
end

local str = rawstring
................................................................................
	conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place
	obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database

	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
	conf_set: {&m.source, rawstring, rawstring} -> {}
	conf_reset: {&m.source, rawstring} -> {}

	actor_save: {&m.source, &m.actor} -> bool
	actor_create: {&m.source, &m.actor} -> uint64
	actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
	actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor)
	actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif)
	actor_enum: {&m.source} -> lib.mem.ptr(&m.actor)
	actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor)
	actor_stats: {&m.source, uint64} -> m.actor_stats








>
>
>
>
>
>
>
>
>







 







|
|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
...
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
		'cred', 'elevate', 'demote', 'rebrand', -- modify site's brand identity
		'herald' -- grant serverwide epithets
	};
	prepmode = lib.enum {
		'full','conf','admin'
	}
}

m.privmap = {}
do local struct pt { name:lib.mem.ptr(int8), priv:m.powerset }
for k,v in pairs(m.powerset.members) do
	m.privmap[#m.privmap + 1] = quote
		var ps: m.powerset ps:clear()
		(ps.[v] << true)
	in pt {name = lib.str.plit(v), priv = ps} end
end end

terra m.powerset:affect_users()
	return self.purge() or self.censor() or self.suspend() or
	       self.elevate() or self.demote() or self.cred()
end

local str = rawstring
................................................................................
	conprep: {&m.source, m.prepmode.t} -> {} -- prepares queries and similar tasks that require the schema to already be in place
	obliterate_everything: &m.source -> bool -- wipes everything parsav-related out of the database

	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
	conf_set: {&m.source, rawstring, rawstring} -> {}
	conf_reset: {&m.source, rawstring} -> {}

	actor_create: {&m.source, &m.actor} -> uint64
	actor_save_privs: {&m.source, &m.actor} -> {}
	actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
	actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor)
	actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif)
	actor_enum: {&m.source} -> lib.mem.ptr(&m.actor)
	actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor)
	actor_stats: {&m.source, uint64} -> m.actor_stats