#!/usr/bin/ruby # # Copyright (c) 2004 Peter Palfrader # # All rights reserved. # require "ldap" require "getoptlong" require "myldap" require "yaml" def fatal(reason) STDERR.puts reason exit 1; end # read mtab # # /dev/sda11 /aux xfs rw 0 0 # /chroot-accounts/MasterChroot/bin /chroot-accounts/weasel.weasel/bin none rw,bind 0 0 # def getmounts() mounts = Hash.new IO.foreach('/etc/mtab') { |line| (device, mountpoint, fstype, option, dummy1, dummy2) = line.split next unless mountpoint.index(@spool) fatal("Unexpected option or fstype in "+line) if (!(option =~ /bind/) || (fstype != 'none')) # check mountpoint in detail components = mountpoint.gsub(/^\//, '').split('/'); fatal("Weird mountpoint (not 3 components) in "+line) unless (components.size == 3) fatal("1st componend of mountpoint is not sane in "+line) unless ('/' + components[0] == @spool) fatal("2nd componend of mountpoint is not sane in "+line) unless (components[1] =~ /^[a-z][a-z0-9-]*(\.[a-z][a-z0-9-]*)+$/) fatal("3rd componend of mountpoint is not sane in "+line) unless (["bin", "usr", "lib", components[1]].include?(components[2])) mounts[components[1]] = Hash.new unless mounts[components[1]] mounts[components[1]][components[2]] = device } mounts end @config = YAML::load( File.open( '/etc/noreply/config' ) ) masterName = @config['module']['ftp']['masterName'] @spool = (@config['module']['ftp']['chrootBaseDir']).gsub('//', '/') @spool.chop! if @spool =~ /\/$/ @master = @spool + '/' + masterName + '/' ldap = MyLDAP.new(@config, "ldap2chroot-accounts") accounts = Hash.new ldap.conn.search2(@config['basedn'], LDAP::LDAP_SCOPE_SUBTREE, '(&(objectclass=tnFTPuser)(tnHost='+@config['thishost']+'))').each{ |e| accounts[ e['uid'][0] ] = e } mounts = getmounts # create all account directories accounts.each_key{ |homedir| dir = @spool + '/' + homedir Dir.mkdir(dir) unless File.exists?(dir) } existingdirs = Dir.entries( @spool ).delete_if { |e| ((e =~ /^\./) != nil) } existingdirs.delete( masterName ) # Iterate over all existing directories # checking up on mounts, and update them if necessary existingdirs.each { |homedir| basedir = @spool + '/' + homedir if accounts[homedir] # Check bin, usr, and lib mounts # also, the user's datadir is in a directory with the same name as her account subdirs = %w(bin usr lib) subdirs << homedir subdirs.each { |subdir| supposedLocation = subdir == homedir ? accounts[homedir]['tnFTPDataDirectory'][0] : @master + subdir mountpoint = basedir + '/' + subdir # umount if it's mounted in the wrong place if mounts[homedir] && mounts[homedir][subdir] if mounts[homedir][subdir] != supposedLocation STDERR.puts("warn: Directory "+mountpoint+" is bind mounted to the wrong place: "+mounts[homedir][subdir]+". Should be "+supposedLocation+" remounting.") system("umount " + mountpoint) or fatal("umount for "+mountpoint+" failed"); mounts[homedir].delete(subdir) end end # mount if it's not mounted if !(mounts[homedir] && mounts[homedir][subdir]) Dir.mkdir(mountpoint) unless File.exists?(mountpoint) system("mount -o bind "+ supposedLocation + " " + mountpoint) or fatal("mount for "+mountpoint+" failed"); end } # create etc etcdir = basedir + '/etc' Dir.mkdir(etcdir, 0111) unless File.exists?(etcdir) uid = accounts[homedir]['uid'][0] uidNumber = accounts[homedir]['uidNumber'][0] gidNumber = accounts[homedir]['gidNumber'][0] passwd = "root:x:0:0:root::\n" + "%s:x:%d:%d:::\n"%[uid, uidNumber, gidNumber] group = "root:x:0:\n" + "%s:x:%s:\n"%[uid, gidNumber] File.open(etcdir+'/passwd', File::CREAT|File::TRUNC|File::RDWR, 0444).write(passwd) File.open(etcdir+'/group', File::CREAT|File::TRUNC|File::RDWR, 0444).write(group) # create .ssh sshdir = basedir + '/.ssh' Dir.mkdir(sshdir, 0111) unless File.exists?(sshdir) authkeys = ''; accounts[homedir]['tnSSHKey'] and accounts[homedir]['tnSSHKey'].each{ |key| key.gsub!('\n', '') authkeys << "no-port-forwarding,no-X11-forwarding,no-agent-forwarding "+key+"\n" } File.open(sshdir+'/authorized_keys', File::CREAT|File::TRUNC|File::RDWR, 0444).write(authkeys) else # clean up old stuff mounts[homedir] and mounts[homedir].each_key{ |subdir| mountpoint = basedir + '/' + subdir system("umount " + mountpoint) or fatal("umount for "+mountpoint+" failed"); Dir.rmdir(mountpoint) } { 'etc' => ['passwd', 'group'], '.ssh' => ['authorized_keys'], homedir => [] }.each{ |dir, files| dir = basedir + '/' + dir if File.exists?(dir) files.each { |f| file = dir + '/' + f File.unlink(file) if File.exists?(file) } Dir.rmdir(dir) end } Dir.rmdir(basedir) end }