parsav  Diff

Differences From Artifact [54eca1a845]:

To Artifact [c954b701da]:


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

52
53
54




55


56
57
58
59
60
61
62
...
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
...
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
...
182
183
184
185
186
187
188
189






190
191
192
193
194
195
196
...
245
246
247
248
249
250
251
252








253
254
255
256
257
258
259
260

261
262
263
264
265
266
267
...
285
286
287
288
289
290
291



292
293
294
295
296
297









298
299
300
301
302
303
304
local subcmds = {
}

local ctlcmds = {
	{ 'start', 'start a new instance of the server' };
	{ 'stop', 'stop a running instance' };
	{ 'attach', 'capture log output from a running instance' };
	{ 'db init <domain>', 'initialize backend databases (or a single specified database) with the necessary schema and structures for the given FQDN' };
	{ 'db vacuum', 'delete old remote content from the database' };
	{ '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' };
	{ 'serv dl', 'initiate an update cycle over foreign actors' };
	{ 'tl', 'print the current local timeline to standard out' };
	{ 'be pgsql setup-auth (managed|unmanaged)', '(PGSQL backends) select the authentication strategy to use' };
}

local ctlcmdhelp = 'commands:\n'

for _, v in ipairs(ctlcmds) do
	ctlcmdhelp = ctlcmdhelp .. string.format (
		'    \27[1m%s\27[m: %s\n', v[1]:gsub('(<%w+>)','\27[36m%1\27[;1m'), v[2]




	)


end

local struct idelegate {
	all: bool
	src: &lib.store.source
	srv: &lib.srv.overlord
}
................................................................................
	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]

	var srv: lib.srv.overlord
	var dlg = idelegate { srv = &srv, src = nil }

	var mode: ctloptions
	mode:parse(argc,argv) defer mode:free()
	if mode.version then version() return 0 end
	if mode.help then
		[ lib.emit(false, 1, 'usage: ', `argv[0], ' ', ctloptions.helptxt.flags, ' <cmd> [<args>…]', ctloptions.helptxt.opts, ctlcmdhelp) ]
		return 0
	end

	var cnf: rawstring
	if mode.backend_file ~= nil
		then cnf = @mode.backend_file
		else cnf = lib.proc.getenv('parsav_backend_file')
	end
	if cnf == nil then cnf = "backend.conf" end
	if mode.all then dlg.all = true else
		-- iterate through and pick the right backend
	end

	if mode.arglist.ct == 0 then lib.bail('no command') return 1 end







	if lib.str.cmp(mode.arglist(0),'attach') == 0 then
	elseif lib.str.cmp(mode.arglist(0),'start') == 0 then


































	elseif lib.str.cmp(mode.arglist(0),'stop') == 0 then










	else
		if lib.str.cmp(mode.arglist(0),'db') == 0 then
			var dbmode: pbasic dbmode:parse(mode.arglist.ct, &mode.arglist(0))
			if dbmode.help then
				[ lib.emit(false, 1, 'usage: ', `argv[0], ' db ', dbmode.type.helptxt.flags, ' <cmd> [<args>…]', dbmode.type.helptxt.opts) ]







				return 1
			end
			if dbmode.arglist.ct < 1 then goto cmderr end

			srv:setup(cnf) 
			if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then
				lib.report('initializing new database structure for domain ', dbmode.arglist(1))
................................................................................
				var confirmstrs = array(
					'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa'
				)
				var cfmstr: int8[64] cfmstr[0] = 0
				var tdx = lib.osclock.time(nil) / 60
				for i=0,3 do
					if i ~= 0 then lib.str.cat(&cfmstr[0], '-') end
					lib.str.cat(&cfmstr[0], confirmstrs[(tdx + 49*i) % [confirmstrs.type.N]])
				end

				if dbmode.arglist.ct == 1 then
					lib.bail('you are attempting to completely obliterate all data! make sure you have selected your target correctly. if you really want to do this, pass the confirmation string ', &cfmstr[0])
				elseif dbmode.arglist.ct == 2 then
					if lib.str.cmp(dbmode.arglist(1), cfmstr) == 0 then
						lib.warn('completely obliterating all data!')
