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))
|
ctx.print(fmt.Sprintf(`error: failed to parse command: %v`, err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
handleServiceCommand(ctx, words)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleServiceCommand(ctx *serviceContext, words []string) {
|
||||||
cmd, params, err := serviceCommands.Get(words)
|
cmd, params, err := serviceCommands.Get(words)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.print(fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
|
ctx.print(fmt.Sprintf(`error: %v (type "help" for a list of commands)`, err))
|
||||||
@ -285,6 +288,12 @@ func init() {
|
|||||||
desc: "delete a user",
|
desc: "delete a user",
|
||||||
handle: handleUserDelete,
|
handle: handleUserDelete,
|
||||||
},
|
},
|
||||||
|
"run": {
|
||||||
|
usage: "<username> <command>",
|
||||||
|
desc: "run a command as another user",
|
||||||
|
handle: handleUserRun,
|
||||||
|
admin: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"channel": {
|
"channel": {
|
||||||
@ -1047,6 +1056,57 @@ func handleUserDelete(ctx *serviceContext, params []string) error {
|
|||||||
return nil
|
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 {
|
func handleServiceChannelStatus(ctx *serviceContext, params []string) error {
|
||||||
var defaultNetworkName string
|
var defaultNetworkName string
|
||||||
if ctx.network != nil {
|
if ctx.network != nil {
|
||||||
|
21
user.go
21
user.go
@ -82,6 +82,11 @@ type eventTryRegainNick struct {
|
|||||||
nick string
|
nick string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type eventUserRun struct {
|
||||||
|
params []string
|
||||||
|
print chan string
|
||||||
|
}
|
||||||
|
|
||||||
type deliveredClientMap map[string]string // client name -> msg ID
|
type deliveredClientMap map[string]string // client name -> msg ID
|
||||||
|
|
||||||
type deliveredStore struct {
|
type deliveredStore struct {
|
||||||
@ -769,6 +774,22 @@ func (u *user) run() {
|
|||||||
}
|
}
|
||||||
case eventTryRegainNick:
|
case eventTryRegainNick:
|
||||||
e.uc.tryRegainNick(e.nick)
|
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:
|
case eventStop:
|
||||||
for _, dc := range u.downstreamConns {
|
for _, dc := range u.downstreamConns {
|
||||||
dc.Close()
|
dc.Close()
|
||||||
|
Loading…
Reference in New Issue
Block a user