~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to ivle/chat.py

  • Committer: Matt Giuca
  • Date: 2010-07-22 02:12:36 UTC
  • mfrom: (1812.1.13 late-submit)
  • Revision ID: matt.giuca@gmail.com-20100722021236-k8kt4cqdtywzpk24
Merge from trunk late-submit.
Students may now submit projects after the deadline, but they are warned that the submission is late.
Lecturers are now given data on which submissions were made late, and how many days.
(LP: #598346)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# IVLE - Informatics Virtual Learning Environment
 
2
# Copyright (C) 2007-2008 The University of Melbourne
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
17
 
 
18
# Module: Chat
 
19
# Author: Thomas Conway
 
20
# Date:   5/2/2008
 
21
 
 
22
import cjson
 
23
import cStringIO
 
24
import hashlib
 
25
import sys
 
26
import os
 
27
import socket
 
28
import traceback
 
29
 
 
30
SOCKETTIMEOUT = 60
 
31
BLOCKSIZE = 1024
 
32
 
 
33
class Terminate(Exception):
 
34
    """Exception thrown when server is to be shut down. It will attempt to
 
35
    send the final_response to the client and then exits"""
 
36
    def __init__(self, final_response=None):
 
37
        self.final_response = final_response
 
38
 
 
39
    def __str__(self):
 
40
        return repr(self.final_response)
 
41
 
 
42
class ProtocolError(Exception):
 
43
    """Exception thrown when client violates the the chat protocol"""
 
44
    pass
 
45
 
 
46
def start_server(port, magic, daemon_mode, handler, initializer = None):
 
47
    # Attempt to open the socket.
 
48
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
49
    s.bind(('', port))
 
50
    s.listen(1)
 
51
 
 
52
    # Excellent! It worked. Let's turn ourself into a daemon,
 
53
    # then get on with the job of being a python interpreter.
 
54
    if daemon_mode:
 
55
        if os.fork():   # launch child and...
 
56
            os._exit(0) # kill off parent
 
57
        os.setsid()
 
58
        if os.fork():   # launch child and...
 
59
            os._exit(0) # kill off parent again.
 
60
        os.umask(077)
 
61
 
 
62
        try:
 
63
            MAXFD = os.sysconf("SC_OPEN_MAX")
 
64
        except:
 
65
            MAXFD = 256
 
66
 
 
67
        # Close all file descriptors, except the socket.
 
68
        for i in xrange(MAXFD):
 
69
            if i == s.fileno():
 
70
                continue
 
71
            try:
 
72
                os.close(i)
 
73
            except OSError:
 
74
                pass
 
75
 
 
76
        si = os.open(os.devnull, os.O_RDONLY)
 
77
        os.dup2(si, sys.stdin.fileno())
 
78
 
 
79
        so = os.open(os.devnull, os.O_WRONLY)
 
80
        os.dup2(so, sys.stdout.fileno())
 
81
 
 
82
        se = os.open(os.devnull, os.O_WRONLY)
 
83
        os.dup2(se, sys.stderr.fileno())
 
84
 
 
85
    if initializer:
 
86
        initializer()
 
87
 
 
88
    while True:
 
89
        (conn, addr) = s.accept()
 
90
        conn.settimeout(SOCKETTIMEOUT)
 
91
        try:
 
92
            # Grab the input and try to decode
 
93
            inp = recv_netstring(conn)
 
94
            try:
 
95
                content = decode(inp, magic)
 
96
            except ProtocolError:
 
97
                conn.close()
 
98
                continue
 
99
 
 
100
            response = handler(content)
 
101
 
 
102
            send_netstring(conn, cjson.encode(response))
 
103
 
 
104
            conn.close()
 
105
 
 
106
        except Terminate, t:
 
107
            # Try and send final response and then terminate
 
108
            if t.final_response:
 
109
                send_netstring(conn, cjson.encode(t.final_response))
 
110
            conn.close()
 
111
            sys.exit(0)
 
112
        except Exception:
 
113
            # Make a JSON object full of exceptional goodness
 
114
            tb_dump = cStringIO.StringIO()
 
115
            e_type, e_val, e_tb = sys.exc_info()
 
116
            traceback.print_tb(e_tb, file=tb_dump)
 
117
            json_exc = {
 
118
                "type": e_type.__name__,
 
119
                "value": str(e_val),
 
120
                "traceback": tb_dump.getvalue()
 
121
            }
 
122
            send_netstring(conn, cjson.encode(json_exc))
 
123
            conn.close()
 
124
 
 
125
 
 
126
def chat(host, port, msg, magic, decode = True):
 
127
    sok = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
128
    sok.connect((host, port))
 
129
    sok.settimeout(SOCKETTIMEOUT)
 
130
 
 
131
    json = encode(msg, magic)
 
132
 
 
133
    send_netstring(sok, json)
 
134
    inp = recv_netstring(sok)
 
135
 
 
136
    sok.close()
 
137
 
 
138
    if decode:
 
139
        return cjson.decode(inp)
 
140
    else:
 
141
        return inp
 
142
 
 
143
def encode(message, magic):
 
144
    """Converts a message into a JSON serialisation and uses a magic
 
145
    string to attach a HMAC digest.
 
146
    """
 
147
    # XXX: Any reason that we double encode?
 
148
    content = cjson.encode(message)
 
149
 
 
150
    digest = hashlib.md5(content + magic).hexdigest()
 
151
    env = {'digest':digest,'content':content}
 
152
    json = cjson.encode(env)
 
153
 
 
154
    return json
 
155
 
 
156
 
 
157
def decode(message, magic):
 
158
    """Takes a message with an attached HMAC digest and validates the message.
 
159
    """
 
160
    msg = cjson.decode(message)
 
161
 
 
162
    # Check that the message is valid
 
163
    digest = hashlib.md5(msg['content'] + magic).hexdigest()
 
164
    if msg['digest'] != digest:
 
165
        raise ProtocolError("HMAC digest is invalid")
 
166
    content = cjson.decode(msg['content'])
 
167
 
 
168
    return content
 
169
 
 
170
 
 
171
def send_netstring(sok, data):
 
172
    """ Sends a netstring to a socket
 
173
    """
 
174
    netstring = "%d:%s,"%(len(data),data)
 
175
    sok.sendall(netstring)
 
176
 
 
177
 
 
178
def recv_netstring(sok):
 
179
    """ Attempts to recieve a Netstring from a socket.
 
180
    Throws a ProtocolError if the received data violates the Netstring 
 
181
    protocol.
 
182
    """
 
183
    # Decode netstring
 
184
    size_buffer = []
 
185
    c = sok.recv(1)
 
186
    while c != ':':
 
187
        # Limit the Netstring to less than 10^10 bytes (~1GB).
 
188
        if len(size_buffer) >= 10:
 
189
            raise ProtocolError(
 
190
                    "Could not read Netstring size in first 9 bytes: '%s'"%(
 
191
                    ''.join(size_buffer)))
 
192
        size_buffer.append(c)
 
193
        c = sok.recv(1)
 
194
    try:
 
195
        # Message should be length plus ',' terminator
 
196
        recv_expected = int(''.join(size_buffer)) + 1
 
197
    except ValueError, e:
 
198
        raise ProtocolError("Could not decode Netstring size as int: '%s'"%(
 
199
                ''.join(size_buffer)))
 
200
 
 
201
    # Read data
 
202
    buf = []
 
203
    recv_data = sok.recv(min(recv_expected, BLOCKSIZE))
 
204
    recv_size = len(recv_data)
 
205
    while recv_size < recv_expected:
 
206
        buf.append(recv_data)
 
207
        recv_data = sok.recv(min(recv_expected-recv_size, 1024))
 
208
        recv_size = recv_size + len(recv_data)
 
209
    assert(recv_size == recv_expected)
 
210
 
 
211
    # Did we receive the correct amount?
 
212
    if recv_data[-1] != ',':
 
213
        raise ProtocolError("Netstring did not end with ','")
 
214
    buf.append(recv_data[:-1])
 
215
 
 
216
    return ''.join(buf)