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

« back to all changes in this revision

Viewing changes to ivle/chat.py

  • Committer: William Grant
  • Date: 2012-06-28 01:52:02 UTC
  • Revision ID: me@williamgrant.id.au-20120628015202-f6ru7o367gt6nvgz
Hah

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
try:
 
23
    import json
 
24
except ImportError:
 
25
    import simplejson as json
 
26
 
 
27
import cStringIO
 
28
import hashlib
 
29
import sys
 
30
import os
 
31
import socket
 
32
import traceback
 
33
 
 
34
SOCKETTIMEOUT = 60
 
35
BLOCKSIZE = 1024
 
36
 
 
37
class Terminate(Exception):
 
38
    """Exception thrown when server is to be shut down. It will attempt to
 
39
    send the final_response to the client and then exits"""
 
40
    def __init__(self, final_response=None):
 
41
        self.final_response = final_response
 
42
 
 
43
    def __str__(self):
 
44
        return repr(self.final_response)
 
45
 
 
46
class ProtocolError(Exception):
 
47
    """Exception thrown when client violates the the chat protocol"""
 
48
    pass
 
49
 
 
50
def start_server(port, magic, daemon_mode, handler, initializer = None):
 
51
    # Attempt to open the socket.
 
52
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
53
    s.bind(('', port))
 
54
    s.listen(1)
 
55
 
 
56
    # Excellent! It worked. Let's turn ourself into a daemon,
 
57
    # then get on with the job of being a python interpreter.
 
58
    if daemon_mode:
 
59
        if os.fork():   # launch child and...
 
60
            os._exit(0) # kill off parent
 
61
        os.setsid()
 
62
        if os.fork():   # launch child and...
 
63
            os._exit(0) # kill off parent again.
 
64
        os.umask(077)
 
65
 
 
66
        try:
 
67
            MAXFD = os.sysconf("SC_OPEN_MAX")
 
68
        except:
 
69
            MAXFD = 256
 
70
 
 
71
        # Close all file descriptors, except the socket.
 
72
        for i in xrange(MAXFD):
 
73
            if i == s.fileno():
 
74
                continue
 
75
            try:
 
76
                os.close(i)
 
77
            except OSError:
 
78
                pass
 
79
 
 
80
        si = os.open(os.devnull, os.O_RDONLY)
 
81
        os.dup2(si, sys.stdin.fileno())
 
82
 
 
83
        so = os.open(os.devnull, os.O_WRONLY)
 
84
        os.dup2(so, sys.stdout.fileno())
 
85
 
 
86
        se = os.open(os.devnull, os.O_WRONLY)
 
87
        os.dup2(se, sys.stderr.fileno())
 
88
 
 
89
    if initializer:
 
90
        initializer()
 
91
 
 
92
    while True:
 
93
        (conn, addr) = s.accept()
 
94
        conn.settimeout(SOCKETTIMEOUT)
 
95
        try:
 
96
            # Grab the input and try to decode
 
97
            inp = recv_netstring(conn)
 
98
            try:
 
99
                content = decode(inp, magic)
 
100
            except ProtocolError:
 
101
                conn.close()
 
102
                continue
 
103
 
 
104
            response = handler(content)
 
105
 
 
106
            send_netstring(conn, json.dumps(response))
 
107
 
 
108
            conn.close()
 
109
 
 
110
        except Terminate, t:
 
111
            # Try and send final response and then terminate
 
112
            if t.final_response:
 
113
                send_netstring(conn, json.dumps(t.final_response))
 
114
            conn.close()
 
115
            sys.exit(0)
 
116
        except Exception:
 
117
            # Make a JSON object full of exceptional goodness
 
118
            tb_dump = cStringIO.StringIO()
 
119
            e_type, e_val, e_tb = sys.exc_info()
 
120
            traceback.print_tb(e_tb, file=tb_dump)
 
121
            json_exc = {
 
122
                "type": e_type.__name__,
 
123
                "value": str(e_val),
 
124
                "traceback": tb_dump.getvalue()
 
125
            }
 
126
            send_netstring(conn, json.dumps(json_exc))
 
127
            conn.close()
 
128
 
 
129
 
 
130
def chat(host, port, msg, magic, decode = True):
 
131
    sok = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
132
    sok.connect((host, port))
 
133
    sok.settimeout(SOCKETTIMEOUT)
 
134
 
 
135
    out = encode(msg, magic)
 
136
 
 
137
    send_netstring(sok, out)
 
138
    inp = recv_netstring(sok)
 
139
 
 
140
    sok.close()
 
141
 
 
142
    if decode:
 
143
        return json.loads(inp)
 
144
    else:
 
145
        return inp
 
146
 
 
147
def encode(message, magic):
 
148
    """Converts a message into a JSON serialisation and uses a magic
 
149
    string to attach a HMAC digest.
 
150
    """
 
151
    # XXX: Any reason that we double encode?
 
152
    content = json.dumps(message)
 
153
 
 
154
    digest = hashlib.md5(content + magic).hexdigest()
 
155
    env = {'digest':digest,'content':content}
 
156
    return json.dumps(env)
 
157
 
 
158
 
 
159
def decode(message, magic):
 
160
    """Takes a message with an attached HMAC digest and validates the message.
 
161
    """
 
162
    msg = json.loads(message)
 
163
 
 
164
    # Check that the message is valid
 
165
    digest = hashlib.md5(msg['content'] + magic).hexdigest()
 
166
    if msg['digest'] != digest:
 
167
        raise ProtocolError("HMAC digest is invalid")
 
168
    content = json.loads(msg['content'])
 
169
 
 
170
    return content
 
171
 
 
172
 
 
173
def send_netstring(sok, data):
 
174
    """ Sends a netstring to a socket
 
175
    """
 
176
    netstring = "%d:%s,"%(len(data),data)
 
177
    sok.sendall(netstring)
 
178
 
 
179
 
 
180
def recv_netstring(sok):
 
181
    """ Attempts to recieve a Netstring from a socket.
 
182
    Throws a ProtocolError if the received data violates the Netstring 
 
183
    protocol.
 
184
    """
 
185
    # Decode netstring
 
186
    size_buffer = []
 
187
    c = sok.recv(1)
 
188
    while c != ':':
 
189
        # Limit the Netstring to less than 10^10 bytes (~1GB).
 
190
        if len(size_buffer) >= 10:
 
191
            raise ProtocolError(
 
192
                    "Could not read Netstring size in first 9 bytes: '%s'"%(
 
193
                    ''.join(size_buffer)))
 
194
        size_buffer.append(c)
 
195
        c = sok.recv(1)
 
196
    try:
 
197
        # Message should be length plus ',' terminator
 
198
        recv_expected = int(''.join(size_buffer)) + 1
 
199
    except ValueError, e:
 
200
        raise ProtocolError("Could not decode Netstring size as int: '%s'"%(
 
201
                ''.join(size_buffer)))
 
202
 
 
203
    # Read data
 
204
    buf = []
 
205
    recv_data = sok.recv(min(recv_expected, BLOCKSIZE))
 
206
    recv_size = len(recv_data)
 
207
    while recv_size < recv_expected:
 
208
        buf.append(recv_data)
 
209
        recv_data = sok.recv(min(recv_expected-recv_size, 1024))
 
210
        recv_size = recv_size + len(recv_data)
 
211
    assert(recv_size == recv_expected)
 
212
 
 
213
    # Did we receive the correct amount?
 
214
    if recv_data[-1] != ',':
 
215
        raise ProtocolError("Netstring did not end with ','")
 
216
    buf.append(recv_data[:-1])
 
217
 
 
218
    return ''.join(buf)