This is the new fasClient. It almost works right.
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright © 2007-2009  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 
#                    Toshio Kuratomi 
#                    Ricky Zhou 

import os
import sys
import codecs
import tempfile
import logging
import syslog
import datetime
import subprocess
import time
from fedora.client import AccountSystem, AuthError, ServerError
try:
    import cPickle as pickle
except ImportError:
    import pickle


import ConfigParser
from optparse import OptionParser

from shutil import move, rmtree, copytree
from rhpl.translate import _

parser = OptionParser()

parser.add_option('-i', '--install',
                     dest = 'install',
                     default = False,
                     action = 'store_true',
                     help = _('Download and sync most recent content'))
parser.add_option('-I', '--info',
                     dest = 'info_username',
                     default = False,
                     metavar = 'info_username',
                     help = _('Get info about a user'))
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('-p', '--prefix',
                     dest = 'prefix',
                     default = None,
                     metavar = 'prefix',
                     help = _('Specify install prefix.  Useful for testing'))
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'))
parser.add_option('--nosession',
                     dest = 'nosession',
                     default = False,
                     action = 'store_true',
                     help = _('Disable the creation of ~/.fedora_session'))
parser.add_option('--debug',
                     dest = 'debug',
                     default = False,
                     action = 'store_true',
                     help = _('Enable debugging messages'))

