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