From 0c0fe0f7a00a461a05bb1edf53b0e68b59c78cd5 Mon Sep 17 00:00:00 2001 From: Toshio Kuratomi Date: Sun, 9 Mar 2008 23:43:10 -0700 Subject: [PATCH] Almost have install working right. Just have to figure out how to make it choose the right directory for static in both cases of: 1) data directory not specified and 2) data directory specified. --- fas/client/fasClient.py | 495 ---------------------------------------- fas/setup.py | 48 +++- 2 files changed, 40 insertions(+), 503 deletions(-) delete mode 100755 fas/client/fasClient.py diff --git a/fas/client/fasClient.py b/fas/client/fasClient.py deleted file mode 100755 index e8d42df..0000000 --- a/fas/client/fasClient.py +++ /dev/null @@ -1,495 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -# -# Copyright © 2007-2008 Red Hat, Inc. All rights reserved. -# -# This copyrighted material is made available to anyone wishing to use, modify, -# copy, or redistribute it subject to the terms and conditions of the GNU -# General Public License v.2. This program is distributed in the hope that it -# will be useful, but WITHOUT ANY WARRANTY expressed or implied, including the -# implied warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the GNU General Public License for more details. You should have -# received a copy of the GNU General Public License along with this program; -# if not, write to the Free Software Foundation, Inc., 51 Franklin Street, -# Fifth Floor, Boston, MA 02110-1301, USA. Any Red Hat trademarks that are -# incorporated in the source code or documentation are not subject to the GNU -# General Public License and may only be used or replicated with the express -# permission of Red Hat, Inc. -# -# Red Hat Author(s): Mike McGrath -# -# TODO: put tmp files in a 700 tmp dir - -import sys -import logging -import syslog -import os -import tempfile -import codecs -import datetime -import time - -from fedora.tg.client import BaseClient, AuthError, ServerError -from optparse import OptionParser -from shutil import move, rmtree, copytree -from rhpl.translate import _ - -import ConfigParser - -parser = OptionParser() - -parser.add_option('-i', '--install', - dest = 'install', - default = False, - action = 'store_true', - help = _('Download and sync most recent content')) -parser.add_option('-c', '--config', - dest = 'CONFIG_FILE', - default = '/etc/fas.conf', - metavar = 'CONFIG_FILE', - help = _('Specify config file (default "%default")')) -parser.add_option('--nogroup', - dest = 'no_group', - default = False, - action = 'store_true', - help = _('Do not sync group information')) -parser.add_option('--nopasswd', - dest = 'no_passwd', - default = False, - action = 'store_true', - help = _('Do not sync passwd information')) -parser.add_option('--noshadow', - dest = 'no_shadow', - default = False, - action = 'store_true', - help = _('Do not sync shadow information')) -parser.add_option('--nohome', - dest = 'no_home_dirs', - default = False, - action = 'store_true', - help = _('Do not create home dirs')) -parser.add_option('--nossh', - dest = 'no_ssh_keys', - default = False, - action = 'store_true', - help = _('Do not create ssh keys')) -parser.add_option('-s', '--server', - dest = 'FAS_URL', - default = None, - metavar = 'FAS_URL', - help = _('Specify URL of fas server.')) -parser.add_option('-e', '--enable', - dest = 'enable', - default = False, - action = 'store_true', - help = _('Enable FAS synced shell accounts')) -parser.add_option('-d', '--disable', - dest = 'disable', - default = False, - action = 'store_true', - help = _('Disable FAS synced shell accounts')) -parser.add_option('-a', '--aliases', - dest = 'aliases', - default = False, - action = 'store_true', - help = _('Sync mail aliases')) - - -(opts, args) = parser.parse_args() - -log = logging.getLogger('fas') - -try: - config = ConfigParser.ConfigParser() - if os.path.exists(opts.CONFIG_FILE): - config.read(opts.CONFIG_FILE) - elif os.path.exists('fas.conf'): - config.read('fas.conf') - print >> sys.stderr, "Could not open %s, defaulting to ./fas.conf" % opts.CONFIG_FILE - else: - print >> sys.stderr, "Could not open %s." % opts.CONFIG_FILE - sys.exit(5) -except ConfigParser.MissingSectionHeaderError, e: - print >> sys.stderr, "Config file does not have proper formatting - %s" % e - sys.exit(6) - -FAS_URL = config.get('global', 'url').strip('"') - -def _chown(arg, dir_name, files): - os.chown(dir_name, arg[0], arg[1]) - for file in files: - os.chown(os.path.join(dir_name, file), arg[0], arg[1]) - -class MakeShellAccounts(BaseClient): - temp = None - groups = None - people = None - memberships = None - emails = None - group_mapping = {} - - def mk_tempdir(self): - self.temp = tempfile.mkdtemp('-tmp', 'fas-', config.get('global', 'temp').strip('"')) - - def rm_tempdir(self): - rmtree(self.temp) - - def valid_user(self, username): - valid_groups = config.get('host', 'groups').strip('"').split(',') + \ - config.get('host', 'restricted_groups').strip('"').split(',') + \ - config.get('host', 'ssh_restricted_groups').strip('"').split(',') - try: - for group in valid_groups: - if username in self.group_mapping[group]: - return True - except KeyError: - return False - return False - - def ssh_key(self, person): - ''' determine what ssh key a user should have ''' - for group in config.get('host', 'groups').strip('"').split(','): - try: - if person['username'] in self.group_mapping[group]: - return person['ssh_key'] - except KeyError: - print >> sys.stderr, '%s is could not be found in fas but was in your config under "groups"!' % group - continue - for group in config.get('host', 'restricted_groups').strip('"').split(','): - try: - if person['username'] in self.group_mapping[group]: - return person['ssh_key'] - except KeyError: - print >> sys.stderr, '%s is could not be found in fas but was in your config under "restricted_groups"!' % group - continue - for group in config.get('host', 'ssh_restricted_groups').strip('"').split(','): - try: - if person['username'] in self.group_mapping[group]: - command = config.get('users', 'ssh_restricted_app').strip('"') - options = config.get('users', 'ssh_key_options').strip('"') - key = 'command="%s",%s %s' % (command, options, person['ssh_key']) - return key - except KeyError: - print >> sys.stderr, '%s is could not be found in fas but was in your config under "ssh_restricted_groups"!' % group - continue - return 'INVALID\n' - def shell(self, username): - ''' Determine what shell username should have ''' - for group in config.get('host', 'groups').strip('"').split(','): - try: - if username in self.group_mapping[group]: - return config.get('users', 'shell').strip('"') - except KeyError: - print >> sys.stderr, '%s is could not be found in fas but was in your config under "groups"!' % group - continue - for group in config.get('host', 'restricted_groups').strip('"').split(','): - try: - if username in self.group_mapping[group]: - return config.get('users', 'restricted_shell').strip('"') - except KeyError: - print >> sys.stderr, '%s is could not be found in fas but was in your config under "restricted_groups"!' % group - continue - for group in config.get('host', 'ssh_restricted_groups').strip('"').split(','): - try: - if username in self.group_mapping[group]: - return config.get('users', 'ssh_restricted_shell').strip('"') - except KeyError: - print >> sys.stderr, '%s is could not be found in fas but was in your config under "restricted_groups"!' % group - continue - - print >> sys.stderr, 'Could not determine shell for %s. Defaulting to /sbin/nologin' % username - return '/sbin/nologin' - - def install_aliases_txt(self): - move(self.temp + '/aliases', '/etc/aliases') - - def passwd_text(self, people=None): - i = 0 - passwd_file = codecs.open(self.temp + '/passwd.txt', mode='w', encoding='utf-8') - shadow_file = codecs.open(self.temp + '/shadow.txt', mode='w', encoding='utf-8') - os.chmod(self.temp + '/shadow.txt', 00400) - if not self.people: - self.people = self.people_list() - for person in self.people: - username = person['username'] - if self.valid_user(username): - uid = person['id'] - human_name = person['human_name'] - password = person['password'] - home_dir = "%s/%s" % (config.get('users', 'home').strip('"'), username) - shell = self.shell(username) - passwd_file.write("=%s %s:x:%i:%i:%s:%s:%s\n" % (uid, username, uid, uid, human_name, home_dir, shell)) - passwd_file.write("0%i %s:x:%i:%i:%s:%s:%s\n" % (i, username, uid, uid, human_name, home_dir, shell)) - passwd_file.write(".%s %s:x:%i:%i:%s:%s:%s\n" % (username, username, uid, uid, human_name, home_dir, shell)) - shadow_file.write("=%i %s:%s:99999:0:99999:7:::\n" % (uid, username, password)) - shadow_file.write("0%i %s:%s:99999:0:99999:7:::\n" % (i, username, password)) - shadow_file.write(".%s %s:%s:99999:0:99999:7:::\n" % (username, username, password)) - i = i + 1 - passwd_file.close() - shadow_file.close() - - def valid_user_group(self, person_id): - ''' Determine if person is valid on this machine as defined in the - config file. I worry that this is going to be horribly inefficient - with large numbers of users and groups.''' - for member in self.memberships: - for group in self.memberships[member]: - if group['person_id'] == person_id: - return True - return False - - def usernames(self): - usernames = {} - if not self.people: - self.people_list() - for person in self.people: - uid = person['id'] - if self.valid_user_group(uid): - username = person['username'] - usernames[uid] = username - return usernames - - def groups_text(self, groups=None, people=None): - i = 0 - file = open(self.temp + '/group.txt', 'w') - if not groups: - groups = self.group_list() - if not people: - people = self.people_list() - - ''' First create all of our users/groups combo ''' - for person in people: - uid = person['id'] - if self.valid_user_group(uid): - username = person['username'] - file.write("=%i %s:x:%i:\n" % (uid, username, uid)) - file.write("0%i %s:x:%i:\n" % (i, username, uid)) - file.write(".%s %s:x:%i:\n" % (username, username, uid)) - i = i + 1 - - usernames = self.usernames() - for group in groups: - gid = group['id'] - name = group['name'] - try: - ''' Shoot me now I know this isn't right ''' - members = [] - for member in self.memberships[name]: - members.append(usernames[member['person_id']]) - memberships = ','.join(members) - self.group_mapping[name] = members - except KeyError: - ''' No users exist in the group ''' - pass - file.write("=%i %s:x:%i:%s\n" % (gid, name, gid, memberships)) - file.write("0%i %s:x:%i:%s\n" % (i, name, gid, memberships)) - file.write(".%s %s:x:%i:%s\n" % (name, name, gid, memberships)) - i = i + 1 - - file.close() - - def group_list(self, search='*'): - params = {'search' : search} - request = self.send_request('group/list', auth=True, input=params) - self.groups = request['groups'] - self.memberships = request['memberships'] - return self.groups - - def people_list(self, search='*'): - params = {'search' : search} - self.people = self.send_request('user/list', auth=True, input=params)['people'] - return self.people - - def email_list(self, search='*'): - params = {'search' : search} - self.emails = self.send_request('user/email_list', auth=True, input=params)['emails'] - return self.emails - - def make_group_db(self): - self.groups_text() - os.system('makedb -o %s/group.db %s/group.txt' % (self.temp, self.temp)) - - def make_passwd_db(self): - self.passwd_text() - os.system('makedb -o %s/passwd.db %s/passwd.txt' % (self.temp, self.temp)) - os.system('makedb -o %s/shadow.db %s/shadow.txt' % (self.temp, self.temp)) - os.chmod(self.temp + '/shadow.db', 00400) - - def install_passwd_db(self): - try: - move(self.temp + '/passwd.db', '/var/db/passwd.db') - except IOError, e: - print "ERROR: Could not write passwd db - %s" % e - - def install_shadow_db(self): - try: - move(self.temp + '/shadow.db', '/var/db/shadow.db') - except IOError, e: - print "ERROR: Could not write shadow db - %s" % e - - def install_group_db(self): - try: - move(self.temp + '/group.db', '/var/db/group.db') - except IOError, e: - print "ERROR: Could not write group db - %s" % e - - def create_homedirs(self): - ''' Create homedirs and home base dir if they do not exist ''' - home_base = config.get('users', 'home').strip('"') - if not os.path.exists(home_base): - os.makedirs(home_base, mode=0755) - for person in self.people: - home_dir = os.path.join(home_base, person['username']) - if not os.path.exists(home_dir) and self.valid_user(person['username']): - syslog.syslog('Creating homedir for %s' % person['username']) - copytree('/etc/skel/', home_dir) - os.path.walk(home_dir, _chown, [person['id'], person['id']]) - - def remove_stale_homedirs(self): - ''' Remove homedirs of users that no longer have access ''' - home_base = config.get('users', 'home').strip('"') - try: - home_backup_dir = config.get('users', 'home_backup_dir').strip('"') - except ConfigParser.NoOptionError: - home_backup_dir = '/var/tmp/' - users = os.listdir(home_base) - for user in users: - if not self.valid_user(user): - if not os.path.exists(home_backup_dir): - os.makedirs(home_backup_dir) - syslog.syslog('Backed up %s to %s' % (user, home_backup_dir)) - target = '%s-%s' % (user, time.mktime(datetime.datetime.now().timetuple())) - move(os.path.join(home_base, user), os.path.join(home_backup_dir, target)) - - def create_ssh_keys(self): - ''' Create ssh keys ''' - home_base = config.get('users', 'home').strip('"') - for person in self.people: - username = person['username'] - if self.valid_user(username): - ssh_dir = os.path.join(home_base, username, '.ssh') - if person['ssh_key']: - key = self.ssh_key(person) - if not os.path.exists(ssh_dir): - os.makedirs(ssh_dir, mode=0700) - f = open(os.path.join(ssh_dir, 'authorized_keys'), 'w') - f.write(key) - f.close() - os.chmod(os.path.join(ssh_dir, 'authorized_keys'), 0600) - os.path.walk(ssh_dir, _chown, [person['id'], person['id']]) - - def make_aliases_txt(self): - ''' update your mail aliases file ''' - if not self.groups: - groups = self.group_list() - - self.emails = self.email_list() - email_file = codecs.open(self.temp + '/aliases', mode='w', encoding='utf-8') - email_template = codecs.open(config.get('host', 'aliases_template').strip('"')) - email_file.write("# Generated by fasClient\n") - for line in email_template.readlines(): - email_file.write(line) - sorted = self.emails.keys() - sorted.sort() - for person in sorted: - email_file.write("%s: %s\n" % (person, self.emails[person])) - usernames = self.usernames() - for group in self.groups: - name = group['name'] - members = {} - members['member'] = [] - for membership in self.memberships[name]: - role_type = membership['role_type'] - person = usernames[membership['person_id']] - if role_type == 'user': - ''' Legacy support ''' - members['member'].append(person) - continue - members['member'].append(person) - try: - members[role_type].append(person) - except KeyError: - members[role_type] = [person] - for role in members: - email_file.write("%s-%ss: %s\n" % (name, role, ','.join(members[role]))) - email_file.close() - -def enable(): - temp = tempfile.mkdtemp('-tmp', 'fas-', config.get('global', 'temp').strip('"')) - - old = open('/etc/sysconfig/authconfig', 'r') - new = open(temp + '/authconfig', 'w') - for line in old: - if line.startswith("USEDB"): - new.write("USEDB=yes\n") - else: - new.write(line) - new.close() - old.close() - try: - move(temp + '/authconfig', '/etc/sysconfig/authconfig') - except IOError, e: - print "ERROR: Could not write /etc/sysconfig/authconfig - %s" % e - sys.exit(5) - os.system('/usr/sbin/authconfig --updateall') - rmtree(temp) - -def disable(): - temp = tempfile.mkdtemp('-tmp', 'fas-', config.get('global', 'temp').strip('"')) - old = open('/etc/sysconfig/authconfig', 'r') - new = open(temp + '/authconfig', 'w') - for line in old: - if line.startswith("USEDB"): - new.write("USEDB=no\n") - else: - new.write(line) - old.close() - new.close() - try: - move(temp + '/authconfig', '/etc/sysconfig/authconfig') - except IOError, e: - print "ERROR: Could not write /etc/sysconfig/authconfig - %s" % e - sys.exit(5) - os.system('/usr/sbin/authconfig --updateall') - rmtree(temp) - - -if __name__ == '__main__': - if opts.enable: - enable() - if opts.disable: - disable() - - if opts.install: - try: - fas = MakeShellAccounts(FAS_URL, config.get('global', 'login').strip('"'), config.get('global', 'password').strip('"'), False) - except AuthError, e: - print >> sys.stderr, e - sys.exit(1) - fas.mk_tempdir() - fas.make_group_db() - fas.make_passwd_db() - if not opts.no_group: - fas.install_group_db() - if not opts.no_passwd: - fas.install_passwd_db() - if not opts.no_shadow: - fas.install_shadow_db() - if not opts.no_home_dirs: - fas.create_homedirs() - fas.remove_stale_homedirs() - if not opts.no_ssh_keys: - fas.create_ssh_keys() - fas.rm_tempdir() - if opts.aliases: - try: - fas = MakeShellAccounts(FAS_URL, config.get('global', 'login').strip('"'), config.get('global', 'password').strip('"'), False) - except AuthError, e: - print >> sys.stderr, e - sys.exit(1) - fas.mk_tempdir() - fas.make_aliases_txt() - fas.install_aliases_txt() - - if not (opts.install or opts.enable or opts.disable or opts.aliases): - parser.print_help() diff --git a/fas/setup.py b/fas/setup.py index 6945c68..542a93c 100644 --- a/fas/setup.py +++ b/fas/setup.py @@ -2,16 +2,24 @@ import os import re +import glob from distutils.command.build import build as _build +from distutils.command.install_data import install_data as _install_data from distutils.dep_util import newer from setuptools import setup, find_packages -from turbogears.finddata import find_package_data +from turbogears.finddata import find_package_data, standard_exclude, \ + standard_exclude_directories execfile(os.path.join('fas', 'release.py')) -SUBSTFILES=('fas/config/app.cfg',) +excludeFiles = ['*.cfg.in'] +excludeFiles.extend(standard_exclude) +excludeDataDirs = ['fas/static'] +excludeDataDirs.extend(standard_exclude_directories) + +SUBSTFILES = ('fas/config/app.cfg',) class Build(_build, object): ''' @@ -58,6 +66,23 @@ class Build(_build, object): f.close() super(Build, self).run() +class InstallData(_install_data, object): + def finalize_options(self): + '''Override to emulate setuptools in the default case. + install_data => install_dir + ''' + print 'DEBUG:', self.install_dir, '#' + haveInstallDir = self.install_dir + self.set_undefined_options('install', + ('install_lib', 'install_dir'), + ('root', 'root'), + ('force', 'force'), + ) + if not haveInstallDir: + # We set this above, now we need to add the module subdirectory to + # make it truly correct. + self.install_dir = os.path.join(self.install_dir, 'fas') + setup( name=NAME, version=VERSION, @@ -71,9 +96,7 @@ setup( cmdclass={ 'build': Build, - #'build_scripts': BuildScripts, - # 'install': Install, - # 'install_lib': InstallApp}, + 'install_data': InstallData, }, install_requires = [ 'TurboGears >= 1.0.4', @@ -81,11 +104,21 @@ setup( 'TurboMail', 'python_fedora >= 0.2.99.2' ], - scripts = ['client/fasClient.py', 'client/restricted-shell'], + scripts = ['client/fasClient', 'client/restricted-shell'], zip_safe=False, packages=find_packages(), + data_files = (('static', [f for f in glob.glob('fas/static/*') if os.path.isfile(f)]), + ('static/css', [f for f in glob.glob('fas/static/css/*') if os.path.isfile(f)]), + ('static/images', [f for f in glob.glob('fas/static/images/*') if os.path.isfile(f)]), + ('static/images/balloons', + [f for f in glob.glob('fas/static/images/balloons/*') if os.path.isfile(f)]), + ('static/js', [f for f in glob.glob('fas/static/js/*') if os.path.isfile(f)]), + ), package_data = find_package_data(where='fas', - package='fas'), + package='fas', + exclude=excludeFiles, + exclude_directories=excludeDataDirs, + ), keywords = [ # Use keywords if you'll be adding your package to the # Python Cheeseshop @@ -129,4 +162,3 @@ setup( ) } ) -