diff --git a/root/usr/local/bin/epfl_roaming.py b/root/usr/local/bin/epfl_roaming.py index 7237c6c..cb41e2a 100755 --- a/root/usr/local/bin/epfl_roaming.py +++ b/root/usr/local/bin/epfl_roaming.py @@ -1,946 +1,985 @@ #!/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 keep_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 UNLINK_CRED_FILE = True -SEMAPHORE_LOCK_FILE = "/tmp/epfl_roaming_global_lock" +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, GConf/DConf save", action="store_const", dest="context", default=None, const="pam", ) parser.add_argument( "--session", help="Session (GConf/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 GConf and DConf (test)", action="store_const", dest="context", const="test_load", ) parser.add_argument( "--test_dump", help="Dump GConf and 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 # 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.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"): 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. Exiting.") sys.exit(1) 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" : [], "gconf" : {}, "dconf" : {},} gconf_file = "" 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") ## gconf file elif subject == "gconf_file": try: gconf_file = re.findall(r'"(.+)"', line)[0] gconf_file = apply_subst(gconf_file, user) except IndexError, e: raise ConfigLineException(line, reason="syntax") ## gconf entry elif subject == "gconf": if gconf_file == "": raise ConfigLineException(line, reason="gconf key before gconf_file instruction") try: gconf_entry = re.findall(r'"(.+)"', line)[0] conf["gconf"].setdefault(gconf_file, []).append(gconf_entry) 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): +def count_sessions(user, increment=0, clear_count=False): """ Increments/decrements session count for current user """ - user_count_file = "/tmp/epfl_count_%s" % user.username - IO.write("session counter :", eol="") - counter = 0 # default try: - with open(user_count_file, "r") as f: - counter = int(f.readline()) - except (IOError, ValueError): - pass - - IO.write("%i -> %i" % (counter, counter + increment)) - counter += increment - if counter > 0: - with open(user_count_file, "w") as f: - f.write("%i\n" % counter) + 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: - try: - os.unlink(user_count_file) - except OSError: - pass - return counter + 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 get_credentials(username): cred_filename = "/tmp/%s_epfl_cred" % username # Decode credential def decode(username, enc_password): username = unicode(username, 'utf-8') factor = len(enc_password) / len(username) + 1 key = username * factor password = "".join([unichr(ord(enc_password[i]) - ord(key[i])) for i in range(0, len(enc_password)) ]) return password.encode('utf-8') try: with open(cred_filename, "rb") as f: if UNLINK_CRED_FILE: os.unlink(cred_filename) enc_password = pickle.load(f) except Exception: IO.write("Warning: could not load file %s, skipping." % cred_filename) return None return decode(username, enc_password) 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): """ Performs all mount return True if all succeed return False if one failed """ IO.write("Proceeding mount!") success = True if PASSWORD != None: os.environ['PASSWD'] = PASSWORD # For CIFS mounts for mount_point, mount_instruction in config["mounts"].items(): if not os.path.exists(mount_point): #~ os.makedirs(mount_point) run_cmd( cmd=["mkdir", "-p", mount_point] ) run_cmd( cmd=["chown", "%s:" % user.username, mount_point] ) # chown parents also parent_dir = os.path.dirname(mount_point) while parent_dir.startswith(user.home_dir): run_cmd( cmd=["chown", "%s:" % user.username, parent_dir] ) parent_dir = os.path.dirname(parent_dir) run_cmd( cmd=mount_instruction, shell=True, ) if not ismount(mount_point): success = False if PASSWORD != None: del os.environ['PASSWD'] return success 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'!") paths_to_chown = [] 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): os.makedirs(target) else: if not os.path.exists(target_parent): os.makedirs(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): os.makedirs(link_name_parent) IO.write("ln -s %s %s" % (target, link_name)) os.symlink(target, link_name) # Symlink link_name and it's parents if link_name not in paths_to_chown: paths_to_chown.append(link_name) path_to_chown = os.path.dirname(link_name) while path_to_chown.startswith(user.home_dir): if path_to_chown not in paths_to_chown: paths_to_chown.append(path_to_chown) path_to_chown = os.path.dirname(path_to_chown) ## Make homedir make_homedir(user) ## Mounts (sudo) filers_mount(config, user) ## Links for target, link_name in config["links"] + config["su_links"]: prepare_link(target, link_name, user) if len(paths_to_chown) != 0: run_cmd( cmd=["chown", "-h", "%s:" % user.username] + paths_to_chown ) def proceed_roaming_close(options, config, user): IO.write("Proceeding roaming 'close'!") ## Links 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) 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 proceed_on_halt(options): - def list_current_users(): - return [f[11:] for f in os.listdir("/tmp") if f.startswith("epfl_count")] +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_users(): + for username in list_current_user_sessions(): IO.write("on_halt %s" % username) user = read_user(options, username) - config = read_config(options, user) - proceed_roaming_close(options, config, user) - try: - os.unlink("/tmp/epfl_count_%s" % user.username) - except OSError: - pass + 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__': username = os.environ.get("PAM_USER", None) if username is not None: PASSWORD = get_credentials(username) + 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) EPFL_ROAMING_DONE_FILE = os.path.join("/tmp/epfl_roaming_{}_done".format(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": - proceed_guest_open(user) + with lockfile.FileLock(SEMAPHORE_LOCK_FILE): + if count_sessions(user, increment=+1) == (0, 1): + proceed_guest_open(user) elif user.conn_type == "close_session": - proceed_guest_close(user) + 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): - count_sessions(user, +1) + count_sessions(user, increment=+1) if PASSWORD is not None and not os.path.exists(EPFL_ROAMING_DONE_FILE): proceed_roaming_open(config, user) with open(EPFL_ROAMING_DONE_FILE, "w"): pass elif user.conn_type == "close_session": with lockfile.FileLock(SEMAPHORE_LOCK_FILE): - if count_sessions(user, -1) == 0: + if count_sessions(user, increment=-1) == (1, 0): with PreventInterrupt(): proceed_roaming_close(options, config, user) os.unlink(EPFL_ROAMING_DONE_FILE) 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)))