................................................................................
			srv:conprep(lib.store.prepmode.conf)
			var cfmode: lib.cmdparse {
				help = {'h','display this list'};
				no_notify = {'n', "don't instruct the server to refresh its configuration cache after making changes; useful for \"transactional\" configuration changes."};
			}
			cfmode:parse(mode.arglist.ct, &mode.arglist(0))
			if cfmode.help then
				[ lib.emit(false, 1, 'usage: ', `argv[0], ' conf ', cfmode.type.helptxt.flags, ' <cmd> [<args>…]', cfmode.type.helptxt.opts) ]






				return 1
			end
			if cfmode.arglist.ct < 1 then goto cmderr end

			if cfmode.arglist.ct == 1 then
				if lib.str.cmp(cfmode.arglist(0),'chsec') == 0 then
					var sec: int8[65] gensec(&sec[0])
................................................................................
						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
................................................................................
						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







|
|
<
<
<
<

<
<
<
<
<
<



|
<
<
<
<





|
>
|
|
|
>
>
>
>
|
>
>







 







>



|









|


>





|





>
>
>
>
>
>
>


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

>
>
>
>
>
>
>
>
>
>




|
>
>
>
>
>
>
>







 







|







 







|
>
>
>
>
>
>







 







|
>
>
>
>
>
>
>
>






<

>







 







>
>
>






>
>
>
>
>
>
>
>
>







18
19
20
21
22
23
24
25
26




27






28
29
30
31




32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
..
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
...
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
...
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
...
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325

326
327
328
329
330
331
332
333
334
...
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
local subcmds = {
}

local ctlcmds = {
	{ 'start', 'start a new instance of the server' };
	{ 'stop', 'stop a running instance' };
	{ 'attach', 'capture log output from a running instance' };
	{ 'db', 'set up and manage the database' };
	{ 'user', 'manage users, privileges, and credentials'};




	{ 'mkroot <handle>', 'establish a new root user with the given handle' };






	{ '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', 'manage the server configuration'};




	{ 'serv dl', 'initiate an update cycle over foreign actors' };
	{ 'tl', 'print the current local timeline to standard out' };
	{ 'be pgsql setup-auth (managed|unmanaged)', '(PGSQL backends) select the authentication strategy to use' };
}

local cmdhelp = function(tbl)
	local str = '\ncommands:\n'
	for _, v in ipairs(tbl) do
		str = str .. string.format (
			'    \27[1m%s\27[m: %s\n',
			v[1]
				:gsub('([%(%)|%[%]])', '\27[34m%1\27[;1m')
				:gsub('(<.->)','\27[36m%1\27[;1m'),
			v[2]
		)
	end
	return str
end

local struct idelegate {
	all: bool
	src: &lib.store.source
	srv: &lib.srv.overlord
}
................................................................................
	end
	lib.dbg('assigning temporary password')
	dlg:auth_create_pw(uid, reset, pstr {
		ptr = [rawstring](tmppw), ct = 32
	})
end

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

	lib.noise.init(2)
	[lib.init]

	var srv: lib.srv.overlord
	var dlg = idelegate { srv = &srv, src = nil }

	var mode: ctloptions
	mode:parse(argc,argv) defer mode:free()
	if mode.version then version() return 0 end
	if mode.help then
		[ lib.emit(false, 1, 'usage: ', `argv[0], ' ', ctloptions.helptxt.flags, ' <cmd> [<args>…]', ctloptions.helptxt.opts, cmdhelp(ctlcmds)) ]
		return 0
	end
	if mode.quiet then lib.noise.level = 0 end
	var cnf: rawstring
	if mode.backend_file ~= nil
		then cnf = @mode.backend_file
		else cnf = lib.proc.getenv('parsav_backend_file')
	end
	if cnf == nil then cnf = [config.prefix_conf .. "/backend.conf"] end
	if mode.all then dlg.all = true else
		-- iterate through and pick the right backend
	end

	if mode.arglist.ct == 0 then lib.bail('no command') return 1 end

	if lib.str.cmp(mode.arglist(0),'start') ~= 0 then
	-- hack to save us some pain around forking
		emp = lib.ipc.emperor.mk(false)
	end
	defer emp:release()

	if lib.str.cmp(mode.arglist(0),'attach') == 0 then
	elseif lib.str.cmp(mode.arglist(0),'start') == 0 then
		mode.arglist(0) = "-";
		var chargv = mode.arglist.ptr
		var lsr = lib.ipc.listener.mk()
		var chpid = lib.proc.fork()
		if chpid == 0 then
			lsr:release()
			--lib.proc.daemonize(1,0)
			lib.io.close(0) lib.io.close(1) lib.io.close(2)
			lib.proc.exec([config.prefix_bin .. '/parsavd'], chargv)
			lib.proc.execp([config.prefix_bin .. '/parsavd'], chargv)
			lib.ipc.notify_parent(lib.ipc.signals.state_fail_find)
			lib.bail('cannot find parsav program')
		else
			lib.report('starting parsav daemon')
			while true do
				var sig = lsr:block()
				if sig.system then lib.dbg('got system signal') end
				if sig.from == chpid then
					if sig.system and sig.sig == lib.ipc.signals.sys_child then 
						lib.warn('parsavd failed to start')
						return 0
					elseif sig.sig == lib.ipc.signals.notify_state_change and
					       sig.event == lib.ipc.signals.state_success then
						lib.report('parsavd successfully started')
						return 0
					elseif sig.sig == lib.ipc.signals.notify_state_change and
					       sig.event == lib.ipc.signals.state_fail_find then
						lib.bail('parsavd could not be found')
					else lib.warn('got unrecognized signal, ignoring')
					end
				end
			end
			lsr:release() -- just because i feel distinctly uncomfortable leaving it out
		end
	elseif lib.str.cmp(mode.arglist(0),'stop') == 0 then
		var acks = emp:mallack()
		emp:decree(0,nil, lib.ipc.cmd.stop, 0, &acks(0)) -- TODO targeting
		for i=0,acks.ct do
			if acks(i).success then
				lib.io.fmt('instance %llu successfully stepped down\n', acks(i).clid)
			else
				lib.io.fmt('instance %llu reports failure to halt\n', acks(i).clid)
			end
		end
		acks:free()
	else
		if lib.str.cmp(mode.arglist(0),'db') == 0 then
			var dbmode: pbasic dbmode:parse(mode.arglist.ct, &mode.arglist(0))
			if dbmode.help then
				[ lib.emit(false, 1, 'usage: ', `argv[0], ' db ', dbmode.type.helptxt.flags, ' <cmd> [<args>…]', dbmode.type.helptxt.opts, cmdhelp {
					{ 'db init <domain>', 'initialize backend databases (or a single specified database) with the necessary schema and structures for the given FQDN' };
					{ 'db vacuum', 'delete old remote content from the database' };
					{ 'db extract (<artifact>|<post>/<attachment number>)', 'extracts an attachment artifact from the database and prints it to standard out' };
					{ 'db excise (<artifact>|<post>/<attachment number>)', 'removes an undesirable artifact from the database' };
					{ 'db obliterate [<confirmation code>]', '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' };
				}) ]
				return 1
			end
			if dbmode.arglist.ct < 1 then goto cmderr end

			srv:setup(cnf) 
			if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then
				lib.report('initializing new database structure for domain ', dbmode.arglist(1))
................................................................................
				var confirmstrs = array(
					'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa'
				)
				var cfmstr: int8[64] cfmstr[0] = 0
				var tdx = lib.osclock.time(nil) / 60
				for i=0,3 do
					if i ~= 0 then lib.str.cat(&cfmstr[0], '-') end
					lib.str.cat(&cfmstr[0], confirmstrs[(tdx ^ (173*i)) % [confirmstrs.type.N]])
				end

				if dbmode.arglist.ct == 1 then
					lib.bail('you are attempting to completely obliterate all data! make sure you have selected your target correctly. if you really want to do this, pass the confirmation string ', &cfmstr[0])
				elseif dbmode.arglist.ct == 2 then
					if lib.str.cmp(dbmode.arglist(1), cfmstr) == 0 then
						lib.warn('completely obliterating all data!')
................................................................................
			srv:conprep(lib.store.prepmode.conf)
			var cfmode: lib.cmdparse {
				help = {'h','display this list'};
				no_notify = {'n', "don't instruct the server to refresh its configuration cache after making changes; useful for \"transactional\" configuration changes."};
			}
			cfmode:parse(mode.arglist.ct, &mode.arglist(0))
			if cfmode.help then
				[ lib.emit(false, 1, 'usage: ', `argv[0], ' conf ', cfmode.type.helptxt.flags, ' <cmd> [<args>…]', cfmode.type.helptxt.opts, cmdhelp {
					{ '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' };
				}) ]
				return 1
			end
			if cfmode.arglist.ct < 1 then goto cmderr end

			if cfmode.arglist.ct == 1 then
				if lib.str.cmp(cfmode.arglist(0),'chsec') == 0 then
					var sec: int8[65] gensec(&sec[0])
................................................................................
						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, cmdhelp {
						{ '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> forgive', 'restore all default powers to 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'};
					}) ]
					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 grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then
						if not usr then lib.bail('unknown handle') end
						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
................................................................................
						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
							-- FIXME enable resetting pws for users who have
							-- not logged in yet
							if not usr then lib.bail('unknown handle') end
							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
							var uid: uint64 = 0
							if usr:ref() then uid = usr(0).id end
							if lib.str.cmp(umode.arglist(2),'pw') == 0 then
								dlg:auth_purge_pw(uid, handle)
							elseif lib.str.cmp(umode.arglist(2),'otp') == 0 then
								dlg:auth_purge_otp(uid, handle)
							elseif lib.str.cmp(umode.arglist(2),'trust') == 0 then
								dlg:auth_purge_trust(uid, handle)
							else lib.bail('unknown credential type') end
						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