summaryrefslogtreecommitdiff
path: root/src/share/checkperms
blob: 3f8ad567baebb03ac442fbd3ecd8169d76bb83df (plain)
  1. #!/usr/bin/perl -T
  2. # checkperms: ensure as best we can that a given file can only be
  3. # modified by the given user (or the superuser, naturally). This
  4. # means checking file ownership and permissions all the way back to
  5. # the root directory. Pass the file by its absolute path.
  6. # example invocation:
  7. # checkperms dkg /home/dkg/.monkeysphere/authorized_user_ids
  8. # return values: zero if we believe the file and path can only be
  9. # modified by the user. non-zero otherwise.
  10. # see StrictModes in sshd_config(5) (and its implementation in
  11. # OpenSSH's secure_filename() in auth.c) for the initial
  12. # inspiration/rationale for this code.
  13. # Author:
  14. # Daniel Kahn Gillmor <dkg@fifthhorseman.net>
  15. # Started on: 2009-07-31 11:10:16-0400
  16. # License: GPL v3 or later
  17. use strict;
  18. use Cwd qw(realpath); # found in debian in perl-base
  19. use File::stat; # found in debian in perl-modules
  20. use User::pwent; # found in debian in perl-modules
  21. use Fcntl qw(:mode); # for S_IS* functions (in perl-base)
  22. use File::Basename; # for dirname (in perl-modules)
  23. my $username = shift;
  24. my $path = shift;
  25. defined($username) or die "You must pass a username and an absolute path.\n";
  26. defined($path) or die "You must pass a username and an absolute path.\n";
  27. my $pw = getpwnam($username) or die "no such user $username\n";
  28. $path =~ m#^/# or die "path was not absolute (did not start with /)\n";
  29. sub mslog {
  30. my $level = shift;
  31. # FIXME: check and compare the log level
  32. if ($ENV{LOG_LEVEL} eq 'DEBUG') {
  33. my $format = shift;
  34. my $out = sprintf($format, @_);
  35. $out =~ s/^/$ENV{LOG_PREFIX}/ ;
  36. printf STDERR "%s", $out;
  37. }
  38. }
  39. ## return undef if permissions are OK. otherwise return an error string
  40. sub permissions_ok {
  41. my $user = shift;
  42. my $path = shift;
  43. # if we can't even stat the path, the permissions are not ok:
  44. my $stat = lstat($path) or return "cannot stat '$path'";
  45. while (S_ISLNK($stat->mode)) {
  46. my $newpath = realpath($path) or return "cannot trace symlink '$path'";
  47. mslog('DEBUG', "tracing link %s to %s\n", $path, $newpath);
  48. $path = $newpath;
  49. $stat = lstat($path) or return "cannot stat '$path'";
  50. }
  51. mslog('DEBUG', "checking '%s'\n", $path);
  52. if (($stat->uid != $user->uid) &&
  53. ($stat->uid != 0)) {
  54. return sprintf("improper ownership on '%s': owner ID %d is neither %s (ID %d) nor the superuser",
  55. $path, $stat->uid, $user->name, $user->uid);
  56. }
  57. if ($stat->mode & S_IWGRP) {
  58. return sprintf("improper group writability on '%s'", $path);
  59. }
  60. if ($stat->mode & S_IWOTH) {
  61. return sprintf("improper other writability on '%s'", $path);
  62. }
  63. # see the rationalization in secure_filename() in auth.c in the
  64. # OpenSSH sources for an explanation of this bailout (see also
  65. # monkeysphere #675):
  66. if ($path eq $user->dir) {
  67. mslog('DEBUG', "stopping at %s's home directory '%s'\n", $user->name, $path);
  68. return undef;
  69. }
  70. my $nextlevel = dirname($path);
  71. if ($path eq $nextlevel) { # we bottom out at the root (/ in UNIX)
  72. return undef;
  73. }
  74. return permissions_ok($user, $nextlevel);
  75. }
  76. my $err = permissions_ok($pw, $path);
  77. if (defined($err)) {
  78. printf(STDERR "%s%s\n", $ENV{LOG_PREFIX}, $err);
  79. exit(1);
  80. } else {
  81. exit(0);
  82. }