parsav  Check-in [87731d4007]

Overview
Comment:add ipc backbone
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 87731d4007d725922fe0a577842d943573ecfbecc36a385055c3d6b62c1c2061
User & Date: lexi on 2020-12-29 14:35:10
Other Links: manifest | tags
Context
2020-12-29
14:35
check in missing file check-in: 5a4f99fb55 user: lexi tags: trunk
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
Changes

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

   215    215   			insert into parsav_auth (uid, name, kind, cred) values (
   216    216   				$1::bigint,
   217    217   				(select handle from parsav_actors where id = $1::bigint),
   218    218   				'pw-sha256', $2::bytea
   219    219   			)
   220    220   		]]
   221    221   	};
          222  +
          223  +	auth_purge_type = {
          224  +		params = {rawstring, uint64, rawstring}, cmd = true, sql = [[
          225  +			delete from parsav_auth where
          226  +				((uid = 0 and name = $1::text) or uid = $2::bigint) and
          227  +				kind like $3::text
          228  +		]]
          229  +	};
   222    230   
   223    231   	post_create = {
   224    232   		params = {uint64, rawstring, rawstring, rawstring}, sql = [[
   225    233   			insert into parsav_posts (
   226    234   				author, subject, acl, body,
   227    235   				posted, discovered,
   228    236   				circles, mentions
................................................................................
   876    884   
   877    885   	auth_create_pw = [terra(
   878    886   		src: &lib.store.source,
   879    887   		uid: uint64,
   880    888   		reset: bool,
   881    889   		pw: lib.mem.ptr(int8)
   882    890   	): {}
   883         -		-- TODO impl reset support
   884    891   		var hash: uint8[lib.crypt.algsz.sha256]
   885    892   		if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id),
   886    893   			[&uint8](pw.ptr), pw.ct, &hash[0]) ~= 0 then
   887    894   			lib.bail('cannot hash password')
   888    895   		end
          896  +		if reset then queries.auth_purge_type.exec(src, nil, uid, 'pw-%') end
   889    897   		queries.auth_create_pw.exec(src, uid, [lib.mem.ptr(uint8)] {ptr = &hash[0], ct = [hash.type.N]})
   890    898   	end];
          899  +
          900  +	auth_purge_pw = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {}
          901  +		queries.auth_purge_type.exec(src, handle, uid, 'pw-%')
          902  +	end];
          903  +
          904  +	auth_purge_otp = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {}
          905  +		queries.auth_purge_type.exec(src, handle, uid, 'otp-%')
          906  +	end];
          907  +
          908  +	auth_purge_trust = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {}
          909  +		queries.auth_purge_type.exec(src, handle, uid, 'trust')
          910  +	end];
   891    911   
   892    912   	actor_auth_register_uid = nil; -- not necessary for view-based auth
   893    913   
   894    914   }
   895    915   
   896    916   return b

Modified cmdparse.t from [bfedd61eec] to [49c267b075].

    61     61   		end
    62     62   		terra options:free() self.arglist:free() end
    63     63   		options.methods.parse = terra([self], [argc], [argv])
    64     64   			[init]
    65     65   			var parseopts = true
    66     66   			var [optstack] = 0
    67     67   			var [subcmd] = [ opts.subcmd or 0 ]
    68         -			self.arglist = lib.mem.heapa(rawstring, argc)
           68  +			self.arglist = lib.mem.heapa(rawstring, argc + 1)
    69     69   			var finalargc = 0
    70     70   			for [idx]=1,argc do
    71     71   				var [arg] = argv[idx]
    72     72   				if optstack > 0 then optstack = optstack - 1 goto [skip] end
    73     73   				if arg[0] == @'-' and parseopts then
    74     74   					if arg[1] == @'-' then -- long option
    75     75   						if arg[2] == 0 then -- last option
................................................................................
    88     88   						subcmd = subcmd - 1
    89     89   						if subcmd == 0 then parseopts = false end
    90     90   					end
    91     91   				end
    92     92   				::[skip]::
    93     93   			end
    94     94   			[verifiers]
           95  +			self.arglist.ptr[finalargc] = nil -- for lazy-ass argv compat
    95     96   			if finalargc == 0 then self.arglist:free()
    96     97   							  else self.arglist:resize(finalargc) end
    97     98   		end
    98     99   		options.helptxt = { opts = helpstr, flags = flagstr }
    99    100   	end
   100    101   	return options
   101    102   end

