170
171
return (to_add, to_remove)
173
def make_jail(username, uid, force=True, manifest=None, svn_pass=None):
174
def make_jail(username, uid, force=True, svn_pass=None):
174
175
"""Creates a new user's jail space, in the jail directory as configured in
177
This expects there to be a "staging" directory within the jail root which
178
contains all the files for a sample student jail. It creates the student's
179
directory in the jail root, by making a hard-link copy of every file in the
180
staging directory, recursively.
178
This only creates things within /home - everything else is expected to be
179
part of another UnionFS branch.
182
181
Returns the path to the user's home directory.
191
190
force: If false, exception if jail already exists for this user.
192
191
If true (default), overwrites it, but preserves home directory.
194
manifest: If provided this will be a pair (to_add, to_remove) of files or
195
directories to add or remove from the jail.
197
193
svn_pass: If provided this will be a string, the randomly-generated
198
194
Subversion password for this user (if you happen to already have it).
199
195
If not provided, it will be read from the database.
213
205
elif not os.path.isdir(tempdir):
214
206
os.unlink(tempdir)
215
207
os.mkdir(tempdir)
216
userdir = os.path.join(conf.jail_base, username)
208
userdir = os.path.join(conf.jail_src_base, username)
217
209
homedir = os.path.join(userdir, 'home')
210
userhomedir = os.path.join(homedir, username) # Return value
219
212
if os.path.exists(userdir):
221
214
raise Exception("User's jail already exists")
222
215
# User jail already exists. Blow it away but preserve their home
216
# directory. It should be all that is there anyway, but you never
224
218
# Ignore warnings about the use of tmpnam
225
219
warnings.simplefilter('ignore')
226
220
homebackup = os.tempnam(tempdir)
229
223
# into a directory if it already exists, just fails. Therefore it is
230
224
# not susceptible to tmpnam symlink attack.
231
225
shutil.move(homedir, homebackup)
233
# Any errors that occur after making the backup will be caught and
234
# the backup will be un-made.
235
# XXX This will still leave the user's jail in an unusable state,
236
# but at least they won't lose their files.
238
(to_add, to_remove) = manifest
239
# Remove redundant files and directories
241
dst = os.path.join(userdir, d)
242
src = os.path.join(stagingdir, d)
243
if os.path.isdir(dst):
245
elif os.path.isfile(dst):
249
dst = os.path.join(userdir, d)
250
src = os.path.join(stagingdir, d)
251
# Clear the previous file/dir
252
if os.path.isdir(dst):
254
elif os.path.isfile(dst):
257
if os.path.isdir(src):
259
elif os.path.isfile(src):
263
# No manifest, do a full rebuild
264
shutil.rmtree(userdir)
265
# Hard-link (copy aliasing) the entire tree over
266
linktree(stagingdir, userdir)
268
# Set up the user's home directory (restore backup)
269
# First make sure the directory is empty and its parent exists
271
shutil.rmtree(homedir)
274
# XXX If this fails the user's directory will be lost (in the temp
275
# directory). But it shouldn't fail as homedir should not exist.
277
shutil.move(homebackup, homedir)
278
userhomedir = os.path.join(homedir, username) # Return value
226
shutil.rmtree(userdir)
228
shutil.move(homebackup, homedir)
280
230
# No user jail exists
281
# Hard-link (copy aliasing) the entire tree over
282
linktree(stagingdir, userdir)
284
231
# Set up the user's home directory
285
userhomedir = os.path.join(homedir, username)
286
os.mkdir(userhomedir)
232
os.makedirs(userhomedir)
287
233
# Chown (and set the GID to the same as the UID).
288
234
os.chown(userhomedir, uid, uid)
289
235
# Chmod to rwxr-xr-x (755)
290
236
os.chmod(userhomedir, 0755)
292
# There is 1 special file which should not be hard-linked, but instead
293
# generated specific to this user: /opt/ivle/lib/conf/conf.py.
238
# There is 1 special file which needs to be generated specific to this
239
# user: /opt/ivle/lib/conf/conf.py.
294
240
# "__" username "__" users are exempt (special)
295
241
if not (username.startswith("__") and username.endswith("__")):
296
make_conf_py(username, userdir, stagingdir, svn_pass)
242
make_conf_py(username, userdir, conf.jail_system, svn_pass)
298
244
return userhomedir
300
246
def make_conf_py(username, user_jail_dir, staging_dir, svn_pass=None):
302
Creates (overwriting any existing file) a file /opt/ivle/lib/conf/conf.py
303
in a given user's jail.
248
Creates (overwriting any existing file, and creating directories) a
249
file /opt/ivle/lib/conf/conf.py in a given user's jail.
304
250
username: Username.
305
251
user_jail_dir: User's jail dir, ie. conf.jail_base + username
306
252
staging_dir: The dir with the staging copy of the jail. (With the
308
254
svn_pass: As with make_jail. User's SVN password, but if not supplied,
309
255
will look up in the DB.
311
# Note: It is important to delete this file and recreate it (somewhat
312
# ironically beginning by pasting the same contents in again), rather than
314
# Note that all files initially are aliased, so appending would result
315
# in a massive aliasing problem. Deleting and recreating ensures that
316
# the conf.py files are unique to each jail.
317
257
template_conf_path = os.path.join(staging_dir,"opt/ivle/lib/conf/conf.py")
318
258
conf_path = os.path.join(user_jail_dir, "opt/ivle/lib/conf/conf.py")
259
os.makedirs(os.path.dirname(conf_path))
320
261
# If svn_pass isn't supplied, grab it from the DB
321
262
if svn_pass is None:
421
357
# Make sure the file is owned by the web server
422
358
if create == "c":
423
359
chown_to_webserver(conf.svn_auth_local)
361
def mount_jail(login):
362
# This is where we'll mount to...
363
destdir = os.path.join(conf.jail_base, login)
364
# ... and this is where we'll get the user bits.
365
srcdir = os.path.join(conf.jail_src_base, login)
367
if not os.path.exists(destdir):
369
if os.system('/bin/mount -t aufs -o dirs=%s:%s=ro none %s'
370
% (srcdir, conf.jail_system, destdir)) == 0:
371
logging.info("mounted user %s's jail." % login)
373
logging.error("failed to mount user %s's jail!" % login)
374
except Exception, message:
375
logging.warning(str(message))