On Tue, 25 Nov 2008, Dave Whipp wrote:
> Brandon S. Allbery KF8NH wrote:
> > Still misunderstanding, I think.  Yes, it will fail anyway, but in the
> > general case you're checking to see if as a privileged process it is safe to
> > operate on a given file.
>
> I'd actually been thinking that one would use the check in the opposite
> direction:
>
> if need_priv() {
>   sudo { ... }; # might ask user for passwd
> }
> else {
>   ...
> }
>
> But then I'm not an expert in this area: I just want an API that makes it easy
> to hack simple scripts that do what I need. It was C<chmod> that I really
> wanted to improve the API for; I touched C<chown> only because it was adjacent
> in the POD, and it seems reasonable for the two functions/methods to have
> similar look&feel.

I've been wondering about this idiom, used when copying files:

  my $s = POSIX::stat($filename1);
  POSIX_extensions::setstat($filename2, $s);

where setstat() is a combination of chown, chmod and utime -- in the right
order to make them work as best as possible. The type of check contemplated
in this thread would then come in handy for implementing setstat().

If an unprivileged user can give away a file then that change has to be the
last thing done to the file. Otherwise changing the permissions should be
done last, so as to maintain the set*id bits. So I would envisage it would
look a bit like this (please excuse my obvious hand-waving pseudocode):

sub setstat(String|File $filename, StatBuf $stat) {
  use fatal :everything;

  if $stat.uid != any(POSIX::getuid(), POSIX::geteuid()) |
     $stat.gid != any(POSIX::getgid(), POSIX::getegid(), POSIX::getgroups()) {

    # we need to give the file away

    my $caps = POSIX_1e::capget();

    if $caps.CAP_FOWNER {
      # we're privileged, so it *should* just work.
      POSIX::chown $filename, $stat.uid, $stat.gid;
      POSIX::chmod $filename, Fcntl::ST_PERM($stat.mode);
      POSIX::utime $filename, $stat.mtime, $stat.atime;
      return;
    }
    if $caps.CAP_CHOWN | !chown.need_priv() {
      # we have enough privilege to give the file away, but not enough to
      # touch it after we've done so.
      POSIX::chmod $filename, Fcntl::ST_PERM($stat.mode);
      POSIX::utime $filename, $stat.mtime, $stat.atime;
      POSIX::chown $filename, $stat.uid, $stat.gid;
      return;
    }
    if catching_exception(IO_error) {
      throw IO_error(EPERM, $filename, "Can't give file away");
    }
  }
  # can't (or don't need to) give file away
  POSIX::chmod $filename, Fcntl::ST_PERM($stat.mode);
  POSIX::utime $filename, $stat.mtime, $stat.atime;
}

-Martin

Reply via email to