(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('"')
if opts.prefix:
    prefix = opts.prefix
else:
    prefix = config.get('global', 'prefix').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(AccountSystem):
    _users = None
    _groups = None
    _good_users = None
    _group_types = None
    _temp = None

    def _make_tempdir(self, force=False):
        '''Return a temporary directory'''
        if not self._temp or force:
            # Remove any existing temp directories
            if self._temp:
                rmtree(self._temp)
            self._temp = tempfile.mkdtemp('-tmp', 'fas-', config.get('global', 'temp').strip('"'))
        return self._temp
    temp = property(_make_tempdir)

    def _refresh_users(self, force=False):
        '''Return a list of users in FAS'''
        # Cached values present, return
        if not self._users or force:
            self._users = self.user_data()
        return self._users

    users = property(_refresh_users)

    def _refresh_groups(self, force=False):
        '''Return a list of groups in FAS'''
        # Cached values present, return
        if not self._groups or force:
            group_data = self.group_data()
            # The JSON output from FAS encodes dictionary keys as strings, but leaves
            # array elements as integers (in the case of group member UIDs).  This
            # normalizes them to all strings.
            for group in group_data:
                for role_type in ('administrators', 'sponsors', 'users'):
                    group_data[group][role_type] = [str(uid) for uid in group_data[group][role_type]]
            self._groups = group_data
        return self._groups

    groups = property(_refresh_groups)

    def _refresh_good_users_group_types(self, force=False):
        # Cached values present, return
        if self._good_users and self._group_types and not force:
            return

        cla_group = config.get('global', 'cla_group').strip('"')
        if cla_group not in self.groups:
            print >> sys.stderr, 'No such group: %s' % cla_group
            print >> sys.stderr, 'Aborting.'
            sys.exit(1)

        cla_uids = self.groups[cla_group]['users'] + \
            self.groups[cla_group]['sponsors'] + \
            self.groups[cla_group]['administrators']

        user_groupcount = {}
        group_types = {}
        for uid in cla_uids:
            user_groupcount[uid] = 0

        for group in self.groups:
            group_type = self.groups[group]['type']
            if group.startswith('cla_'):
                continue
            for uid in self.groups[group]['users'] + \
                self.groups[group]['sponsors'] + \
                self.groups[group]['administrators']:
                if group_type not in group_types:
                    group_types[group_type] = set()
                group_types[group_type].add(uid)
                if uid in user_groupcount:
                    user_groupcount[uid] += 1

        good_users = set()
        for uid in user_groupcount:
            # If the user is active, has signed a CLA, and is in at least one
            # other group, add them to good_users.
            if uid in self.users and user_groupcount[uid] > 0:
                good_users.add(uid)

        self._good_users = good_users
        self._group_types = group_types

    def _refresh_good_users(self, force=False):
        '''Return a list of users in who have CLA + 1 group'''
        self._refresh_good_users_group_types(force)
        return self._good_users

    good_users = property(_refresh_good_users)

    def _refresh_group_types(self, force=False):
        '''Return a list of users in group with various types'''
        self._refresh_good_users_group_types(force)
        return self._group_types

    group_types = property(_refresh_group_types)

    def filter_users(self, valid_groups=None, restricted_groups=None):
        '''Return a list of users who get normal and restricted accounts on a machine'''
        if valid_groups is None:
            valid_groups = []
        if restricted_groups is None:
            restricted_groups = []

        all_groups = valid_groups + restricted_groups

        users = {}

        for group in all_groups:
            uids = set()
            restricted = group not in valid_groups

            if group.startswith('@'):
                # Filter by group type
                group_type = group[1:]
                if group_type == 'all':
                    # It's all good as long as a the user is in CLA + one group
                    uids.update(self.good_users)
                else:
                    if group_type not in self.group_types:
                        print >> sys.stderr, 'No such group type: %s' % group_type
                        continue
                    uids.update(self.group_types[group_type])
            else:
                if group not in self.groups:
                    print >> sys.stderr, 'No such group: %s' % group
                    continue
                uids.update(self.groups[group]['users'])
                uids.update(self.groups[group]['sponsors'])
                uids.update(self.groups[group]['administrators'])

            for uid in uids:
                if uid not in self.users:
                    # The user is most likely inactive.
                    continue
                if restricted:
                    # Make sure that the most privileged group wins.
                    if uid not in users:
                        users[uid] = {}
                        users[uid]['shell'] = config.get('users', 'shell').strip('"')
                        users[uid]['ssh_cmd'] = config.get('users', 'ssh_restricted_app').strip('"')
                        users[uid]['ssh_options'] = config.get('users', 'ssh_key_options').strip('"')
                else:
                    users[uid] = {}
                    users[uid]['shell'] = config.get('users', 'ssh_restricted_shell').strip('"')
                    users[uid]['ssh_cmd'] = ''
                    users[uid]['ssh_options'] = ''
        return users

    def passwd_text(self, users):
        '''Create the text password file'''
        i = 0
        home_dir_base = config.get('users', 'home').strip('"')

        # Touch shadow and secure the permissions
        shadow_file = codecs.open(os.path.join(self.temp, 'shadow.txt'), mode='w', encoding='utf-8')
        shadow_file.close()
        os.chmod(os.path.join(self.temp, 'shadow.txt'), 00600)

        passwd_file = codecs.open(os.path.join(self.temp, 'passwd.txt'), mode='w', encoding='utf-8')
        shadow_file = codecs.open(os.path.join(self.temp, 'shadow.txt'), mode='w', encoding='utf-8')

        for uid in users:
            username = self.users[uid]['username']
            human_name = username
            password = self.users[uid]['password']
            home_dir = '%s/%s' % (home_dir_base, username)
            shell = users[uid]['shell']

            passwd_file.write('=%s %s:x:%s:%s:%s:%s:%s\n' % (uid, username, uid, uid, human_name, home_dir, shell))
            passwd_file.write('0%i %s:x:%s:%s:%s:%s:%s\n' % (i, username, uid, uid, human_name, home_dir, shell))
            passwd_file.write('.%s %s:x:%s:%s:%s:%s:%s\n' % (username, username, uid, uid, human_name, home_dir, shell))

            shadow_file.write('=%s %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 += 1

        passwd_file.close()
        shadow_file.close()

    def groups_text(self, users):
        '''Create the text groups file'''
        i = 0
        group_file = codecs.open(os.path.join(self.temp, 'group.txt'), 'w')

        # First create all of our users/groups combo
        # Only create user groups for users that actually exist on the system
        for uid in users:
            username = self.users[uid]['username']
            group_file.write('=%s %s:x:%s:\n' % (uid, username, uid))
            group_file.write('0%i %s:x:%s:\n' % (i, username, uid))
            group_file.write('.%s %s:x:%s:\n' % (username, username, uid))
            i += 1

        for group in self.groups:
            gid = self.groups[group]['id']
            members = set()
            memberships = ''

            for member_uid in self.groups[group]['administrators'] + \
                self.groups[group]['sponsors'] + \
                self.groups[group]['users']:
                try:
                    members.add(self.users[member_uid]['username'])
                except KeyError:
                    # This means that the user is most likely disabled.
                    pass

            memberships = ','.join(members)
            group_file.write('=%i %s:x:%i:%s\n' % (gid, group, gid, memberships))
            group_file.write('0%i %s:x:%i:%s\n' % (i, group, gid, memberships))
            group_file.write('.%s %s:x:%i:%s\n' % (group, group, gid, memberships))
            i += 1

        group_file.close()

    def make_group_db(self, users):
        '''Compile the groups file'''
        self.groups_text(users)
        subprocess.call(['/usr/bin/makedb', '-o', os.path.join(self.temp, 'group.db'), os.path.join(self.temp, 'group.txt')])

    def make_passwd_db(self, users):
        '''Compile the password and shadow files'''
        self.passwd_text(users)
        subprocess.call(['/usr/bin/makedb', '-o', os.path.join(self.temp, 'passwd.db'), os.path.join(self.temp, 'passwd.txt')])
        subprocess.call(['/usr/bin/makedb', '-o', os.path.join(self.temp, 'shadow.db'), os.path.join(self.temp, 'shadow.txt')])
        os.chmod(os.path.join(self.temp, 'shadow.db'), 00400)
        os.chmod(os.path.join(self.temp, 'shadow.txt'), 00400)

    def make_aliases_text(self):
        '''Create the aliases file'''
        email_file = codecs.open(os.path.join(self.temp, 'aliases'), mode='w', encoding='utf-8')
        try:
            email_template = codecs.open(config.get('host', 'aliases_template').strip('"'))
        except IOError, e:
            print >> sys.stderr, 'Could not open aliases template %s: %s' % (config.get('host', 'aliases_template').strip('"'), e)
            print >> sys.stderr, 'Aborting.'
            sys.exit(1)
        email_file.write('# Generated by fasClient\n')
        for line in email_template.readlines():
            email_file.write(line)

        for uid in self.good_users:
            email_file.write('%s: %s\n' % (self.users[uid]['username'], self.users[uid]['email']))

        for group in self.groups:
            administrators = set()
            sponsors = set()
            members = set()

            for uid in self.groups[group]['users']:
                if uid in self.good_users:
                    # The user has an @fedoraproject.org alias
                    username = self.users[uid]['username']
                    members.add(username)
                else:
                    # Add their email if they aren't disabled.
                    if uid in self.users:
                        members.add(self.users[uid]['email'])

            for uid in self.groups[group]['sponsors']:
                if uid in self.good_users:
                    # The user has an @fedoraproject.org alias
                    username = self.users[uid]['username']
                    sponsors.add(username)
                    members.add(username)
                else:
                    # Add their email if they aren't disabled.
                    if uid in self.users:
                        sponsors.add(self.users[uid]['email'])
                        members.add(self.users[uid]['email'])

            for uid in self.groups[group]['administrators']:
                if uid in self.good_users:
                    # The user has an @fedoraproject.org alias
                    username = self.users[uid]['username']
                    administrators.add(username)
                    sponsors.add(username)
                    members.add(username)
                else:
                    # Add their email if they aren't disabled.
                    if uid in self.users:
                        administrators.add(self.users[uid]['email'])
                        sponsors.add(self.users[uid]['email'])
                        members.add(self.users[uid]['email'])

            if administrators:
                email_file.write('%s-administrators: %s\n' % (group, ','.join(administrators)))
            if sponsors:
                email_file.write('%s-sponsors: %s\n' % (group, ','.join(sponsors)))
            if members:
                email_file.write('%s-members: %s\n' % (group, ','.join(members)))

    def create_home_dirs(self, users, modes=None):
        ''' Create homedirs and home base dir if they do not exist '''
        if modes is None:
            modes = {}
        home_dir_base = os.path.join(prefix, config.get('users', 'home').strip('"').lstrip('/'))
        if not os.path.exists(home_dir_base):
            os.makedirs(home_dir_base, mode=0755)
        for uid in users:
            username = self.users[uid]['username']
            home_dir = os.path.join(home_dir_base, username)
            if not os.path.exists(home_dir):
                syslog.syslog('Creating homedir for %s' % username)
                copytree('/etc/skel/', home_dir)
                os.path.walk(home_dir, _chown, [int(uid), int(uid)])
            else:
                dir_stat = os.stat(home_dir)
                if dir_stat.st_uid == 0:
                    if username in modes:
                        os.chmod(home_dir, modes[username])
                    else:
                        os.chmod(home_dir, 00755)
                    os.path.walk(home_dir, _chown, [int(uid), int(uid)])

    def remove_stale_homedirs(self, users):
        ''' Remove homedirs of users that no longer have access '''
        home_dir_base = os.path.join(prefix, config.get('users', 'home').strip('"').lstrip('/'))
        home_backup_dir = os.path.join(prefix, config.get('users', 'home_backup_dir').strip('"').lstrip('/'))
        valid_users = [self.users[uid]['username'] for uid in users]
        current_users = os.listdir(home_dir_base)
        modes = {}
        for user in current_users:
            if user not in valid_users:
                home_dir = os.path.join(home_dir_base, user)
                dir_stat = os.stat(home_dir)
                if dir_stat.st_uid != 0:
                    modes[user] = dir_stat.st_mode
                    syslog.syslog('Locking permissions on %s' % home_dir)
                    os.path.walk(home_dir, _chown, [0, 0])
                    os.chmod(home_dir, 00700)
        return modes

    def create_ssh_keys(self, users):
        ''' Create SSH keys '''
        home_dir_base = os.path.join(prefix, config.get('users', 'home').strip('"').lstrip('/'))
        for uid in users:
            username = self.users[uid]['username']
            ssh_dir = os.path.join(home_dir_base, username, '.ssh')
            if self.users[uid]['ssh_key']:
                if users[uid]['ssh_cmd'] or users[uid]['ssh_options']:
                   key = 'command="%s",%s %s' % (users[uid]['ssh_cmd'], users[uid]['ssh_options'], self.users[uid]['ssh_key'])
                else:
                   key = self.users[uid]['ssh_key']
                if not os.path.exists(ssh_dir):
                    os.makedirs(ssh_dir, mode=0700)
                f = codecs.open(os.path.join(ssh_dir, 'authorized_keys'), mode='w', encoding='utf-8')
                f.write(key + '\n')
                f.close()
                os.chmod(os.path.join(ssh_dir, 'authorized_keys'), 0600)
                os.path.walk(ssh_dir, _chown, [int(uid), int(uid)])

    def install_passwd_db(self):
        '''Install the password database'''
        try:
            move(os.path.join(self.temp, 'passwd.db'), os.path.join(prefix, 'var/db/passwd.db'))
        except IOError, e:
            print 'ERROR: Could not install passwd db: %s' % e

    def install_shadow_db(self):
        '''Install the shadow database'''
        try:
            move(os.path.join(self.temp, 'shadow.db'), os.path.join(prefix, 'var/db/shadow.db'))
        except IOError, e:
            print 'ERROR: Could not install shadow db: %s' % e

    def install_group_db(self):
        '''Install the group database'''
        try:
            move(os.path.join(self.temp, 'group.db'), os.path.join(prefix, 'var/db/group.db'))
        except IOError, e:
            print 'ERROR: Could not install group db: %s' % e

    def install_aliases(self):
        '''Install the aliases file'''
        move(os.path.join(self.temp, 'aliases'), os.path.join(prefix, 'etc/aliases'))
        subprocess.call(['/usr/bin/newaliases'])

    def user_info(self, username):
        '''Print information on a user'''
        person = self.person_by_username(username)
        if not person:
            print 'No such person: %s' % username
            return
        print 'User: %s' % person['username']
        print ' Name: %s' % person['human_name']
        print ' Created: %s' % person['creation'].split(' ')[0]
        print ' Timezone: %s' % person['timezone']
        print ' IRC Nick: %s' % person['ircnick']
        print ' Locale: %s' % person['locale']
        print ' Status: %s' % person['status']
        print ' Approved Groups: '
        if person['approved_memberships']:
            for group in person['approved_memberships']:
                print '   %s' % group['name']
        else:
            print '    None'
        print ' Unapproved Groups: '
        if person['unapproved_memberships']:
            for group in person['unapproved_memberships']:
                print '   %s' % group['name']
        else:
            print '    None'

    def cleanup(self):
        '''Perform any necessary cleanup tasks'''
        if self.temp:
            rmtree(self.temp)

def enable():
    '''Enable FAS authentication'''
    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(os.path.join(temp, 'authconfig'), '/etc/sysconfig/authconfig')
    except IOError, e:
        print 'ERROR: Could not write /etc/sysconfig/authconfig: %s' % e
        sys.exit(5)
    subprocess.call(['/usr/sbin/authconfig', '--updateall'])
    rmtree(temp)

def disable():
    '''Disable FAS authentication'''
    temp = tempfile.mkdtemp('-tmp', 'fas-', config.get('global', 'temp').strip('"'))
    old = open('/etc/sysconfig/authconfig', 'r')
    new = open(os.path.join(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(os.path.join(temp, 'authconfig'), '/etc/sysconfig/authconfig')
    except IOError, e:
        print 'ERROR: Could not write /etc/sysconfig/authconfig: %s' % e
        sys.exit(5)
    subprocess.call(['/usr/sbin/authconfig', '--updateall'])
    rmtree(temp)

if __name__ == '__main__':

    if not (opts.install or opts.enable or opts.disable or opts.aliases or opts.info_username):
        parser.print_help()
        sys.exit(0)

    if opts.enable:
        enable()
    if opts.disable:
        disable()

    try:
        fas = MakeShellAccounts(FAS_URL, username=config.get('global', 'login').strip('"'), password=config.get('global', 'password').strip('"'), debug=opts.debug)
    except AuthError, e:
        sys.stderr.write('%s\n' % str(e))
        sys.exit(1)
    except URLError, e:
        sys.stderr.write('Could not connect to %s: %s\n' % (FAS_URL, e.reason[1]))
        sys.exit(9)

    valid_groups = []
    restricted_groups = []

    valid_grouplist = config.get('host', 'groups').strip('"')
    restricted_grouplist = config.get('host', 'ssh_restricted_groups').strip('"')

    if valid_grouplist:
        valid_groups = valid_grouplist.split(',')
    if restricted_grouplist:
        restricted_groups = restricted_grouplist.split(',')

    if opts.info_username:
        fas.user_info(opts.info_username)

    if opts.install:
        users = fas.filter_users(valid_groups=valid_groups, restricted_groups=restricted_groups)
        fas.make_group_db(users)
        fas.make_passwd_db(users)
        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:
            try:
                modefile = open(config.get('global', 'modefile'), 'r')
                modes = pickle.load(modefile)
            except IOError:
                modes = {}
            else:
                modefile.close()
            fas.create_home_dirs(users, modes=modes)
            new_modes = fas.remove_stale_homedirs(users)
            modes.update(new_modes)
            try:
                modefile = open(config.get('global', 'modefile'), 'w')
                pickle.dump(modes, modefile)
            except IOError:
                pass
            else:
                modefile.close()
        if not opts.no_ssh_keys:
            fas.create_ssh_keys(users)

    if opts.aliases:
        fas.make_aliases_text()
        fas.install_aliases()

    fas.cleanup()
    
If you spot the bug, let me know.