Modified config.lua from [0c09bec0e6] to [931922a3e1].

    33     33   	tgthf     = u.tobool(default('parsav_arch_armhf',true)); 
    34     34   	doc       = {
    35     35   		online  = u.tobool(default('parsav_online_documentation',true)); 
    36     36   		offline = u.tobool(default('parsav_offline_documentation',true)); 
    37     37   	};
    38     38   	outform   = default('parsav_emit_type', 'o');
    39     39   	endian    = default('parsav_arch_endian', 'little');
    40         -	prefix    = default('parsav_install_prefix', './');
           40  +	prefix    = default('parsav_install_prefix', '.');
    41     41   	build     = {
    42     42   		id = u.rndstr(6);
    43     43   		release = u.ingest('release');
    44     44   		when = os.date();
    45     45   	};
    46     46   	feat = {};
    47     47   	debug = u.tobool(default('parsav_enable_debug',true)); 
................................................................................
    65     65   	}):gsub("^'(.*)'$", '%1')
    66     66   end
    67     67   conf.os    = default('parsav_host_os', default_os)
    68     68   conf.tgtos = default('parsav_target_os', default_os)
    69     69   conf.posix = posixes[conf.os]
    70     70   conf.exe   = u.tobool(default('parsav_link',not conf.tgttrip)) -- turn off for partial builds
    71     71   conf.prefix_conf = default('parsav_install_prefix_cfg', conf.prefix)
           72  +conf.prefix_bin = default('parsav_install_prefix_cfg', conf.prefix)
    72     73   conf.prefix_static = default('parsav_install_prefix_static', nil)
    73     74   conf.build.origin = coalesce(
    74     75   	os.getenv('parsav_builder'),
    75     76   	string.format('%s@%s', coalesce (
    76     77   		os.getenv('USER'),
    77     78   		u.exec{'whoami'}
    78     79   	), u.exec{'hostname'}) -- whoami and hostname are present on both windows & unix

Modified crypt.t from [9b6529621c] to [f5b057e4fa].

    58     58   		for i=0,sz do dest[i] = [uint8](rnd()) end
    59     59   		return sz
    60     60   	end
    61     61   end
    62     62   
    63     63   m.random = macro(function(typ, from, to)
    64     64   	local ty = typ:astype()
           65  +	from = from or 0
           66  +	to = to or ty:max()
    65     67   	return quote
    66     68   		var v: ty
    67     69   		m.spray([&uint8](&v), sizeof(ty))
    68     70   		v = v % (to - from) + from -- only works with unsigned!!
    69     71   	in v end
    70     72   end)
    71     73   

Modified mgtool.t from [54eca1a845] to [c954b701da].

    18     18   local subcmds = {
    19     19   }
    20     20   
    21     21   local ctlcmds = {
    22     22   	{ 'start', 'start a new instance of the server' };
    23     23   	{ 'stop', 'stop a running instance' };
    24     24   	{ 'attach', 'capture log output from a running instance' };
    25         -	{ 'db init <domain>', 'initialize backend databases (or a single specified database) with the necessary schema and structures for the given FQDN' };
    26         -	{ 'db vacuum', 'delete old remote content from the database' };
    27         -	{ 'db extract (<artifact>|<post>/<attachment number>)', 'extracts an attachment artifact from the database and prints it to standard out' };
    28         -	{ 'db excise <artifact>', 'extracts an attachment artifact from the database and prints it to standard out' };
    29         -	{ 'db obliterate', 'completely purge all parsav-related content and structure from the database, destroying all user content (requires confirmation)' };
    30         -	{ 'db insert', 'reads a file from standard in and inserts it into the attachment database, printing the resulting ID' };
           25  +	{ 'db', 'set up and manage the database' };
           26  +	{ 'user', 'manage users, privileges, and credentials'};
    31     27   	{ 'mkroot <handle>', 'establish a new root user with the given handle' };
    32         -	{ 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' };
    33         -	{ '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' };
    34         -	{ 'user <handle> auth (<type>|all) purge', 'delete all credentials that would allow this user to log in (where possible)' };
    35         -	{ 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' };
    36         -	{ 'user <handle> emasculate', 'strip all administrative powers from a user' };
    37         -	{ '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'};
    38     28   	{ '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)' };
    39     29   	{ 'actor <xid> create', 'instantiate a new actor' };
    40     30   	{ 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' };
    41         -	{ 'conf set <setting> <value>', 'add or a change a server configuration parameter to the database' };
    42         -	{ 'conf get <setting>', 'report the value of a server setting' };
    43         -	{ 'conf reset <setting>', 'reset a server setting to its default value' };
    44         -	{ 'conf refresh', 'instruct an instance to refresh its configuration cache' };
    45         -	{ 'conf chsec', 'reset the server secret, invalidating all authentication cookies' };
           31  +	{ 'conf', 'manage the server configuration'};
    46     32   	{ 'serv dl', 'initiate an update cycle over foreign actors' };
    47     33   	{ 'tl', 'print the current local timeline to standard out' };
    48     34   	{ 'be pgsql setup-auth (managed|unmanaged)', '(PGSQL backends) select the authentication strategy to use' };
    49     35   }
    50     36   
    51         -local ctlcmdhelp = 'commands:\n'
    52         -for _, v in ipairs(ctlcmds) do
    53         -	ctlcmdhelp = ctlcmdhelp .. string.format (
    54         -		'    \27[1m%s\27[m: %s\n', v[1]:gsub('(<%w+>)','\27[36m%1\27[;1m'), v[2]
    55         -	)
           37  +local cmdhelp = function(tbl)
           38  +	local str = '\ncommands:\n'
           39  +	for _, v in ipairs(tbl) do
           40  +		str = str .. string.format (
           41  +			'    \27[1m%s\27[m: %s\n',
           42  +			v[1]
           43  +				:gsub('([%(%)|%[%]])', '\27[34m%1\27[;1m')
           44  +				:gsub('(<.->)','\27[36m%1\27[;1m'),
           45  +			v[2]
           46  +		)
           47  +	end
           48  +	return str
    56     49   end
    57     50   
    58     51   local struct idelegate {
    59     52   	all: bool
    60     53   	src: &lib.store.source
    61     54   	srv: &lib.srv.overlord
    62     55   }
................................................................................
   100     93   	end
   101     94   	lib.dbg('assigning temporary password')
   102     95   	dlg:auth_create_pw(uid, reset, pstr {
   103     96   		ptr = [rawstring](tmppw), ct = 32
   104     97   	})
   105     98   end
   106     99   
          100  +local emp = lib.ipc.global_emperor
   107    101   local terra entry_mgtool(argc: int, argv: &rawstring): int
   108    102   	if argc < 1 then lib.bail('bad invocation!') end
   109    103   
   110         -	lib.noise_init(2)
          104  +	lib.noise.init(2)
   111    105   	[lib.init]
   112    106   
   113    107   	var srv: lib.srv.overlord
   114    108   	var dlg = idelegate { srv = &srv, src = nil }
   115    109   
   116    110   	var mode: ctloptions
   117    111   	mode:parse(argc,argv) defer mode:free()
   118    112   	if mode.version then version() return 0 end
   119    113   	if mode.help then
   120         -		[ lib.emit(false, 1, 'usage: ', `argv[0], ' ', ctloptions.helptxt.flags, ' <cmd> [<args>…]', ctloptions.helptxt.opts, ctlcmdhelp) ]
          114  +		[ lib.emit(false, 1, 'usage: ', `argv[0], ' ', ctloptions.helptxt.flags, ' <cmd> [<args>…]', ctloptions.helptxt.opts, cmdhelp(ctlcmds)) ]
   121    115   		return 0
   122    116   	end
          117  +	if mode.quiet then lib.noise.level = 0 end
   123    118   	var cnf: rawstring
   124    119   	if mode.backend_file ~= nil
   125    120   		then cnf = @mode.backend_file
   126    121   		else cnf = lib.proc.getenv('parsav_backend_file')
   127    122   	end
   128         -	if cnf == nil then cnf = "backend.conf" end
          123  +	if cnf == nil then cnf = [config.prefix_conf .. "/backend.conf"] end
   129    124   	if mode.all then dlg.all = true else
   130    125   		-- iterate through and pick the right backend
   131    126   	end
   132    127   
   133    128   	if mode.arglist.ct == 0 then lib.bail('no command') return 1 end
          129  +
          130  +	if lib.str.cmp(mode.arglist(0),'start') ~= 0 then
          131  +	-- hack to save us some pain around forking
          132  +		emp = lib.ipc.emperor.mk(false)
          133  +	end
          134  +	defer emp:release()
          135  +
   134    136   	if lib.str.cmp(mode.arglist(0),'attach') == 0 then
   135    137   	elseif lib.str.cmp(mode.arglist(0),'start') == 0 then
          138  +		mode.arglist(0) = "-";
          139  +		var chargv = mode.arglist.ptr
          140  +		var lsr = lib.ipc.listener.mk()
          141  +		var chpid = lib.proc.fork()
          142  +		if chpid == 0 then
          143  +			lsr:release()
          144  +			--lib.proc.daemonize(1,0)
          145  +			lib.io.close(0) lib.io.close(1) lib.io.close(2)
          146  +			lib.proc.exec([config.prefix_bin .. '/parsavd'], chargv)
          147  +			lib.proc.execp([config.prefix_bin .. '/parsavd'], chargv)
          148  +			lib.ipc.notify_parent(lib.ipc.signals.state_fail_find)
          149  +			lib.bail('cannot find parsav program')
          150  +		else
          151  +			lib.report('starting parsav daemon')
          152  +			while true do
          153  +				var sig = lsr:block()
          154  +				if sig.system then lib.dbg('got system signal') end
          155  +				if sig.from == chpid then
          156  +					if sig.system and sig.sig == lib.ipc.signals.sys_child then 
          157  +						lib.warn('parsavd failed to start')
          158  +						return 0
          159  +					elseif sig.sig == lib.ipc.signals.notify_state_change and
          160  +					       sig.event == lib.ipc.signals.state_success then
          161  +						lib.report('parsavd successfully started')
          162  +						return 0
          163  +					elseif sig.sig == lib.ipc.signals.notify_state_change and
          164  +					       sig.event == lib.ipc.signals.state_fail_find then
          165  +						lib.bail('parsavd could not be found')
          166  +					else lib.warn('got unrecognized signal, ignoring')
          167  +					end
          168  +				end
          169  +			end
          170  +			lsr:release() -- just because i feel distinctly uncomfortable leaving it out
          171  +		end
   136    172   	elseif lib.str.cmp(mode.arglist(0),'stop') == 0 then
          173  +		var acks = emp:mallack()
          174  +		emp:decree(0,nil, lib.ipc.cmd.stop, 0, &acks(0)) -- TODO targeting
          175  +		for i=0,acks.ct do
          176  +			if acks(i).success then
          177  +				lib.io.fmt('instance %llu successfully stepped down\n', acks(i).clid)
          178  +			else
          179  +				lib.io.fmt('instance %llu reports failure to halt\n', acks(i).clid)
          180  +			end
          181  +		end
          182  +		acks:free()
   137    183   	else
   138    184   		if lib.str.cmp(mode.arglist(0),'db') == 0 then
   139    185   			var dbmode: pbasic dbmode:parse(mode.arglist.ct, &mode.arglist(0))
   140    186   			if dbmode.help then
   141         -				[ lib.emit(false, 1, 'usage: ', `argv[0], ' db ', dbmode.type.helptxt.flags, ' <cmd> [<args>…]', dbmode.type.helptxt.opts) ]
          187  +				[ lib.emit(false, 1, 'usage: ', `argv[0], ' db ', dbmode.type.helptxt.flags, ' <cmd> [<args>…]', dbmode.type.helptxt.opts, cmdhelp {
          188  +					{ 'db init <domain>', 'initialize backend databases (or a single specified database) with the necessary schema and structures for the given FQDN' };
          189  +					{ 'db vacuum', 'delete old remote content from the database' };
          190  +					{ 'db extract (<artifact>|<post>/<attachment number>)', 'extracts an attachment artifact from the database and prints it to standard out' };
          191  +					{ 'db excise (<artifact>|<post>/<attachment number>)', 'removes an undesirable artifact from the database' };
          192  +					{ 'db obliterate [<confirmation code>]', 'completely purge all parsav-related content and structure from the database, destroying all user content (requires confirmation)' };
          193  +					{ 'db insert', 'reads a file from standard in and inserts it into the attachment database, printing the resulting ID' };
          194  +				}) ]
   142    195   				return 1
   143    196   			end
   144    197   			if dbmode.arglist.ct < 1 then goto cmderr end
   145    198   
   146    199   			srv:setup(cnf) 
   147    200   			if lib.str.cmp(dbmode.arglist(0),'init') == 0 and dbmode.arglist.ct == 2 then
   148    201   				lib.report('initializing new database structure for domain ', dbmode.arglist(1))
................................................................................
   157    210   				var confirmstrs = array(
   158    211   					'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa'
   159    212   				)
   160    213   				var cfmstr: int8[64] cfmstr[0] = 0
   161    214   				var tdx = lib.osclock.time(nil) / 60
   162    215   				for i=0,3 do
   163    216   					if i ~= 0 then lib.str.cat(&cfmstr[0], '-') end
   164         -					lib.str.cat(&cfmstr[0], confirmstrs[(tdx + 49*i) % [confirmstrs.type.N]])
          217  +					lib.str.cat(&cfmstr[0], confirmstrs[(tdx ^ (173*i)) % [confirmstrs.type.N]])
   165    218   				end
   166    219   
   167    220   				if dbmode.arglist.ct == 1 then
   168    221   					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])
   169    222   				elseif dbmode.arglist.ct == 2 then
   170    223   					if lib.str.cmp(dbmode.arglist(1), cfmstr) == 0 then
   171    224   						lib.warn('completely obliterating all data!')
................................................................................
   182    235   			srv:conprep(lib.store.prepmode.conf)
   183    236   			var cfmode: lib.cmdparse {
   184    237   				help = {'h','display this list'};
   185    238   				no_notify = {'n', "don't instruct the server to refresh its configuration cache after making changes; useful for \"transactional\" configuration changes."};
   186    239   			}
   187    240   			cfmode:parse(mode.arglist.ct, &mode.arglist(0))
   188    241   			if cfmode.help then
   189         -				[ lib.emit(false, 1, 'usage: ', `argv[0], ' conf ', cfmode.type.helptxt.flags, ' <cmd> [<args>…]', cfmode.type.helptxt.opts) ]
          242  +				[ lib.emit(false, 1, 'usage: ', `argv[0], ' conf ', cfmode.type.helptxt.flags, ' <cmd> [<args>…]', cfmode.type.helptxt.opts, cmdhelp {
          243  +					{ 'conf set <setting> <value>', 'add or a change a server configuration parameter to the database' };
          244  +					{ 'conf get <setting>', 'report the value of a server setting' };
          245  +					{ 'conf reset <setting>', 'reset a server setting to its default value' };
          246  +					{ 'conf refresh', 'instruct an instance to refresh its configuration cache' };
          247  +					{ 'conf chsec', 'reset the server secret, invalidating all authentication cookies' };
          248  +				}) ]
   190    249   				return 1
   191    250   			end
   192    251   			if cfmode.arglist.ct < 1 then goto cmderr end
   193    252   
   194    253   			if cfmode.arglist.ct == 1 then
   195    254   				if lib.str.cmp(cfmode.arglist(0),'chsec') == 0 then
   196    255   					var sec: int8[65] gensec(&sec[0])
................................................................................
   245    304   						pwset(dlg, &tmppw, ruid, false)
   246    305   						lib.report('temporary root pw: ', {&tmppw[0], 32})
   247    306   					end
   248    307   				else goto cmderr end
   249    308   			elseif lib.str.cmp(mode.arglist(0),'user') == 0 then
   250    309   				var umode: pbasic umode:parse(mode.arglist.ct, &mode.arglist(0))
   251    310   				if umode.help then
   252         -					[ lib.emit(false, 1, 'usage: ', `argv[0], ' user ', umode.type.helptxt.flags, ' <handle> <cmd> [<args>…]', umode.type.helptxt.opts) ]
          311  +					[ lib.emit(false, 1, 'usage: ', `argv[0], ' user ', umode.type.helptxt.flags, ' <handle> <cmd> [<args>…]', umode.type.helptxt.opts, cmdhelp {
          312  +						{ 'user <handle> auth <type> new', '(where applicable, managed auth only) create a new authentication token of the given type for a user' };
          313  +						{ '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' };
          314  +						{ 'user <handle> auth (<type>|all) purge', 'delete all credentials that would allow this user to log in (where possible)' };
          315  +						{ 'user <handle> (grant|revoke) (<priv>|all)', 'grant or revoke a specific power to or from a user' };
          316  +						{ 'user <handle> emasculate', 'strip all administrative powers from a user' };
          317  +						{ 'user <handle> forgive', 'restore all default powers to a user' };
          318  +						{ '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'};
          319  +					}) ]
   253    320   					return 1
   254    321   				end
   255    322   				if umode.arglist.ct >= 3 then
   256    323   					var grant = lib.str.cmp(umode.arglist(1),'grant') == 0
   257    324   					var handle = umode.arglist(0)
   258    325   					var usr = dlg:actor_fetch_xid(pstr {ptr=handle, ct=lib.str.sz(handle)})
   259         -					if not usr then lib.bail('unknown handle') end
   260    326   					if grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then
          327  +						if not usr then lib.bail('unknown handle') end
   261    328   						var newprivs = usr.ptr.rights.powers
   262    329   						var map = array([lib.store.privmap])
   263    330   						if umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(2),'all') == 0 then
   264    331   							if grant
   265    332   								then newprivs:fill()
   266    333   								else newprivs:clear()
   267    334   							end
................................................................................
   285    352   						end
   286    353   
   287    354   						usr.ptr.rights.powers = newprivs
   288    355   						dlg:actor_save_privs(usr.ptr)
   289    356   					elseif lib.str.cmp(umode.arglist(1),'auth') == 0 and umode.arglist.ct == 4 then
   290    357   						var reset = lib.str.cmp(umode.arglist(3),'reset') == 0
   291    358   						if reset or lib.str.cmp(umode.arglist(3),'new') == 0 then
          359  +							-- FIXME enable resetting pws for users who have
          360  +							-- not logged in yet
          361  +							if not usr then lib.bail('unknown handle') end
   292    362   							if lib.str.cmp(umode.arglist(2),'pw') == 0 then
   293    363   								var tmppw: int8[33]
   294    364   								pwset(dlg, &tmppw, usr.ptr.id, reset)
   295    365   								lib.report('new temporary password for ',usr.ptr.handle,': ', {&tmppw[0], 32})
   296    366   							else lib.bail('unknown credential type') end
   297    367   						elseif lib.str.cmp(umode.arglist(3),'purge') == 0 then
          368  +							var uid: uint64 = 0
          369  +							if usr:ref() then uid = usr(0).id end
          370  +							if lib.str.cmp(umode.arglist(2),'pw') == 0 then
          371  +								dlg:auth_purge_pw(uid, handle)
          372  +							elseif lib.str.cmp(umode.arglist(2),'otp') == 0 then
          373  +								dlg:auth_purge_otp(uid, handle)
          374  +							elseif lib.str.cmp(umode.arglist(2),'trust') == 0 then
          375  +								dlg:auth_purge_trust(uid, handle)
          376  +							else lib.bail('unknown credential type') end
   298    377   						else goto cmderr end
   299    378   					else goto cmderr end
   300    379   				else goto cmderr end
   301    380   			elseif lib.str.cmp(mode.arglist(0),'actor') == 0 then
   302    381   			elseif lib.str.cmp(mode.arglist(0),'tl') == 0 then
   303    382   			elseif lib.str.cmp(mode.arglist(0),'serv') == 0 then
   304    383   			else goto cmderr end

Modified parsav.t from [d1470e4b10] to [1565f096a3].

   124    124   		local q = quote
   125    125   			var [val]
   126    126   			[exp]
   127    127   		in val end
   128    128   		return q
   129    129   	end);
   130    130   	proc = {
          131  +		fork = terralib.externfunction('fork', {} -> int);
          132  +		daemonize = terralib.externfunction('daemon', {int,int} -> {});
   131    133   		exit = terralib.externfunction('exit', int -> {});
   132    134   		getenv = terralib.externfunction('getenv', rawstring -> rawstring);
          135  +		exec = terralib.externfunction('execv', {rawstring,&rawstring} -> int);
          136  +		execp = terralib.externfunction('execvp', {rawstring,&rawstring} -> int);
   133    137   	};
   134    138   	io = {
   135    139   		send = terralib.externfunction('write', {int, rawstring, intptr} -> ptrdiff);
   136    140   		recv = terralib.externfunction('read',  {int, rawstring, intptr} -> ptrdiff);
          141  +		close = terralib.externfunction('close', {int} -> int);
   137    142   		say = macro(function(msg) return `lib.io.send(2, msg, [#(msg:asvalue())]) end);
   138    143   		fmt = terralib.externfunction('printf',
   139    144   			terralib.types.funcpointer({rawstring},{int},true));
   140    145   	};
   141    146   	str = { sz = terralib.externfunction('strlen', rawstring -> intptr) };
   142    147   	copy = function(tbl)
   143    148   		local new = {}
................................................................................
   148    153   	osclock = terralib.includec 'time.h';
   149    154   }
   150    155   if config.posix then
   151    156   	lib.uio = terralib.includec 'sys/uio.h';
   152    157   	lib.emit = lib.emitv -- use more efficient call where available
   153    158   else lib.emit = lib.emit_unitary end
   154    159   
   155         -local starttime = global(lib.osclock.time_t)
   156         -local lastnoisetime = global(lib.osclock.time_t)
   157         -local noise = global(uint8,1)
   158         -local noise_header = function(code,txt,mod)
   159         -	if mod then
   160         -		return string.format('\27[%s;1m(%s %s)\27[m ', code,mod,txt)
   161         -	else
   162         -		return string.format('\27[%s;1m(%s)\27[m ', code,txt)
   163         -	end
   164         -end
          160  +
          161  +lib.noise = {
          162  +	level = global(uint8,1);
          163  +	starttime = global(lib.osclock.time_t);
          164  +	lasttime = global(lib.osclock.time_t);
          165  +	header = function(code,txt,mod)
          166  +		if mod then
          167  +			return string.format('\27[%s;1m(%s %s)\27[m ', code,mod,txt)
          168  +		else
          169  +			return string.format('\27[%s;1m(%s)\27[m ', code,txt)
          170  +		end
          171  +	end;
          172  +}
   165    173   
   166    174   local terra timehdr()
   167    175   	var now = lib.osclock.time(nil)
   168         -	var diff = now - lastnoisetime
          176  +	var diff = now - lib.noise.lasttime
   169    177   	if diff > 30 then -- print cur time
   170         -		lastnoisetime = now
          178  +		lib.noise.lasttime = now
   171    179   		var curtime: int8[26]
   172    180   		lib.osclock.ctime_r(&now, &curtime[0])
   173    181   		for i=0,26 do if curtime[i] == @'\n' then curtime[i] = 0 break end end -- :/
   174    182   		[ lib.emit(false, 2, '\27[1m[', `&curtime[0], ']\27[;36m\n +00 ') ]
   175    183   	else -- print time since last msg
   176    184   		var dfs = arrayof(int8, 0x30 + diff/10, 0x30 + diff%10, 0x20, 0)
   177    185   		[ lib.emit(false, 2, ' \27[36m+', `&dfs[0]) ]
................................................................................
   182    190   	if level >= 3 and config.debug == false then
   183    191   		return macro(function(...) return {} end)
   184    192   	end
   185    193   	return macro(function(...)
   186    194   		local fn = (...).filename
   187    195   		local ln = tostring((...).linenumber)
   188    196   		local dbgtag = string.format('\27[35m · \27[34m%s:\27[1m%s\27[m\n', fn,ln)
   189         -		local q = lib.emit(level < 3 and true or dbgtag, 2, noise_header(code,n), ...)
          197  +		local q = lib.emit(level < 3 and true or dbgtag, 2, lib.noise.header(code,n), ...)
   190    198   		return quote
   191    199   		--lib.io.fmt(['attempting to emit at ' .. fn..':'..ln.. '\n'])
   192         -		if noise >= level then timehdr(); [q] end end
          200  +		if lib.noise.level >= level then timehdr(); [q] end end
   193    201   	end);
   194    202   end
          203  +local terra final_cleanup :: {} -> {}
   195    204   lib.dbg = defrep(3,'debug', '32')
   196    205   lib.report = defrep(2,'info', '35')
   197    206   lib.warn = defrep(1,'warn', '33')
   198    207   lib.bail = macro(function(...)
   199         -	local q = lib.emit(true, 2, noise_header('31','fatal'), ...)
          208  +	local q = lib.emit(true, 2, lib.noise.header('31','fatal'), ...)
   200    209   	return quote
   201         -		timehdr(); [q]
          210  +		if lib.noise.level ~= 0 then
          211  +			timehdr(); [q]
          212  +		end
          213  +		final_cleanup()
   202    214   		lib.proc.exit(1)
   203    215   	end
   204    216   end);
   205    217   lib.stat = terralib.memoize(function(ty)
   206    218   	local n = struct {
   207    219   		ok: bool
   208    220   		union {
................................................................................
   332    344   lib.pk = lib.loadlib('mbedtls','mbedtls/pk.h')
   333    345   lib.md = lib.loadlib('mbedtls','mbedtls/md.h')
   334    346   lib.b64 = lib.loadlib('mbedtls','mbedtls/base64.h')
   335    347   lib.net = lib.loadlib('mongoose','mongoose.h')
   336    348   lib.pq = lib.loadlib('libpq','libpq-fe.h')
   337    349   
   338    350   lib.load {
   339         -	'mem',  'math', 'str', 'file', 'crypt';
          351  +	'mem', 'math', 'str', 'file', 'crypt', 'ipc';
   340    352   	'http', 'html', 'session', 'tpl', 'store';
   341    353   
   342    354   	'smackdown'; -- md-alike parser
   343    355   }
   344    356   
   345    357   local be = {}
   346    358   for _, b in pairs(config.backends) do
................................................................................
   393    405   }
   394    406   
   395    407   do
   396    408   	local p = string.format('parsav: %s\nbuilt on %s\n', config.build.str, config.build.when)
   397    409   	terra version() lib.io.send(1, p, [#p]) end
   398    410   end
   399    411   
   400         -terra lib.noise_init(default_level: uint)
   401         -	starttime = lib.osclock.time(nil)
   402         -	lastnoisetime = 0
          412  +terra lib.noise.init(default_level: uint)
          413  +	lib.noise.starttime = lib.osclock.time(nil)
          414  +	lib.noise.lasttime = 0
   403    415   	var n = lib.proc.getenv('parsav_noise')
   404    416   	if n ~= nil then
   405    417   		if n[0] >= 0x30 and n[0] <= 0x39 and n[1] == 0 then
   406         -			noise = n[0] - 0x30
          418  +			lib.noise.level = n[0] - 0x30
   407    419   			return
   408    420   		end
   409    421   	end
   410         -	noise = default_level
          422  +	lib.noise.level = default_level
   411    423   end
   412    424   lib.load{'mgtool'}
   413    425   
   414    426   local options = lib.cmdparse {
   415    427   	version = {'V', 'display information about the binary build and exit'};
   416    428   	verbose = {'v', 'increase logging verbosity', inc=1};
   417    429   	quiet = {'q', 'do not print to standard out'};
   418    430   	help = {'h', 'display this list'};
   419    431   	backend_file = {'B', 'init from specified backend file', consume=1};
   420    432   	static_dir = {'S', 'directory with overrides for static content', consume=1};
   421    433   	builtin_data = {'D', 'do not load static content overrides at runtime under any circumstances'};
   422    434   	instance = {'i', 'set an instance name to make it easier to control multiple daemons', consume = 1};
          435  +	no_ipc = {'I', 'disable IPC'};
   423    436   }
   424    437   
   425    438   
   426    439   local static_setup = quote end
   427    440   local mapin = quote end
   428    441   local odir = symbol(rawstring)
   429    442   local pathbuf = symbol(lib.str.acc)
................................................................................
   461    474   		end or quote return end
   462    475   	] end
   463    476   
   464    477   	var [pathbuf] defer pathbuf:free()
   465    478   	pathbuf:compose(odir,'/')
   466    479   	[mapin]
   467    480   end
          481  +
          482  +terra final_cleanup()
          483  +	lib.ipc.global_emperor:release()
          484  +end
   468    485   
   469    486   local terra entry_daemon(argc: int, argv: &rawstring): int
   470    487   	if argc < 1 then lib.bail('bad invocation!') end
   471    488   
   472         -	lib.noise_init(1)
          489  +	lib.noise.init(1)
   473    490   	[lib.init]
   474    491   
   475    492   	-- shut mongoose the fuck up
   476    493   	lib.net.mg_log_set_callback([terra(msg: &opaque, sz: int, u: &opaque) end], nil)
   477    494   	var srv: lib.srv.overlord
   478    495   
   479    496   	do var mode: options
................................................................................
   480    497   		mode:parse(argc,argv) defer mode:free()
   481    498   		static_init(&mode)
   482    499   		if mode.version then version() return 0 end
   483    500   		if mode.help then
   484    501   			[ lib.emit(true, 1, 'usage: ',`argv[0],' ', options.helptxt.flags, ' [<args>…]', options.helptxt.opts) ]
   485    502   			return 0
   486    503   		end
          504  +		if mode.quiet then lib.noise.level = 0 end
   487    505   		var cnf: rawstring
   488    506   		if mode.backend_file ~= nil
   489    507   			then cnf = @mode.backend_file
   490    508   			else cnf = lib.proc.getenv('parsav_backend_file')
   491    509   		end
   492         -		if cnf == nil then cnf = [config.prefix_conf .. "backend.conf"] end
          510  +		if cnf == nil then cnf = [config.prefix_conf .. "/backend.conf"] end
   493    511   
   494    512   		srv:setup(cnf)
          513  +		if argv[0][0] == @'-' and argv[0][1] == 0 then
          514  +			lib.ipc.notify_parent(lib.ipc.signals.state_success)
          515  +			argv[0] = 'parsav service daemon'
          516  +		end
   495    517   		srv:start(lib.trn(mode.instance ~= nil, @mode.instance, nil))
   496    518   	end
          519  +	lib.ipc.global_emperor = lib.ipc.emperor.mk(true) defer lib.ipc.global_emperor:release()
   497    520   
   498    521   	lib.report('listening for requests')
   499         -	while true do
          522  +	while lib.ipc.proc_active do
   500    523   		srv:poll()
          524  +		while true do
          525  +			var d: lib.ipc.demand
          526  +			lib.ipc.global_emperor:poll(&d)
          527  +			var a = lib.ipc.ack {success = true}
          528  +			if d.cmd == lib.ipc.cmd.none then break
          529  +			elseif d.cmd == lib.ipc.cmd.stop then
          530  +				lib.ipc.proc_active = false
          531  +			elseif d.cmd == lib.ipc.cmd.enumerate then
          532  +				if srv.id ~= nil then
          533  +					lib.str.ncpy(&a.iname[0], srv.id, [(`a.iname).tree.type.N])
          534  +				else a.iname[0] = 0 end
          535  +			elseif d.cmd == lib.ipc.cmd.chnoise then
          536  +				lib.noise.level = d.operand
          537  +			elseif d.cmd == lib.ipc.cmd.cfgrefresh then
          538  +				srv.cfg:free()
          539  +				srv.cfg:load()
          540  +			end
          541  +			d:ack(&lib.ipc.global_emperor, &a)
          542  +		end
   501    543   	end
   502    544   	srv:shutdown()
   503    545   
   504    546   	return 0
   505    547   end
   506         -
   507    548   
   508    549   local bflag = function(long,short)
   509    550   	if short and util.has(buildopts, short) then return true end
   510    551   	if long and util.has(buildopts, long) then return true end
   511    552   	return false
   512    553   end
   513    554   

Modified store.t from [3a79c99b1d] to [763fd9ba8a].

   266    266   	actor_conf_str: cnf(rawstring, lib.mem.ptr(int8))
   267    267   	actor_conf_int: cnf(intptr, lib.stat(intptr))
   268    268   
   269    269   	auth_create_pw: {&m.source, uint64, bool, lib.mem.ptr(int8)} -> {}
   270    270   		-- uid: uint64
   271    271   		-- reset: bool (delete other passwords?)
   272    272   		-- pw: pstring
          273  +	auth_purge_pw: {&m.source, uint64, rawstring} -> {}
          274  +	auth_purge_otp: {&m.source, uint64, rawstring} -> {}
          275  +	auth_purge_trust: {&m.source, uint64, rawstring} -> {}
   273    276   
   274    277   	post_save: {&m.source, &m.post} -> {}
   275    278   	post_create: {&m.source, &m.post} -> uint64
   276    279   	post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
   277    280   	convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post)
   278    281   	convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post)
   279    282