summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPrabhu Ramachandran2011-11-12 10:02:57 +0530
committerPrabhu Ramachandran2011-11-12 10:02:57 +0530
commitfb31dcc7693395c9856fb19c3e867fbfa2e9b4f8 (patch)
tree24b50d4bce7ac5faa5ec6ae98db041766340814b
parentc69bc272c1c3ba02c3f244586b65254036e73de3 (diff)
downloadonline_test-fb31dcc7693395c9856fb19c3e867fbfa2e9b4f8.tar.gz
online_test-fb31dcc7693395c9856fb19c3e867fbfa2e9b4f8.tar.bz2
online_test-fb31dcc7693395c9856fb19c3e867fbfa2e9b4f8.zip
ENH: Running remote code safely via XMLRPC.
Adding a python_server which executes code as nobody safely so users cannot do too much damage.
-rw-r--r--README.txt17
-rw-r--r--exam/views.py42
-rw-r--r--exam/xmlrpc_clients.py5
-rwxr-xr-xpython_server.py65
4 files changed, 87 insertions, 42 deletions
diff --git a/README.txt b/README.txt
index fdfddf5..cb783ee 100644
--- a/README.txt
+++ b/README.txt
@@ -19,17 +19,24 @@ To install/deploy this app follow the steps below:
Note that you can supply multiple xml files as arguments and all of
those will be added to the database.
-
- 5. Now, run::
+
+ 5. First run the python server provided. This ensures that the code is
+ executed in a safe environment. Do this like so::
+
+ $ sudo python python_server.py
+
+ Using sudo is necessary since the server is run as the user "nobody".
+
+ 6. Now, run::
$ python manage.py runserver <desired_ip>:<desired_port>
- 6. Go to http://server_ip:server_port/admin
+ 7. Go to http://server_ip:server_port/admin
- 7. Login with your credentials and look at the questions and modify if
+ 8. Login with your credentials and look at the questions and modify if
needed.
- 7. Now ask users to login at:
+ 9. Now ask users to login at:
http://server_ip:server_port/exam
And you should be all set.
diff --git a/exam/views.py b/exam/views.py
index 7ee3c53..ed33c91 100644
--- a/exam/views.py
+++ b/exam/views.py
@@ -10,6 +10,7 @@ from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.template import RequestContext
from exam.models import Question, Quiz, Profile, Answer
from exam.forms import UserRegisterForm, UserLoginForm
+from exam.xmlrpc_clients import python_server
def gen_key(no_of_chars):
"""Generate a random key of the number of characters."""
@@ -116,41 +117,6 @@ def question(request, q_id):
return render_to_response('exam/question.html', context,
context_instance=ci)
-def test_python(answer, test_code):
- """Tests given Python function with the test code supplied.
-
- Returns
- -------
-
- A tuple: (success, error message).
-
- """
- success = False
- tb = None
- try:
- submitted = compile(answer, '<string>', mode='exec')
- g = {}
- exec submitted in g
- _tests = compile(test_code, '<string>', mode='exec')
- exec _tests in g
- except AssertionError:
- type, value, tb = sys.exc_info()
- info = traceback.extract_tb(tb)
- fname, lineno, func, text = info[-1]
- text = str(test_code).splitlines()[lineno-1]
- err = "{0} {1} in: {2}".format(type.__name__, str(value), text)
- except:
- type, value = sys.exc_info()[:2]
- err = "Error: {0}".format(repr(value))
- else:
- success = True
- err = 'Correct answer'
- finally:
- del tb
-
- return success, err
-
-
def check(request, q_id):
user = request.user
question = get_object_or_404(Question, pk=q_id)
@@ -162,8 +128,10 @@ def check(request, q_id):
next_q = quiz.skip()
return show_question(request, next_q)
- # Otherwise we were asked to check.
- success, err_msg = test_python(answer, question.test)
+ # Otherwise we were asked to check. We obtain the results via XML-RPC
+ # with the code executed safely in a separate process (the python_server.py)
+ # running as nobody.
+ success, err_msg = python_server.run_code(answer, question.test)
# Add the answer submitted.
new_answer = Answer(question=question, answer=answer.strip())
diff --git a/exam/xmlrpc_clients.py b/exam/xmlrpc_clients.py
new file mode 100644
index 0000000..1bc5513
--- /dev/null
+++ b/exam/xmlrpc_clients.py
@@ -0,0 +1,5 @@
+from xmlrpclib import ServerProxy
+
+# Connect to the python server.
+python_server = ServerProxy('http://localhost:8001')
+ \ No newline at end of file
diff --git a/python_server.py b/python_server.py
new file mode 100755
index 0000000..debd73d
--- /dev/null
+++ b/python_server.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+"""This server runs an XMLRPC server that can be submitted code and tests
+and returns the output. It *should* be run as root and will run as the user
+'nobody' so as to minimize any damange by errant code.
+"""
+import sys
+from SimpleXMLRPCServer import SimpleXMLRPCServer
+import pwd
+import os
+
+
+# Set the effective uid
+nobody = pwd.getpwnam('nobody')
+os.seteuid(nobody.pw_uid)
+
+
+def run_code(answer, test_code, in_dir=None):
+ """Tests given Python function (`answer`) with the `test_code` supplied.
+ If the optional `in_dir` keyword argument is supplied it changes the
+ directory to that directory (it does not change it back to the original when
+ done).
+
+ Returns
+ -------
+
+ A tuple: (success, error message).
+
+ """
+ if in_dir is not None:
+ os.chdir(in_dir)
+
+ success = False
+ tb = None
+ try:
+ submitted = compile(answer, '<string>', mode='exec')
+ g = {}
+ exec submitted in g
+ _tests = compile(test_code, '<string>', mode='exec')
+ exec _tests in g
+ except AssertionError:
+ type, value, tb = sys.exc_info()
+ info = traceback.extract_tb(tb)
+ fname, lineno, func, text = info[-1]
+ text = str(test_code).splitlines()[lineno-1]
+ err = "{0} {1} in: {2}".format(type.__name__, str(value), text)
+ except:
+ type, value = sys.exc_info()[:2]
+ err = "Error: {0}".format(repr(value))
+ else:
+ success = True
+ err = 'Correct answer'
+ finally:
+ del tb
+
+ return success, err
+
+
+def main():
+ server = SimpleXMLRPCServer(("localhost", 8001))
+ server.register_function(run_code)
+ server.serve_forever()
+
+if __name__ == '__main__':
+ main()
+ \ No newline at end of file