Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F91202820
epfl_roaming.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, Nov 8, 22:46
Size
31 KB
Mime Type
text/x-python
Expires
Sun, Nov 10, 22:46 (2 d)
Engine
blob
Format
Raw Data
Handle
22219094
Attached To
R8811 EPFL Roaming
epfl_roaming.py
View Options
#!/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
)
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
)))
Event Timeline
Log In to Comment