...
Tawesoft Logo

Source file src/tawesoft.co.uk/go/drop/drop.go

Documentation: src/tawesoft.co.uk/go/drop/drop.go

     1  package drop
     2  
     3  import (
     4      "context"
     5      "fmt"
     6      "os"
     7      "os/exec"
     8      "os/signal"
     9      "runtime"
    10      "syscall"
    11  )
    12  
    13  // Drop works in two ways, depending on the privileges of the current process.
    14  //
    15  // As the superuser (root), drop executes a new copy of the running program as
    16  // the given user, blocks until that child program exits, then returns.
    17  //
    18  // As the child process, drop returns without blocking. The current stdio
    19  // streams, and zero-or-more "inheritable" files, are persisted and inherited
    20  // by the new process.
    21  //
    22  // If the first return argument is true, the caller is the root process and
    23  // should exit immediately (e.g. by returning from main(), with os.Exit(), etc.)
    24  //
    25  // The second return argument is a function. It is nil if the third argument
    26  // is an error. In the child process, this calls the Close() method on each of
    27  // the supplied Inheritable files. The caller may either Close() each
    28  // Inheritable manually or typically simply defer this returned function. The
    29  // returned []error argument from this function contains the errors returned by
    30  // Inheritable Close() methods. In the root process, this returned function is
    31  // always a no-op and does not need to be called.
    32  func Drop(username string, files ... Inheritable,) (bool, func() []error, error) {
    33      if runtime.GOOS == "windows" { panic("drop does not work on Windows") }
    34  
    35      // no way to exit without supervising in a way that is safe against PID
    36      // reuse.
    37      const supervise = true
    38  
    39      closer := func(files ... Inheritable) []error {
    40          errors := make([]error, 0)
    41          for _, i := range files {
    42              err := i.Close()
    43              if err != nil { errors = append(errors, err) }
    44          }
    45          return errors
    46      }
    47  
    48      handles := make([]*os.File, 0, len(files))
    49  
    50      if IsSuperuser() { // drop
    51          uid, gid, groups, err := UserLookup(username)
    52          if err != nil { return false, nil, fmt.Errorf("user lookup error for %s: %v", username, err) }
    53          if uid == 0 { return false, nil, fmt.Errorf("cannot drop to root") }
    54  
    55          defer closer(files...)
    56  
    57          // convert []int groups to []uint32
    58          // for cmd.SysProcAttr.Credential
    59          ugroups := make([]uint32, len(groups))
    60          for i, _ := range groups {
    61              ugroups[i] = uint32(groups[i])
    62          }
    63  
    64          for _, file := range files {
    65              handle, err := file.Open()
    66              if err != nil {
    67                  return false, nil, fmt.Errorf("error opening file while privileged: %v", err)
    68              }
    69              handles = append(handles, handle)
    70          }
    71  
    72          args := os.Args
    73          cmd := exec.Command(args[0], args[1:]...)
    74  
    75          if supervise {
    76              cmd.Stdin  = os.Stdin
    77              cmd.Stdout = os.Stdout
    78              cmd.Stderr = os.Stderr
    79          }
    80  
    81          cmd.ExtraFiles = handles
    82          cmd.SysProcAttr = &syscall.SysProcAttr{}
    83          cmd.SysProcAttr.Credential = &syscall.Credential{
    84              Uid: uint32(uid),
    85              Gid: uint32(gid),
    86              Groups: ugroups,
    87          }
    88  
    89          // Let the parent process recover if the child process is killed
    90          // so that we can
    91          // e.g. so that we can close any open sockets
    92          ctx, cancel := context.WithCancel(context.Background())
    93          defer cancel()
    94  
    95          go func(ctx context.Context) {
    96              sigchan := make(chan os.Signal, 1)
    97              signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM)
    98  
    99              select {
   100                  case <- sigchan:
   101                  case <- ctx.Done():
   102              }
   103  
   104              signal.Stop(sigchan)
   105          }(ctx)
   106  
   107          err = cmd.Start()
   108          if err != nil {
   109              return false, nil, fmt.Errorf("error dropping privileges: %v", err)
   110          }
   111  
   112          if supervise {
   113              err = cmd.Wait()
   114              if err != nil {
   115                  return false, nil, fmt.Errorf("child process exited with error: %v", err)
   116              }
   117          }
   118  
   119          closer(files...)
   120          return true, func() []error { return []error{} }, nil
   121      } else { // inherit
   122  
   123          for i := 0; i < len(files); i++ {
   124              name := files[i].String()
   125              handle := os.NewFile(uintptr(3 + i), name)
   126              if handle == nil {
   127                  closer(files...)
   128                  return false, nil, fmt.Errorf("missing file handle for %s", name)
   129              }
   130              handles = append(handles, handle)
   131          }
   132  
   133          for i := 0; i < len(files); i++ {
   134              name := files[i].String()
   135              err := files[i].Inherit(handles[i])
   136              if err != nil {
   137                  closer(files...)
   138                  return false, nil, fmt.Errorf("error inheriting file %s %v", name, err)
   139              }
   140          }
   141  
   142          return false, func() []error { return closer(files...) }, nil
   143      }
   144  }
   145  

View as plain text