service: add user run
This enables to run commands as other users, like sudo. This is useful for eg fixing a user networks on their behalf.
This commit is contained in:
parent
e7a06fe208
commit
b29c9ef09a
60
service.go
60
service.go
@ -128,7 +128,10 @@ func handleServicePRIVMSG(ctx *serviceContext, text string) {
|
||||
ctx.print(fmt.Sprintf(`error: failed to parse command: %v`, err))
|
||||
return
|
||||
}
|
||||
handleServiceCommand(ctx, words)
|
||||
}
|
||||
|
||||
func handleServiceCommand(ctx *serviceContext, words []string) {
|
||||
cmd, params, err := serviceCommands.Get(words)
|
||||
if err != nil {
|
||||
ctx.print(fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
|
||||
@ -285,6 +288,12 @@ func init() {
|
||||
desc: "delete a user",
|
||||
handle: handleUserDelete,
|
||||
},
|
||||
"run": {
|
||||
usage: "<username> <command>",
|
||||
desc: "run a command as another user",
|
||||
handle: handleUserRun,
|
||||
admin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
"channel": {
|
||||
@ -1047,6 +1056,57 @@ func handleUserDelete(ctx *serviceContext, params []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleUserRun(ctx *serviceContext, params []string) error {
|
||||
if !ctx.user.Admin {
|
||||
return fmt.Errorf("only admins may run command as other users")
|
||||
}
|
||||
if len(params) < 2 {
|
||||
return fmt.Errorf("expected at least two arguments")
|
||||
}
|
||||
|
||||
username := params[0]
|
||||
params = params[1:]
|
||||
if username == ctx.user.Username {
|
||||
handleServiceCommand(ctx, params)
|
||||
return nil
|
||||
}
|
||||
|
||||
u := ctx.user.srv.getUser(username)
|
||||
if u == nil {
|
||||
return fmt.Errorf("unknown username %q", username)
|
||||
}
|
||||
|
||||
printCh := make(chan string, 1)
|
||||
ev := eventUserRun{
|
||||
params: params,
|
||||
print: printCh,
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case u.events <- ev:
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// This handles a possible race condition:
|
||||
// - we send ev to u.events
|
||||
// - the user goroutine for u stops (because of a crash or user deletion)
|
||||
// - we would block on printCh
|
||||
// Quitting on ctx.Done() prevents us from blocking indefinitely
|
||||
// in case the event is never processed.
|
||||
// TODO: Properly fix this condition by flushing the u.events queue
|
||||
// and running close(ev.print) in a defer
|
||||
return nil
|
||||
case text, ok := <-printCh:
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
ctx.print(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleServiceChannelStatus(ctx *serviceContext, params []string) error {
|
||||
var defaultNetworkName string
|
||||
if ctx.network != nil {
|
||||
|
21
user.go
21
user.go
@ -82,6 +82,11 @@ type eventTryRegainNick struct {
|
||||
nick string
|
||||
}
|
||||
|
||||
type eventUserRun struct {
|
||||
params []string
|
||||
print chan string
|
||||
}
|
||||
|
||||
type deliveredClientMap map[string]string // client name -> msg ID
|
||||
|
||||
type deliveredStore struct {
|
||||
@ -769,6 +774,22 @@ func (u *user) run() {
|
||||
}
|
||||
case eventTryRegainNick:
|
||||
e.uc.tryRegainNick(e.nick)
|
||||
case eventUserRun:
|
||||
ctx := context.TODO()
|
||||
handleServiceCommand(&serviceContext{
|
||||
Context: ctx,
|
||||
user: u,
|
||||
print: func(text string) {
|
||||
// Avoid blocking on e.print in case our context is canceled.
|
||||
// This is a no-op right now because we use context.TODO(),
|
||||
// but might be useful later when we add timeouts.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case e.print <- text:
|
||||
}
|
||||
},
|
||||
}, e.params)
|
||||
close(e.print)
|
||||
case eventStop:
|
||||
for _, dc := range u.downstreamConns {
|
||||
dc.Close()
|
||||
|
Loading…
Reference in New Issue
Block a user