Recipe 23.9. Normalizing Ownership and Permissions in User Directories
Problem
You want to make make sure your users' home directories don't contain world-writable directories, directories owned by other users, or other potential security problems.
Solution
Use the etc library to look up a user's home directory and UID from the username. Then use Find.find to walk the directory trees, and File methods to check and modify access to each file.
We are looking out for any case where one user's home directory can be modified by some other user. Whenever we find such a case, we fix it with a File.chmod or File.chown call. In this program, the actual calls are commented out, so that you don't accidentally change your
permissions when you just want to test out the program.
#!/usr/bin/ruby -w
# normalize_homes.rb
require 'etc'
require 'find'
require 'optparse'
def normalize_home(pwd_entry, maximum_perms=0775, dry_run=true)
uid, home = pwd_entry.uid, pwd_entry.dir
username = pwd_entry.name
puts "Scanning #{username}'s home of #{home}."
Find.find(home) do |f|
next unless File.exists? f
stat = File.stat(f)
file_uid, file_gid, mode = stat.uid, stat.gid, stat.mode
The most obvious thing we want to check is whether the user owns every file in their home directory. With occasional exceptions (such as files owned by the web server), a user should own the files in his or her home directory:
# Does the user own the file?
if file_uid != uid
begin
current_owner = Etc.getpwuid(file_uid).name
rescue ArgumentError # No such user; just use UID
current_owner = "uid #{file_uid}"
end
puts " CHOWN #{f}"
puts " Current owner is #{current_owner}, should be #{username}"
# File.chown(uid, nil, f) unless dry_run
end
A less obvious check involves the Unix group that owns the file. A user can let other people work on a file in their home directory by giving
ownership to a user group. But you can only give
ownership to a group if you're a member of that group. If a user's home directory contains a file owned by a group the user doesn't belong to, something fishy is probably going on.
# Does the user belong to the group that owns the file?
begin
group = Etc.getgrgid(file_gid)
group_name = group.name
rescue ArgumentError # No such group
group_name = "gid #{file_gid}"
end
unless group && (group.mem.member?(username) || group.name == username)
puts " CHGRP #{f}"
puts " Current group is #{group_name}, and #{username} doesn't belong."
# File.chown(nil, uid, f) unless dry_run
end
Finally, we'll check each file's
permissions and make sure they are no more permissive than the value passed in as maximum_perms. The default value of 0775 allows any kind of file except a world-writable file. If normalize_home finds a world-writable file, it will flip the world-writable bit and leave the rest of the
permissions alone:
# Does the file have more than the maximum allowed permissions?
perms = mode & 0777 # Drop non-permission bits
should_be = perms & maximum_perms
if perms != should_be
puts " CHMOD #{f}"
puts " Current perms are #{perms.to_s(8)}, " +
"should be #{should_be.to_s(8)}"
# File.chmod(perms & maximum_perms, f) unless dry_run
end
end
end
All that's left to do is a simple command-line interface to the normalize_home method:
dry_run = false
opts = OptionParser.new do |opts|
opts.on("-D", "--dry-run",
"Display changes to be made, don't make them.") do
dry_run = true
end
opts.on_tail("-h", "--help", "display this help and exit") do
puts opts
exit
end
end
opts.banner = "Usage: #{__FILE__} [--dry-run] username [username2, …]"
opts.parse!(ARGV)
# Make sure all the users exist.
pwd_entries = ARGV.collect { |username| Etc.getpwnam(username) }
# Normalize all given home
directories.
pwd_entries.each { |p| normalize_home(p, 0775, dry_run ) }
Discussion
Running this script on my home directory shows over 2,500 problems. These are mostly files owned by root, files owned by UIDs that don't exist on my system (these come from tarballs), and world-writable files. Below I give a sample of the embarrassment:
$ ruby -D normalize_homes.rb leonardr
Scanning leonardr's home of /home/leonardr.
CHOWN /home/leonardr/writing/Ruby Cookbook/sys-proctable-0.7.3/proctable.so
Current owner is root, should be leonardr
CHGRP /home/leonardr/writing/Ruby Cookbook/sys-proctable-0.7.3/proctable.so
Current group is root, and leonardr doesn't belong.
…
CHOWN /home/leonardr/writing/Ruby Cookbook/rubygems-0.8.4/lib/rubygems.rb
Current owner is uid 501, should be leonardr
CHGRP /home/leonardr/writing/Ruby Cookbook/rubygems-0.8.4/lib/rubygems.rb
Current group is gid 501, and leonardr doesn't belong.
…
CHMOD /home/leonardr/SORT/gogol-home-2002/mail
Current perms are 722, should be 720
…
Running the script as root (and with the File.chmod and File.chown calls uncommented) fixes all the problems.
You can run the script as yourself to check your own home directory, and it'll fix permission problems on files you own. But if a file is owned by someone else, you can't take it back just because it's in your home directorythat's part of the problem with having a file owned by someone else in your home directory.
As usual with system administration scripts, normalize.homes.rb is only a starting point. You'll probably need to adapt this program to your specific purposes. For instance, you may want to leave certain files alone, especially files owned by root (who can modify anyone's home directory anyway) or by system processes such as the web server (usually user apache, httpd, or nobody).
See Also
 |