-- vim: ft=terra
-- provides the functionality of the `parsav` utility that controls `parsavd`
local pstr = lib.mem.ptr(int8)
local ctloptions = lib.cmdparse({
version = {'V', 'display information about the binary build and exit'};
verbose = {'v', 'increase logging verbosity', inc=1};
quiet = {'q', 'do not print to standard out'};
help = {'h', 'display this list'};
backend_file = {'B', 'init from specified backend file', consume=1};
backend = {'b', 'operate on only the selected backend'};
instance_id = {'i', 'specify the instance to control by name', consume=1};
instance_serial = {'I', 'specify the instance to control by serial', consume=1};
all = {'A', 'affect all running instances/backends'};
}, { subcmd = 1 })
local pbasic = lib.cmdparse {
help = {'h', 'display this list'}
}
local subcmds = {
}
local ctlcmds = {
{ 'start', 'start a new instance of the server' };
{ 'stop', 'stop a running instance' };
{ 'ls', 'list all running instances' };
{ 'attach', 'capture log output from a running instance' };
{ 'db', 'set up and manage the database' };
{ 'user', 'create and manage users, privileges, and credentials'};
{ 'actor', 'manage and purge actors, epithets, and ranks'};
{ 'mkroot <handle>', 'establish a new root user with the given handle' };
{ 'conf', 'manage the server configuration'};
{ 'grow <count> [<acl>]', 'grant a new round of invites to all users, or those who match the given ACL' };
{ '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 {
emperor: &lib.ipc.emperor
all: bool
src: &lib.store.source
srv: &lib.srv.overlord
sid: uint64
iname: rawstring
}
idelegate.metamethods.__methodmissing = macro(function(meth, self, ...)
local expr = {...}
local rt
for _,f in pairs(lib.store.backend.entries) do
local fn = f.field or f[1]
local ft = f.type or f[2]
if fn == meth then rt = ft.type.returntype break end
end
return quote
var r: rt
if self.all or (self.srv ~= nil and self.srv.sources.ct == 1)
then r=self.srv:[meth]([expr])
elseif self.src ~= nil then r=self.src:[meth]([expr])
else lib.bail('no data source specified')
end
in r end
end)
terra idelegate:ipc_send(cmd: lib.ipc.cmd.t, operand: uint64)
var emp = self.emperor
var acks: lib.mem.ptr(lib.ipc.ack)
if self.sid == 0 and self.iname == nil then
if not self.all and emp:countpeers() > 1 then
lib.bail('either specify the instance to control or pass --all to control all instances')
end
acks = emp:mallack()
emp:decree(0,nil, cmd, operand, &acks(0)) -- TODO targeting
else
acks = lib.mem.heapa(lib.ipc.ack, 1)
if not emp:decree(self.sid, self.iname, cmd, operand, &acks(0)) then
acks:free()
end
end
return acks
end
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_attach_pw(uid, reset,
pstr { ptr = [rawstring](tmppw), ct = 32 },
'temporary password');
end
local terra ipc_report(acks: lib.mem.ptr(lib.ipc.ack), rep: rawstring)
var decbuf: int8[21]
for i=0,acks.ct do
var num = lib.math.decstr(acks(i).clid, &decbuf[20])
if acks(i).success then
lib.report('instance #',num,' reports successful ',rep)
else
lib.report('instance #',num,' reports failed ',rep)
end
end
end
local terra gen_cfstr(cfmstr: rawstring, seed: intptr)
var confirmstrs = array(
'alpha', 'beta', 'gamma', 'delta', 'epsilon', 'eta', 'nu', 'kappa',
'emerald', 'carnelian', 'sapphire', 'ruby', 'amethyst'
)
var tdx = lib.osclock.time(nil) / 60
cfmstr[0] = 0
for i=0,3 do
if i ~= 0 then lib.str.cat(cfmstr, '-') end
lib.str.cat(cfmstr, confirmstrs[(seed ^ tdx ^ (173*i)) % [confirmstrs.type.N]])
end
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 {
emperor = &emp, srv = &srv;
src = nil, sid = 0, iname = nil, all = false;
}
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, if one is indicated
end
if mode.instance_id ~= nil and mode.instance_serial ~= nil then
lib.bail('conflicting flags passed')
end
if mode.instance_id ~= nil then
dlg.iname = @mode.instance_id
elseif mode.instance_serial ~= nil then
-- decode serial
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
var smode: lib.cmdparse {
help = {'h', 'display this list'};
log = {'l', 'send server\'s logging output to a file', consume=1};
pid = {'p', 'report PID of launched process'};
instance_serial = {'s', 'report the instance serial of the launched process'};
keep_attached = {'k', 'don\'t detach from the calling terminal until a successful startup (or ever, if passed twice)', inc=1};
}
smode:parse(mode.arglist.ct,mode.arglist.ptr)
if smode.help then
[ lib.emit(false, 1, 'usage: ', `argv[0], ' start ', smode.type.helptxt.flags, ' [<instance args>…] [-- <instance flags>…]', smode.type.helptxt.opts) ]
return 1
end
var lsr = lib.ipc.listener.mk()
var chpid = lib.proc.fork()
if chpid == 0 then
lsr:release()
var chargv = lib.mem.heapa(rawstring, smode.arglist.ct + 2)
chargv(0) = '-'
for i = 1, chargv.ct - 1 do
chargv(i) = smode.arglist(i-1)
end
chargv(chargv.ct-1) = nil
if smode.keep_attached == 0 then
lib.io.close(0) lib.io.close(1) lib.io.close(2)
end
lib.proc.exec([config.prefix_bin .. '/parsavd'], chargv.ptr)
lib.proc.execp([config.prefix_bin .. '/parsavd'], chargv.ptr)
lib.ipc.notify_parent(lib.ipc.signals.state_fail_find)
lib.bail('cannot find parsav program')
-- chargv:free()
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
if mode.arglist.ct ~= 1 then goto cmderr end
var acks = dlg:ipc_send(lib.ipc.cmd.stop, 0)
if acks:ref() then
ipc_report(acks, 'step-down')
acks:free()
end
elseif lib.str.cmp(mode.arglist(0),'ls') == 0 then
if mode.arglist.ct ~= 1 then goto cmderr end
if dlg.sid == 0 and dlg.iname == nil then dlg.all = true end
var acks = dlg:ipc_send(lib.ipc.cmd.enumerate, 0)
if acks:ref() then
for i=0,acks.ct do
var decbuf: int8[21]
var num = lib.math.decstr(acks(i).clid, &decbuf[20])
[ lib.emit(true,1, '\27[1m(', `num, ')\27[m ',`&acks(i).iname[0],' \27[1;32mactive\27[m') ]
end
acks:free()
else lib.bail('no active instances') end
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))
dlg:tx_enter()
if dlg:dbsetup() then
srv:conprep(lib.store.prepmode.conf)
do var newkp = lib.crypt.genkp()
-- generate server privkey
var kbuf: uint8[lib.crypt.const.maxdersz]
var derkey = lib.crypt.der(false,&newkp, kbuf)
dlg:server_setup_self(dbmode.arglist(1), derkey)
end
dlg:conf_set('instance-name', dbmode.arglist(1))
dlg:conf_set('domain', dbmode.arglist(1))
do var sec: int8[65] gensec(&sec[0])
dlg:conf_set('server-secret', &sec[0])
end
lib.report('database setup complete; use mkroot to create an administrative user')
else lib.bail('initialization process interrupted') end
dlg:tx_complete()
elseif lib.str.cmp(dbmode.arglist(0),'obliterate') == 0 then
var cfmstr: int8[64] gen_cfstr(&cfmstr[0],0)
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!')
dlg:obliterate_everything()
elseif lib.str.cmp(dbmode.arglist(1), 'print-confirmation-string') == 0 then
lib.io.send(1, cfmstr, lib.str.sz(cfmstr))
else
lib.bail('you passed an incorrect confirmation string; pass ', &cfmstr[0], ' if you really want to destroy everything')
end
else goto cmderr end
else goto cmderr end
elseif lib.str.cmp(mode.arglist(0),'be') == 0 then
srv:setup(cnf)
elseif lib.str.cmp(mode.arglist(0),'conf') == 0 then
srv:setup(cnf)
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])
dlg:conf_set('server-secret', &sec[0])
lib.report('server secret reset')
elseif lib.str.cmp(cfmode.arglist(0),'refresh') == 0 then
cfmode.no_notify = false -- duh
else goto cmderr end
elseif cfmode.arglist.ct == 2 and
lib.str.cmp(cfmode.arglist(0),'reset') == 0 or
lib.str.cmp(cfmode.arglist(0),'clear') == 0 or
lib.str.cmp(cfmode.arglist(0),'unset') == 0 then
dlg:conf_reset(cfmode.arglist(1))
lib.report('parameter cleared')
elseif cfmode.arglist.ct == 3 and
lib.str.cmp(cfmode.arglist(0),'set') == 0 then
dlg:conf_set(cfmode.arglist(1),cfmode.arglist(2))
lib.report('parameter set')
else goto cmderr end
-- successful commands fall through
if not cfmode.no_notify then
dlg:ipc_send(lib.ipc.cmd.cfgrefresh,0)
end
else
srv:setup(cnf)
srv:conprep(lib.store.prepmode.full)
if lib.str.cmp(mode.arglist(0),'mkroot') == 0 then
var cfmode: pbasic cfmode:parse(mode.arglist.ct, &mode.arglist(0))
if cfmode.help then
[ lib.emit(false, 1, 'usage: ', `argv[0], ' mkroot ', cfmode.type.helptxt.flags, ' <handle>', cfmode.type.helptxt.opts) ]
return 1
end
if cfmode.arglist.ct == 1 then
var am = dlg:conf_get('credential-store')
var mg: bool
if (not am) or am:cmp('managed') then
mg = true
elseif am:cmp('unmanaged') then
lib.warn('credential store is unmanaged; you will need to create credentials for the new root user manually!')
mg = false
else lib.bail('unknown credential store mode "',{am.ptr,am.ct},'"; should be either "managed" or "unmanaged"') end
var kbuf: uint8[lib.crypt.const.maxdersz]
var root = lib.store.actor.mk(&kbuf[0])
root.handle = cfmode.arglist(0)
var epithets = array(
'root', 'god', 'regional jehovah', 'titan king',
'king of olympus', 'cyberpharaoh', 'electric ellimist',
"rampaging c'tan", 'deathless tweetlord', 'postmaster',
'faerie queene', 'lord of the posts', 'ruthless cybercrat',
'general secretary', 'commissar', 'kwisatz haderach',
'dedicated hyperturing', 'grand inquisitor', 'reverend mother',
'cyberpope', 'verified®', 'patent pending'
-- feel free to add more
)
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),'actor') == 0 then
var umode: pbasic umode:parse(mode.arglist.ct, &mode.arglist(0))
if umode.help then
[ lib.emit(false, 1, 'usage: ', `argv[0], ' actor ', umode.type.helptxt.flags, ' <xid> <cmd> [<args>…]', umode.type.helptxt.opts, cmdhelp {
{ 'actor <xid> rank <value>', 'set an actor\'s rank to <value> (remote actors cannot exercise rank-related powers, but benefit from rank immunities)' };
{ 'actor <xid> degrade', 'alias for `actor <xid> rank 0`' };
{ 'actor <xid> bestow <epithet>', 'bestow an epithet upon an actor' };
{ 'actor <xid> instantiate', 'instantiate a remote actor, retrieving their profile and posts even if no one follows them' };
{ 'actor <xid> proscribe', 'globally ban an actor from interacting with your server' };
{ 'actor <xid> rehabilitate', 'lift a proscription on an actor' };
{ 'actor <xid> purge-all <confirm-str>', '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)' };
}) ]
return 1
end
if umode.arglist.ct >= 2 then
var degrade = lib.str.cmp(umode.arglist(1),'degrade') == 0
var xid = umode.arglist(0)
var usr = dlg:actor_fetch_xid(pstr {ptr=xid, ct=lib.str.sz(xid)})
if not usr then lib.bail('no such actor') end
if degrade or lib.str.cmp(umode.arglist(1),'rank') == 0 then
var rank: uint16
if degrade and umode.arglist.ct == 2 then
rank = 0
elseif (not degrade) and umode.arglist.ct == 3 then
var r, ok = lib.math.decparse(pstr {
ptr = umode.arglist(2);
ct = lib.str.sz(umode.arglist(2));
})
if not ok then goto cmderr end
rank = r
else goto cmderr end
usr.ptr.rights.rank = rank
dlg:actor_save(usr.ptr)
lib.report('set user rank')
elseif umode.arglist.ct == 3 and lib.str.cmp(umode.arglist(1),'bestow') == 0 then
if umode.arglist(2)[0] == 0
then usr.ptr.epithet = nil
else usr.ptr.epithet = umode.arglist(2)
end
dlg:actor_save(usr.ptr)
lib.report('bestowed a new epithet on ', usr.ptr.xid)
elseif lib.str.cmp(umode.arglist(1),'purge-all') == 0 then
var cfmstr: int8[64] gen_cfstr(&cfmstr[0],usr.ptr.id)
if umode.arglist.ct == 2 then
lib.bail('you are attempting to completely purge the actor ', usr.ptr.xid, ' and all related content from the database! if you really want to do this, pass the confirmation string ', &cfmstr[0])
elseif umode.arglist.ct == 3 then
if lib.str.ncmp(&cfmstr[0],umode.arglist(2),64) ~= 0 then
lib.bail('you have supplied an invalid confirmation string; if you really want to purge this actor, pass ', &cfmstr[0])
end
lib.warn('completely purging actor ', usr.ptr.xid, ' and all related content from database')
dlg:actor_purge_uid(usr.ptr.id)
lib.report('actor purged')
else goto cmderr end
else goto cmderr 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> create', 'add a new user' };
{ '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 and rank 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
var handle = umode.arglist(0)
var usr = dlg:actor_fetch_xid(pstr {ptr=handle, ct=lib.str.sz(handle)})
if umode.arglist.ct == 2 and lib.str.cmp(umode.arglist(1),'create')==0 then
if usr:ref() then lib.bail('that user already exists') end
if not lib.store.actor.handle_validate(handle) then
lib.bail('invalid user handle') end
var kbuf: uint8[lib.crypt.const.maxdersz]
var na = lib.store.actor.mk(&kbuf[0])
na.handle = handle
dlg:actor_create(&na)
lib.report('created new user @',na.handle,'; assign credentials to enable login')
elseif umode.arglist.ct >= 3 then
var grant = lib.str.cmp(umode.arglist(1),'grant') == 0
if not usr then lib.bail('no such user') end
if grant or lib.str.cmp(umode.arglist(1),'revoke') == 0 then
var newprivs = usr.ptr.rights.powers
var map = array([lib.store.powmap])
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.val
else
lib.dbg('disabling power ', {p.name.ptr,p.name.ct})
newprivs = newprivs - p.val
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
-- 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
end
end
do return 0 end
::cmderr:: lib.bail('invalid command')
end
return entry_mgtool