32
32
# TODO: When creating a new home directory, chown it to its owner
34
# TODO: In chown_to_webserver:
35
# Do not call os.system("chown www-data") - use Python lib
36
# and use the web server uid given in conf. (Several places).
49
def chown_to_webserver(filename):
51
Chowns a file so the web server user owns it.
52
(This is useful in setting up Subversion conf files).
56
os.system("chown -R www-data:www-data %s" % filename)
60
def make_svn_repo(login, throw_on_error=True):
61
"""Create a repository for the given user.
63
path = os.path.join(conf.svn_repo_path, login)
65
res = os.system("svnadmin create '%s'" % path)
66
if res != 0 and throw_on_error:
67
raise Exception("Cannot create repository for %s" % login)
68
except Exception, exc:
73
chown_to_webserver(path)
75
def rebuild_svn_config():
76
"""Build the complete SVN configuration file.
79
res = conn.query("SELECT login, rolenm FROM login;").dictresult()
83
if role not in groups:
85
groups[role].append(r['login'])
86
f = open(conf.svn_conf + ".new", "w")
87
f.write("# IVLE SVN Repositories Configuration\n")
88
f.write("# Auto-generated on %s\n" % time.asctime())
91
for (g,ls) in groups.iteritems():
92
f.write("%s = %s\n" % (g, ",".join(ls)))
96
f.write("[%s:/]\n" % login)
97
f.write("%s = rw\n" % login)
98
#f.write("@tutor = r\n")
99
#f.write("@lecturer = rw\n")
100
#f.write("@admin = rw\n")
103
os.rename(conf.svn_conf + ".new", conf.svn_conf)
104
chown_to_webserver(conf.svn_conf)
106
def make_svn_config(login, throw_on_error=True):
107
"""Add an entry to the apache-svn config file for the given user.
108
Assumes the given user is either a guest or a student.
110
f = open(conf.svn_conf, "a")
111
f.write("[%s:/]\n" % login)
112
f.write("%s = rw\n" % login)
113
#f.write("@tutor = r\n")
114
#f.write("@lecturer = rw\n")
115
#f.write("@admin = rw\n")
118
chown_to_webserver(conf.svn_conf)
120
def make_svn_auth(login, throw_on_error=True):
121
"""Setup svn authentication for the given user.
122
FIXME: create local.auth entry
124
passwd = md5.new(uuid.uuid4().bytes).digest().encode('hex')
125
if os.path.exists(conf.svn_auth_ivle):
130
db.DB().update_user(login, svn_pass=passwd)
132
res = os.system("htpasswd -%smb %s %s %s" % (create,
135
if res != 0 and throw_on_error:
136
raise Exception("Unable to create ivle-auth for %s" % login)
138
# Make sure the file is owned by the web server
140
chown_to_webserver(conf.svn_auth_ivle)
144
def generate_manifest(basedir, targetdir, parent=''):
145
""" From a basedir and a targetdir work out which files are missing or out
146
of date and need to be added/updated and which files are redundant and need
149
parent: This is used for the recursive call to track the relative paths
150
that we have decended.
153
cmp = filecmp.dircmp(basedir, targetdir)
155
# Add all new files and files that have changed
156
to_add = [os.path.join(parent,x) for x in (cmp.left_only + cmp.diff_files)]
158
# Remove files that are redundant
159
to_remove = [os.path.join(parent,x) for x in cmp.right_only]
162
for d in cmp.common_dirs:
163
newbasedir = os.path.join(basedir, d)
164
newtargetdir = os.path.join(targetdir, d)
165
newparent = os.path.join(parent, d)
166
(sadd,sremove) = generate_manifest(newbasedir, newtargetdir, newparent)
170
return (to_add, to_remove)
173
def make_jail(username, uid, force=True, manifest=None, svn_pass=None):
42
def make_jail(username, uid, force=True):
174
43
"""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
46
This expects there to be a "template" directory within the jail root which
178
47
contains all the files for a sample student jail. It creates the student's
179
48
directory in the jail root, by making a hard-link copy of every file in the
180
staging directory, recursively.
49
template directory, recursively.
182
51
Returns the path to the user's home directory.
191
60
force: If false, exception if jail already exists for this user.
192
61
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
svn_pass: If provided this will be a string, the randomly-generated
198
Subversion password for this user (if you happen to already have it).
199
If not provided, it will be read from the database.
201
63
# MUST run as root or some of this may fail
202
64
if os.getuid() != 0:
203
65
raise Exception("Must run make_jail as root")
205
stagingdir = os.path.join(conf.jail_base, '__staging__')
206
if not os.path.isdir(stagingdir):
207
raise Exception("Staging jail directory does not exist: " +
67
templatedir = os.path.join(conf.jail_base, 'template')
68
if not os.path.isdir(templatedir):
69
raise Exception("Template jail directory does not exist: " +
209
71
# tempdir is for putting backup homes in
210
tempdir = os.path.join(conf.jail_base, '__temp__')
72
tempdir = os.path.join(conf.jail_base, 'temp')
211
73
if not os.path.exists(tempdir):
212
74
os.makedirs(tempdir)
213
75
elif not os.path.isdir(tempdir):
234
96
# the backup will be un-made.
235
97
# XXX This will still leave the user's jail in an unusable state,
236
98
# 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)
99
shutil.rmtree(userdir)
101
# Hard-link (copy aliasing) the entire tree over
102
linktree(templatedir, userdir)
268
104
# Set up the user's home directory (restore backup)
269
105
# First make sure the directory is empty and its parent exists
275
111
# directory). But it shouldn't fail as homedir should not exist.
276
112
os.makedirs(homedir)
277
113
shutil.move(homebackup, homedir)
278
userhomedir = os.path.join(homedir, username) # Return value
114
return os.path.join(homedir, username)
280
116
# No user jail exists
281
117
# Hard-link (copy aliasing) the entire tree over
282
linktree(stagingdir, userdir)
118
linktree(templatedir, userdir)
284
120
# Set up the user's home directory
285
121
userhomedir = os.path.join(homedir, username)
286
122
os.mkdir(userhomedir)
287
123
# Chown (and set the GID to the same as the UID).
288
124
os.chown(userhomedir, uid, uid)
289
# Chmod to rwxr-xr-x (755)
290
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.
294
# "__" username "__" users are exempt (special)
295
if not (username.startswith("__") and username.endswith("__")):
296
make_conf_py(username, userdir, stagingdir, svn_pass)
300
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.
305
user_jail_dir: User's jail dir, ie. conf.jail_base + username
306
staging_dir: The dir with the staging copy of the jail. (With the
307
template conf.py file).
308
svn_pass: As with make_jail. User's SVN password, but if not supplied,
309
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
template_conf_path = os.path.join(staging_dir,"opt/ivle/lib/conf/conf.py")
318
conf_path = os.path.join(user_jail_dir, "opt/ivle/lib/conf/conf.py")
320
# If svn_pass isn't supplied, grab it from the DB
323
svn_pass = dbconn.get_user(username).svn_pass
326
# Read the contents of the template conf file
328
template_conf_file = open(template_conf_path, "r")
329
template_conf_data = template_conf_file.read()
330
template_conf_file.close()
332
# Couldn't open template conf.py for some reason
333
# Just treat it as empty file
334
template_conf_data = ""
336
# Remove the target conf file if it exists
341
conf_file = open(conf_path, "w")
342
conf_file.write(template_conf_data)
343
conf_file.write("\n# The login name for the owner of the jail\n")
344
conf_file.write("login = %s\n" % repr(username))
345
conf_file.write("\n")
346
conf_file.write("# The subversion-only password for the owner of "
348
conf_file.write("svn_pass = %s\n" % repr(svn_pass))
351
# Make this file world-readable
352
# (chmod 644 conf_path)
353
os.chmod(conf_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP
125
# Chmod to rwx------ (700)
126
os.chmod(userhomedir, stat.S_IRWXU)
356
129
def linktree(src, dst):
357
130
"""Recursively hard-link a directory tree using os.link().
393
166
raise Exception, errors
395
def make_user_db(throw_on_error = True, **kwargs):
168
def make_user_db(login, password, nick, fullname, rolenm, studentid,
396
170
"""Creates a user's entry in the database, filling in all the fields.
397
All arguments must be keyword args. They are the fields in the table.
398
However, instead of supplying a "passhash", you must supply a
399
"password" argument, which will be hashed internally.
400
Also do not supply a state. All users are created in the "no_agreement"
402
Throws an exception if the user already exists.
171
If force is False, throws an exception if the user already exists.
172
If True, overwrites the user's entry in the DB.
405
dbconn.create_user(**kwargs)
176
# Delete user if it exists
178
dbconn.delete_user(login)
181
dbconn.create_user(login, password, nick, fullname, rolenm, studentid)
408
if kwargs['password']:
409
if os.path.exists(conf.svn_auth_local):
413
res = os.system("htpasswd -%smb %s %s %s" % (create,
417
if res != 0 and throw_on_error:
418
raise Exception("Unable to create local-auth for %s" % kwargs['login'])
420
# Make sure the file is owned by the web server
422
chown_to_webserver(conf.svn_auth_local)