diff --git a/root/usr/local/bin/epfl_roaming.py b/root/usr/local/bin/epfl_roaming.py index 39de69b..b954db0 100755 --- a/root/usr/local/bin/epfl_roaming.py +++ b/root/usr/local/bin/epfl_roaming.py @@ -1,962 +1,961 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- """ epfl_roaming : Script to make application's preferences move along with the user. + It is called by PAM as root at session_open and session_close : $ epfl_roaming.py --pam does : - mount & umount - files/folders ln -s & cp - - rm -rf at session_close - DConf dump at session_close + It is called by an autostart ~/.config/autostart/epfl_roaming.desktop as username : $ epfl_roaming.py --session does : - DConf load + It is called by a systemd service /etc/systemd/system/epfl_roaming_on_shutdown.service at shutdown|reboot as root : $ epfl_roaming.py --on_halt does : - run roaming_close for every user still logged before network is turned off. It requires packages: $ sudo apt install python-lockfile It requires to work with script manage_cred.py """ import os import sys import re import argparse import pwd, grp import ldap import pickle import subprocess import lockfile import shutil import xml.dom.minidom import datetime import signal import time import traceback ### CONSTANTS LOG_PAM = "/var/log/epfl_roaming.log" LOG_SESSION = "/tmp/epfl_roaming_{username}.log" # username replaced during execution time CONFIG_FILE = "/usr/local/etc/epfl_roaming.conf" LDAP_SERVER = "ldap://ldap.epfl.ch" LDAP_BASE_DN = "c=ch" LDAP_SCOPE = ldap.SCOPE_SUBTREE LDAP_NB_RETRY = 3 RM_MAX_ATTEMPT = 3 RM_SLEEP = 1 UMOUNT_MAX_ATTEMPT = 3 UMOUNT_SLEEP = 1 MANAGE_CRED_FLAG_FILE = "/var/run/epfl_roaming/manage_cred.flag" MANAGE_CRED_PID_FILE = "/var/run/manage_cred/manage_cred_{username}.pid" MANAGE_CRED_TIMEOUT = 3 MANAGE_CRED_TERM = True VAR_RUN = "/var/run/epfl_roaming" SEMAPHORE_LOCK_FILE = "/var/run/epfl_roaming/global_lock" SESSIONS_COUNT_FILE = "/var/run/epfl_roaming/sessions_count" class PreventInterrupt(object): def __init__(self): pass def __enter__(self): PreventInterrupt.__no_interrupt__() def __exit__(self, typ, val, tb): PreventInterrupt.__can_interrupt__() @classmethod def is_interruptible(cls): try: return cls.__can_interrupt except Exception: return True @classmethod def __no_interrupt__(cls): cls.__can_interrupt = False @classmethod def __can_interrupt__(cls): cls.__can_interrupt = True class UserIdentity(): def __init__(self, user): self.user = user def __enter__(self): os.setegid(int(self.user.gid)) os.seteuid(int(self.user.uid)) IO.write("ID changed : %s" % (os.getresuid(),)) def __exit__(self, typ, val, tb): os.seteuid(0) os.setegid(0) IO.write("ID changed : %s" % (os.getresuid(),)) class IO(object): def __init__(self, filename): self.filename = filename def __enter__(self): IO.__open__(self.filename) try: for msg, eol in IO.previous_writes: IO.write(msg, eol) except AttributeError: pass def __exit__(self, typ, val, tb): IO.__close__() @classmethod def write(cls, msg, eol="\n"): pid = os.getpid() try: cls.f.write("\n".join(["(%s) %s" % (pid, s) for s in msg.split("\n")]) + eol) except AttributeError: try: cls.previous_writes.append((msg, eol)) except AttributeError: cls.previous_writes = [(msg, eol), ] @classmethod def __open__(cls, filename): cls.f = open(filename, "a", 1) # line buffered @classmethod def __close__(cls): cls.f.close() class NameSpace(object): def __repr__(self): type_name = type(self).__name__ args_string = [] for arg in self._get_args(): args_string.append(repr(arg)) for name, value in self._get_kwargs(): args_string.append("%s=%r" % (name, value)) return "%s(%s)" % (type_name, ", ".join(args_string)) def _get_kwargs(self): return sorted(self.__dict__.items()) def _get_args(self): return [] class Ldap(object): def __init__(self): success = False for _ in xrange(LDAP_NB_RETRY): try: self.l = ldap.initialize(LDAP_SERVER) success = True except Exception, e: time.sleep(1) if not success: raise e def search_s(self, l_filter, l_attrs): for _ in xrange(LDAP_NB_RETRY): try: return self.l.search_s( base=LDAP_BASE_DN, scope=LDAP_SCOPE, filterstr=l_filter, attrlist=l_attrs ) except Exception, e: time.sleep(1) raise e def run_cmd(cmd, s_cmd=None, env=None, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, s_input=None, shell=False): p = subprocess.Popen( cmd, env=env, stdin=stdin, stdout=stdout, stderr=stderr, shell=shell, ) if s_cmd != None: IO.write("-> (%s) %s" % (p.pid, s_cmd)) else: if shell: IO.write("-> (%s) %s" % (p.pid, cmd)) else: IO.write("-> (%s) %s" % (p.pid, " ".join(cmd))) output = p.communicate(s_input)[0] if output != "": IO.write("| (%s) " % p.pid + re.sub(r"\n", "\n| (%s) " % p.pid, output)) if p.returncode == 0: IO.write("ok (%s)" % p.pid) else: IO.write("Error: Returned non-zero exit status %d (%s)" % (p.returncode, p.pid)) return p.returncode == 0 def read_options(): """ Parse command line args """ print " ".join(sys.argv) parser = argparse.ArgumentParser(description="EPFL Roaming.") parser.add_argument( "--pam", help="PAM related actions (!=lightdm) : filers (u)mount, folders/files link/copy, DConf save", action="store_const", dest="context", default=None, const="pam", ) parser.add_argument( "--session", help="Session (DConf load)", action="store_const", dest="context", const="session", ) parser.add_argument( "--on_halt", help="Hold it's execution until all session have been cleaned by epfl_roaming.py (for shutdown/reboot).", action="store_const", dest="context", const="on_halt", ) parser.add_argument( "--list_users", help="List users currently logged in and how many sessions they have.", action="store_const", dest="context", const="list_users", ) parser.add_argument( "--test_load", help="Load DConf (test)", action="store_const", dest="context", const="test_load", ) parser.add_argument( "--test_dump", help="Dump DConf (test)", action="store_const", dest="context", const="test_dump", ) options = parser.parse_args() if options.context == None: parser.print_help() sys.exit(1) return options def read_user(options, on_halt_username=None): """ Extract all necessary info for the user """ user = NameSpace() if options.context == "pam": user.username = os.environ.get("PAM_USER", None) # SERVICE : lightdm | sshd | login | gdm-password # other services (like slurm) are not taken in account in epfl_roaming user.conn_service = os.environ.get("PAM_SERVICE", None) # TTY : :0 | ssh user.conn_tty = os.environ.get("PAM_TTY", None) # TYPE : open_session | close_session user.conn_type = os.environ.get("PAM_TYPE", None) elif options.context == "on_halt": user.username = on_halt_username else: user.username = pwd.getpwuid(os.getuid())[0] user.username = user.username.split('@')[0] user.home_dir = os.path.expanduser("~%s" % user.username) # shortcuts if not user.home_dir.startswith("/home/"): return user try: pw = pwd.getpwnam(user.username) except (KeyError, TypeError): return user # ~ if options.context in ("pam", "on_halt"): my_ldap = Ldap() try: ldap_res = my_ldap.search_s( # l_filter="uidNumber=%s" % pw.pw_uid, l_filter="uid=%s" % user.username, l_attrs=["uniqueIdentifier"] ) unique_identifier = ldap_res[0][1]["uniqueIdentifier"][0] except (KeyError, IndexError): # no pw_uid or not in ldap! return user # EPFL Guests if ldap_res[0][0].endswith("o=epfl-guests,c=ch"): user.epfl_account_type = "guest" return user # Normal EPFL account automount_informations = ("", "", "", "") ldap_res = my_ldap.search_s( l_filter="cn=%s" % user.username, l_attrs=["automountInformation"] ) for entry in ldap_res: if entry[1].get("automountInformation") != None: automount_informations = entry[1]["automountInformation"][0] automount_informations = re.findall(r'-fstype=(\w+),(.+) ([\w\.]+):(.+)$', automount_informations)[0] gr = grp.getgrgid(pw.pw_gid) user.epfl_account_type = "normal" user.uid = str(pw.pw_uid) user.gid = str(pw.pw_gid) user.groupname = gr.gr_name user.domain = "INTRANET" user.sciper = unique_identifier user.sciper_digit = unique_identifier[-1] user.automount_fstype = automount_informations[0] user.automount_options = automount_informations[1] user.automount_host = automount_informations[2] user.automount_path = automount_informations[3] return user def check_options(options, user): """ Performs all required checks """ if options.context == "pam" and user.conn_service not in ( "lightdm", "sshd", "login", "common-session", "gdm-password"): IO.write("Not doing anything for PAM_SERVICE '%s'" % user.conn_service) sys.exit(0) if options.context in ("pam", "on_halt") and os.geteuid() != 0: IO.write("Error: this should be run as root.") sys.exit(1) if options.context == "session" and os.geteuid() == 0: IO.write("Error: this should not be running as root.") sys.exit(1) if user.username == None: if options.context == "pam": IO.write("Error: Could not read PAM_USER") else: IO.write("Error: Could not read USER") sys.exit(1) if not user.home_dir.startswith("/home/"): IO.write("Nothing to do for user %s (home dir: %s)" % (user.username, user.home_dir)) sys.exit(0) if options.context == "pam": if user.conn_type == None: IO.write("Error: Could not read PAM_TYPE") sys.exit(1) if user.conn_type not in ("open_session", "close_session"): IO.write("Error: Unknown PAM_TYPE : %s" % user.conn_type) sys.exit(1) if options.context in ("pam", "on_halt"): try: user.epfl_account_type except AttributeError: IO.write("Warning: Incomplete user informations found in LDAP. Exiting.") sys.exit(0) def apply_subst(name, user): name = re.sub(r'_SCIPER_DIGIT_', user.sciper_digit, name) name = re.sub(r'_SCIPER_', user.sciper, name) name = re.sub(r'_USERNAME_', user.username, name) name = re.sub(r'_GROUPNAME_', user.groupname, name) name = re.sub(r'_DOMAIN_', user.domain, name) name = re.sub(r'_UID_', user.uid, name) name = re.sub(r'_GID_', user.gid, name) name = re.sub(r'_FSTYPE_', user.automount_fstype, name) name = re.sub(r'_HOST_', user.automount_host, name) name = re.sub(r'_PATH_', user.automount_path, name) name = re.sub(r'_OPTIONS_', user.automount_options, name) return name def read_config(options, user): """ Read and Parse config file """ class ConfigLineException(Exception): def __init__(self, line, reason="syntax"): self.line = line self.reason = reason conf = {"mounts": {}, "links": [], "su_links": [], "dconf": {}, } dconf_file = "" try: with open(CONFIG_FILE, "r") as f: for line in f: try: line = re.sub(r'\s*#.*$', '', line).rstrip() if line == "": continue try: subject = re.findall(r'(\S+)', line)[0] except IndexError, e: raise ConfigLineException(line, reason="syntax") ## Mounts if subject == "mount": if not options.context in ("pam", "on_halt"): continue line = apply_subst(line, user) mount_point = get_mount_point(line) conf["mounts"][mount_point] = line ## Links elif subject == "link": try: target, link_name = re.findall(r'"([^"]+)"', line)[0:2] target = apply_subst(target, user) link_name = apply_subst(link_name, user) conf["links"].append((target, link_name)) except IndexError, e: raise ConfigLineException(line, reason="syntax") ## Links elif subject == "su_link": try: target, link_name = re.findall(r'"([^"]+)"', line)[0:2] target = apply_subst(target, user) link_name = apply_subst(link_name, user) conf["su_links"].append((target, link_name)) except IndexError, e: raise ConfigLineException(line, reason="syntax") ## dconf file elif subject == "dconf_file": try: dconf_file = re.findall(r'"(.+)"', line)[0] dconf_file = apply_subst(dconf_file, user) except IndexError, e: raise ConfigLineException(line, reason="syntax") ## dconf entry elif subject == "dconf": if dconf_file == "": raise ConfigLineException(line, reason="dconf key before dconf_file instruction") try: dconf_entry = re.findall(r'"(.+)"', line)[0] conf["dconf"].setdefault(dconf_file, []).append(dconf_entry) except IndexError, e: raise ConfigLineException(line, reason="syntax") else: raise ConfigLineException(line, reason="syntax") except ConfigLineException, e: IO.write("Error: ", eol="") if e.reason == "syntax": IO.write("Unrecognized line :\n%s" % e.line) else: IO.write("%s :\n%s" % (e.reason, e.line)) IO.write("Continuing ignoring that one.") except IOError: IO.write("Conf file %s not readable" % CONFIG_FILE) return conf ### # Clean DConf (remove englobing elements) for dconf_file in conf["dconf"]: indexes_to_drop = set() for i in xrange(len(conf["dconf"][dconf_file])): for j in xrange(len(conf["dconf"][dconf_file])): if i == j: continue if conf["dconf"][dconf_file][i].startswith(conf["dconf"][dconf_file][j]): indexes_to_drop.add(i) for i in reversed(sorted(list(indexes_to_drop))): del (conf["dconf"][dconf_file][i]) return conf def count_sessions(user, increment=0, clear_count=False): """ Increments/decrements session count for current user """ try: with open(SESSIONS_COUNT_FILE, "rb") as f: user_sessions = pickle.load(f) except: user_sessions = {} user_sessions.setdefault(user.username, 0) old_count = user_sessions[user.username] if clear_count: new_count = 0 user_sessions.pop(user.username) else: new_count = max(user_sessions[user.username] + increment, 0) if new_count <= 0: user_sessions.pop(user.username) else: user_sessions[user.username] = new_count IO.write("%i -> %i" % (old_count, new_count)) try: with open(SESSIONS_COUNT_FILE, "wb") as f: pickle.dump(user_sessions, f) except Exception, e: IO.write("Error : %s" % e) raise return old_count, new_count def get_mount_point(mount_instruction): """ Guess mointpoint from a mount instruction """ line = mount_instruction line = re.sub(r'-o \S+\s*', '', line) line = re.sub(r'-t \S+\s*', '', line) line = re.sub(r'-[fnrsvw]\s*', '', line) m = re.search('(\S+)\s*$', line) if m: return m.group(1) else: IO.write("Error: Mount point not found in %s" % mount_instruction) IO.write("Aborting") sys.exit(1) def ismount(path): """ Replaces os.path.ismount which doesn't work for nfsv4 run from root """ p = subprocess.Popen(["mount"], stdout=subprocess.PIPE) output = p.communicate()[0] return path in re.findall(r' on (\S+)\s+', output) def dconf_dump(config, user, test=False): if not os.path.exists(os.path.join(user.home_dir, ".config/dconf/user")): IO.write("dconf_dump : ~/.config/dconf/user not found -> Skipping.") return IO.write("dconf_dump") for dconf_file, keys_to_save in config["dconf"].items(): dconf_file = os.path.join(user.home_dir, dconf_file) IO.write("DConf to %s" % dconf_file) dir_save_to = os.path.dirname(dconf_file) if not os.path.exists(dir_save_to): IO.write("mkdir -p %s" % dir_save_to) os.makedirs(dir_save_to) dump_succeeded = True dump_dconf = "" for k in keys_to_save: IO.write("+ %s" % k) if k[-1] == "/": if test: cmd = ["dconf", "dump", k] else: cmd = ["sudo", "-u", user.username, "dconf", "dump", k] p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env={}) k_dumped = p.communicate()[0] for line in k_dumped.split("\n"): try: fold = re.findall(r'^\[(.*)\]$', line)[0] if fold == "/": dump_dconf += "[%s]\n" % k[1:-1] else: dump_dconf += "[%s]\n" % os.path.join(k[1:-1], fold) except IndexError, e: dump_dconf += line + "\n" else: if test: cmd = ["dconf", "read", k] else: cmd = ["sudo", "-u", user.username, "dconf", "read", k] p = subprocess.Popen(cmd, stdout=subprocess.PIPE) k_dumped = p.communicate()[0] if k_dumped != "": dump_dconf += """ [%s] %s=%s """ % (os.path.dirname(k)[1:], os.path.basename(k), k_dumped) if p.returncode != 0: dump_succeeded = False break if dump_succeeded and dump_dconf != "": with open(dconf_file, "w") as f: f.write(dump_dconf) else: IO.write("DConf dump did not succeeded. Aborting this.") def dconf_load(config, user, test=False): IO.write("dconf_load") for dconf_file in config["dconf"]: dconf_file = os.path.join(user.home_dir, dconf_file) if os.path.exists(dconf_file): with open(dconf_file, "r") as f: dconf_dumped = f.read() # "dbus-launch", "--exit-with-session", cmd = ["dconf", "load", "/"] run_cmd( cmd=cmd, s_cmd="cat %s | %s" % (dconf_file, " ".join(cmd)), stdin=subprocess.PIPE, s_input=dconf_dumped, ) def filers_mount(config, user): """ Triggers manage_cred's extension to mount for us """ try: with open(MANAGE_CRED_PID_FILE.format(username=user.username), "r") as f: manage_cred_pid = int(f.readline()) except: IO.write("Warning, could not find manage_cred process. Not gonna mount filers.") return open(MANAGE_CRED_FLAG_FILE, "a").close() os.kill(manage_cred_pid, signal.SIGUSR2) manage_cred_finished = False for _ in range(MANAGE_CRED_TIMEOUT * 10): time.sleep(0.1) if not os.path.exists(MANAGE_CRED_FLAG_FILE): manage_cred_finished = True break if not manage_cred_finished: IO.write("Warning, manage_cred didn't complete mount filers.") if MANAGE_CRED_TERM: os.kill(manage_cred_pid, signal.SIGTERM) def filers_umount(config, user): """ Performs all umount return True if all succeed return False if one failed """ IO.write("Proceeding umount!") success = True for mount_point in config["mounts"].keys() + [os.path.join(user.home_dir, ".gvfs"), os.path.join(user.home_dir, "freerds_client"), ]: if not ismount(mount_point): IO.write("%s not mounted. Skip." % mount_point) continue for i in xrange(UMOUNT_MAX_ATTEMPT): if run_cmd( cmd=["umount", "-fl", mount_point], ): break time.sleep(UMOUNT_SLEEP) if ismount(mount_point): success = False return success def make_homedir(user): if not os.path.exists(user.home_dir): IO.write("Make homedir") run_cmd( cmd=["cp", "-R", "/etc/skel", user.home_dir] ) run_cmd( cmd=["chown", "-R", "%s:" % user.username, user.home_dir] ) else: IO.write("homedir already exists.") def proceed_roaming_open(config, user): IO.write("Proceeding roaming 'open'!") folders_to_mkdir_as_root = [] def mkdir(folder_name, as_root=False): IO.write("mkdir -p %s" % folder_name) try: os.makedirs(folder_name) except OSError: if not as_root: folders_to_mkdir_as_root.append(folder_name) IO.write("... failed. Will retry as root later!") else: IO.write("... failed.") if as_root: run_cmd( cmd=["chown", "-R", "%s:" % user.username, folder_name] ) def prepare_link(target, link_name, user): if re.search(r'/$', target): target_is_dir = True else: target_is_dir = False if re.match(r'\+', target): force_link = True target = target[1:] else: force_link = False target = os.path.normpath(os.path.join(user.home_dir, target)) link_name = os.path.normpath(os.path.join(user.home_dir, link_name)) target_parent = os.path.normpath(target + "/..") link_name_parent = os.path.normpath(link_name + "/..") already_done = (os.path.islink(link_name) and os.readlink(link_name) == target) if already_done: return if force_link: # create target if non existent if target_is_dir: if not os.path.exists(target): mkdir(target) else: if not os.path.exists(target_parent): mkdir(target_parent) open(target, "a").close() else: no_target = not os.path.exists(target) if no_target: return # Remove link_name if already exist if os.path.isdir(link_name) and not os.path.islink(link_name): shutil.rmtree(link_name) elif os.path.lexists(link_name): os.unlink(link_name) # Make the symlink if not os.path.exists(link_name_parent): mkdir(link_name_parent) IO.write("ln -s %s %s" % (target, link_name)) os.symlink(target, link_name) ## Make homedir make_homedir(user) ## Mounts (sudo) filers_mount(config, user) with UserIdentity(user): ## Links for target, link_name in config["links"] + config["su_links"]: prepare_link(target, link_name, user) for folder_name in folders_to_mkdir_as_root: mkdir(folder_name, as_root=True) def proceed_roaming_close(options, config, user): IO.write("Proceeding roaming 'close'!") ## Links with UserIdentity(user): for target, link_name in config["links"]: if re.match(r'\+', target): target = target[1:] target = os.path.normpath(os.path.join(user.home_dir, target)) target_parent = os.path.normpath(target + "/..") link_name = os.path.normpath(os.path.join(user.home_dir, link_name)) link_name_parent = os.path.normpath(link_name + "/..") if os.path.exists(link_name): if os.path.realpath(link_name) != os.path.realpath(target): # link_name doesn't point to target -> new content -> rm old content. run_cmd( cmd=["rm", "-rf", "--one-file-system", target], ) if not os.path.exists(target): if not os.path.lexists(target_parent): IO.write("mkdir -p %s" % target_parent) os.makedirs(target_parent) if os.path.isdir(link_name): run_cmd( cmd=["cp", "-R", link_name, target], ) else: run_cmd( cmd=["cp", link_name, target], ) dconf_dump(config, user) # Umounts (sudo) if not filers_umount(config, user): IO.write("Skipping rm -rf.") return # RM - for i in xrange(RM_MAX_ATTEMPT): - success = run_cmd( - cmd=["rm", "-rf", "--one-file-system", user.home_dir] - ) - if success: - break - time.sleep(RM_SLEEP) + #for i in xrange(RM_MAX_ATTEMPT): + # success = run_cmd( + # cmd=["rm", "-rf", "--one-file-system", user.home_dir] + # ) + # if success: + # break + # time.sleep(RM_SLEEP) def proceed_guest_open(user): IO.write("Proceeding guest 'open'!") make_homedir(user) def proceed_guest_close(user): IO.write("Proceeding guest 'close'!") IO.write("Nothing to be done ...") def list_current_user_sessions(display=False): try: with open(SESSIONS_COUNT_FILE, "rb") as f: user_sessions = pickle.load(f) except: user_sessions = {} if display: if len(user_sessions) == 0: print "Currently, no user has an open session." else: print "Currently, these users have an open session :" for username in user_sessions: print "%s: %i" % (username, user_sessions[username]) return user_sessions def proceed_on_halt(options): with IO(LOG_PAM): IO.write("\n*** %s" % datetime.datetime.now()) IO.write("Proceeding 'On Halt'!") try: with PreventInterrupt(): with lockfile.FileLock(SEMAPHORE_LOCK_FILE): for username in list_current_user_sessions(): IO.write("on_halt %s" % username) user = read_user(options, username) count_sessions(user, clear_count=True) if user.epfl_account_type == "guest": proceed_guest_close(user) else: config = read_config(options, user) proceed_roaming_close(options, config, user) except Exception, e: exc_type, exc_value, exc_traceback = sys.exc_info() IO.write("\n".join(traceback.format_exception(exc_type, exc_value, exc_traceback))) IO.write("done.") def signal_handler(signum, frame): IO.write("received signal %s" % signum) if PreventInterrupt.is_interruptible(): IO.write("exit.") sys.exit(1) else: IO.write("not interruptible yet. Continuing...") if __name__ == '__main__': try: os.makedirs(VAR_RUN) except OSError: pass # Manage the kill -TERM ... unfortunately not kill -9 signal.signal(signal.SIGTERM, signal_handler) # ~ signal.signal(signal.SIGKILL, signal_handler) options = read_options() if options.context == "on_halt": proceed_on_halt(options) sys.exit(0) if options.context == "list_users": list_current_user_sessions(display=True) sys.exit(0) user = read_user(options) if options.context in ("pam",): logfile_name = LOG_PAM else: logfile_name = LOG_SESSION.format(username=user.username) with IO(logfile_name): try: IO.write("\n*** %s" % datetime.datetime.now()) operation = options.context if options.context == "pam": operation += "_%s" % user.conn_type IO.write("%s %s (uid=%s euid=%s)" % (operation, user.username, os.getuid(), os.geteuid())) check_options(options, user) # EPFL Guests shortcut if user.epfl_account_type == "guest": if options.context == "pam": if user.conn_type == "open_session": with lockfile.FileLock(SEMAPHORE_LOCK_FILE): if count_sessions(user, increment=+1) == (0, 1): proceed_guest_open(user) elif user.conn_type == "close_session": with lockfile.FileLock(SEMAPHORE_LOCK_FILE): if count_sessions(user, increment=-1) == (1, 0): proceed_guest_close(user) sys.exit(0) config = read_config(options, user) if options.context == "pam": if user.conn_type == "open_session": with lockfile.FileLock(SEMAPHORE_LOCK_FILE): if count_sessions(user, increment=+1) == (0, 1): proceed_roaming_open(config, user) elif user.conn_type == "close_session": with lockfile.FileLock(SEMAPHORE_LOCK_FILE): if count_sessions(user, increment=-1) == (1, 0): with PreventInterrupt(): proceed_roaming_close(options, config, user) elif options.context == "session": dconf_load(config, user) elif options.context == "test_load": dconf_load(config, user, test=True) elif options.context == "test_dump": dconf_dump(config, user, test=True) IO.write("Everything complete.") sys.exit(0) except Exception, e: exc_type, exc_value, exc_traceback = sys.exc_info() IO.write("\n".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))