120
137
if res != 0 and throw_on_error:
121
138
raise Exception("Unable to create ivle-auth for %s" % login)
140
# Make sure the file is owned by the web server
142
chown_to_webserver(conf.svn_auth_ivle)
125
def make_jail(username, uid, force=True):
146
def generate_manifest(basedir, targetdir, parent=''):
147
""" From a basedir and a targetdir work out which files are missing or out
148
of date and need to be added/updated and which files are redundant and need
151
parent: This is used for the recursive call to track the relative paths
152
that we have decended.
155
cmp = filecmp.dircmp(basedir, targetdir)
157
# Add all new files and files that have changed
158
to_add = [os.path.join(parent,x) for x in (cmp.left_only + cmp.diff_files)]
160
# Remove files that are redundant
161
to_remove = [os.path.join(parent,x) for x in cmp.right_only]
164
for d in cmp.common_dirs:
165
newbasedir = os.path.join(basedir, d)
166
newtargetdir = os.path.join(targetdir, d)
167
newparent = os.path.join(parent, d)
168
(sadd,sremove) = generate_manifest(newbasedir, newtargetdir, newparent)
172
return (to_add, to_remove)
175
def make_jail(username, uid, force=True, svn_pass=None):
126
176
"""Creates a new user's jail space, in the jail directory as configured in
129
This expects there to be a "template" directory within the jail root which
130
contains all the files for a sample student jail. It creates the student's
131
directory in the jail root, by making a hard-link copy of every file in the
132
template directory, recursively.
179
This only creates things within /home - everything else is expected to be
180
part of another UnionFS branch.
134
182
Returns the path to the user's home directory.
143
191
force: If false, exception if jail already exists for this user.
144
192
If true (default), overwrites it, but preserves home directory.
194
svn_pass: If provided this will be a string, the randomly-generated
195
Subversion password for this user (if you happen to already have it).
196
If not provided, it will be read from the database.
146
198
# MUST run as root or some of this may fail
147
199
if os.getuid() != 0:
148
200
raise Exception("Must run make_jail as root")
150
templatedir = os.path.join(conf.jail_base, 'template')
151
if not os.path.isdir(templatedir):
152
raise Exception("Template jail directory does not exist: " +
154
202
# tempdir is for putting backup homes in
155
tempdir = os.path.join(conf.jail_base, 'temp')
203
tempdir = os.path.join(conf.jail_base, '__temp__')
156
204
if not os.path.exists(tempdir):
157
205
os.makedirs(tempdir)
158
206
elif not os.path.isdir(tempdir):
159
207
os.unlink(tempdir)
160
208
os.mkdir(tempdir)
161
userdir = os.path.join(conf.jail_base, username)
209
userdir = os.path.join(conf.jail_src_base, username)
162
210
homedir = os.path.join(userdir, 'home')
211
userhomedir = os.path.join(homedir, username) # Return value
164
213
if os.path.exists(userdir):
166
215
raise Exception("User's jail already exists")
167
216
# User jail already exists. Blow it away but preserve their home
217
# directory. It should be all that is there anyway, but you never
169
219
# Ignore warnings about the use of tmpnam
170
220
warnings.simplefilter('ignore')
171
221
homebackup = os.tempnam(tempdir)
174
224
# into a directory if it already exists, just fails. Therefore it is
175
225
# not susceptible to tmpnam symlink attack.
176
226
shutil.move(homedir, homebackup)
178
# Any errors that occur after making the backup will be caught and
179
# the backup will be un-made.
180
# XXX This will still leave the user's jail in an unusable state,
181
# but at least they won't lose their files.
182
shutil.rmtree(userdir)
184
# Hard-link (copy aliasing) the entire tree over
185
linktree(templatedir, userdir)
187
# Set up the user's home directory (restore backup)
188
# First make sure the directory is empty and its parent exists
190
shutil.rmtree(homedir)
193
# XXX If this fails the user's directory will be lost (in the temp
194
# directory). But it shouldn't fail as homedir should not exist.
196
shutil.move(homebackup, homedir)
197
return os.path.join(homedir, username)
227
shutil.rmtree(userdir)
229
shutil.move(homebackup, homedir)
230
# Change the ownership of all the files to the right unixid
231
logging.debug("chown %s's home directory files to uid %d"
233
os.chown(userhomedir, uid, uid)
234
for root, dirs, files in os.walk(userhomedir):
235
for fsobj in dirs + files:
236
os.chown(os.path.join(root, fsobj), uid, uid)
199
238
# No user jail exists
200
# Hard-link (copy aliasing) the entire tree over
201
linktree(templatedir, userdir)
203
239
# Set up the user's home directory
204
userhomedir = os.path.join(homedir, username)
205
os.mkdir(userhomedir)
240
os.makedirs(userhomedir)
206
241
# Chown (and set the GID to the same as the UID).
207
242
os.chown(userhomedir, uid, uid)
208
243
# Chmod to rwxr-xr-x (755)
209
244
os.chmod(userhomedir, 0755)
246
# There are 2 special files which need to be generated specific to this
247
# user: /opt/ivle/lib/conf/conf.py and /etc/passwd.
248
# "__" username "__" users are exempt (special)
249
if not (username.startswith("__") and username.endswith("__")):
250
make_conf_py(username, userdir, conf.jail_system, svn_pass)
251
make_etc_passwd(username, userdir, conf.jail_system, uid)
255
def make_conf_py(username, user_jail_dir, staging_dir, svn_pass=None):
257
Creates (overwriting any existing file, and creating directories) a
258
file /opt/ivle/lib/conf/conf.py in a given user's jail.
260
user_jail_dir: User's jail dir, ie. conf.jail_base + username
261
staging_dir: The dir with the staging copy of the jail. (With the
262
template conf.py file).
263
svn_pass: As with make_jail. User's SVN password, but if not supplied,
264
will look up in the DB.
266
template_conf_path = os.path.join(staging_dir,"opt/ivle/lib/conf/conf.py")
267
conf_path = os.path.join(user_jail_dir, "opt/ivle/lib/conf/conf.py")
268
os.makedirs(os.path.dirname(conf_path))
270
# If svn_pass isn't supplied, grab it from the DB
273
svn_pass = dbconn.get_user(username).svn_pass
276
# Read the contents of the template conf file
278
template_conf_file = open(template_conf_path, "r")
279
template_conf_data = template_conf_file.read()
280
template_conf_file.close()
282
# Couldn't open template conf.py for some reason
283
# Just treat it as empty file
284
template_conf_data = ("# Warning: Problem building config script.\n"
285
"# Could not find template conf.py file.\n")
287
conf_file = open(conf_path, "w")
288
conf_file.write(template_conf_data)
289
conf_file.write("\n# The login name for the owner of the jail\n")
290
conf_file.write("login = %s\n" % repr(username))
291
conf_file.write("\n")
292
conf_file.write("# The subversion-only password for the owner of "
294
conf_file.write("svn_pass = %s\n" % repr(svn_pass))
297
# Make this file world-readable
298
# (chmod 644 conf_path)
299
os.chmod(conf_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP
302
def make_etc_passwd(username, user_jail_dir, template_dir, unixid):
304
Creates /etc/passwd in the given user's jail. This will be identical to
305
that in the template jail, except for the added entry for this user.
307
template_passwd_path = os.path.join(template_dir, "etc/passwd")
308
passwd_path = os.path.join(user_jail_dir, "etc/passwd")
309
passwd_dir = os.path.dirname(passwd_path)
310
if not os.path.exists(passwd_dir):
311
os.makedirs(passwd_dir)
312
shutil.copy(template_passwd_path, passwd_path)
313
passwd_file = open(passwd_path, 'a')
314
passwd_file.write('%s:x:%d:%d::/home/%s:/bin/bash'
315
% (username, unixid, unixid, username))
212
318
def linktree(src, dst):
213
319
"""Recursively hard-link a directory tree using os.link().
273
381
if res != 0 and throw_on_error:
274
382
raise Exception("Unable to create local-auth for %s" % kwargs['login'])
384
# Make sure the file is owned by the web server
386
chown_to_webserver(conf.svn_auth_local)
388
# Pulldown subjects and add enrolments
389
pulldown_subj.enrol_user(kwargs['login'])
391
def mount_jail(login):
392
# This is where we'll mount to...
393
destdir = os.path.join(conf.jail_base, login)
394
# ... and this is where we'll get the user bits.
395
srcdir = os.path.join(conf.jail_src_base, login)
397
if not os.path.exists(destdir):
399
if os.system('/bin/mount -t aufs -o dirs=%s:%s=ro none %s'
400
% (srcdir, conf.jail_system, destdir)) == 0:
401
logging.info("mounted user %s's jail." % login)
403
logging.error("failed to mount user %s's jail!" % login)
404
except Exception, message:
405
logging.warning(str(message))