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

1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
22
try:
23
    import json
24
except ImportError:
25
    import simplejson as json
26
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
27
import cStringIO
1197 by Matt Giuca
ivle.chat, ivle.database, ivle.makeuser: Replaced use of md5 library with
28
import hashlib
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
29
import sys
30
import os
31
import socket
32
import traceback
33
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
34
SOCKETTIMEOUT = 60
35
BLOCKSIZE = 1024
36
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
37
class Terminate(Exception):
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
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"""
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
46
class ProtocolError(Exception):
47
    """Exception thrown when client violates the the chat protocol"""
48
    pass
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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
1099.1.176 by William Grant
ivle.chat now closes all file descriptors when daemonising.
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
1099.1.178 by William Grant
ivle.chat now opens /dev/null as std{in,out,err} when daemonising.
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
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
89
    if initializer:
90
        initializer()
91
92
    while True:
93
        (conn, addr) = s.accept()
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
94
        conn.settimeout(SOCKETTIMEOUT)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
95
        try:
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
96
            # Grab the input and try to decode
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
97
            inp = recv_netstring(conn)
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
98
            try:
1707 by Matt Giuca
Fixed broken console, due to function called with not enough arguments.
99
                content = decode(inp, magic)
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
100
            except ProtocolError:
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
101
                conn.close()
102
                continue
103
104
            response = handler(content)
105
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
106
            send_netstring(conn, json.dumps(response))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
107
108
            conn.close()
109
110
        except Terminate, t:
111
            # Try and send final response and then terminate
112
            if t.final_response:
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
113
                send_netstring(conn, json.dumps(t.final_response))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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
            }
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
126
            send_netstring(conn, json.dumps(json_exc))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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))
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
133
    sok.settimeout(SOCKETTIMEOUT)
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
134
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
135
    out = encode(msg, magic)
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
136
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
137
    send_netstring(sok, out)
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
138
    inp = recv_netstring(sok)
139
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
140
    sok.close()
141
142
    if decode:
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
143
        return json.loads(inp)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
144
    else:
145
        return inp
146
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
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?
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
152
    content = json.dumps(message)
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
153
154
    digest = hashlib.md5(content + magic).hexdigest()
155
    env = {'digest':digest,'content':content}
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
156
    return json.dumps(env)
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
157
158
159
def decode(message, magic):
160
    """Takes a message with an attached HMAC digest and validates the message.
161
    """
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
162
    msg = json.loads(message)
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
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")
1801.1.1 by William Grant
Replace cjson with json, or simplejson if json is not available (Python <2.6)
168
    content = json.loads(msg['content'])
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
169
170
    return content
171
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
172
173
def send_netstring(sok, data):
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
174
    """ Sends a netstring to a socket
175
    """
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
176
    netstring = "%d:%s,"%(len(data),data)
177
    sok.sendall(netstring)
178
179
180
def recv_netstring(sok):
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
181
    """ Attempts to recieve a Netstring from a socket.
182
    Throws a ProtocolError if the received data violates the Netstring 
183
    protocol.
184
    """
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
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:
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
191
            raise ProtocolError(
192
                    "Could not read Netstring size in first 9 bytes: '%s'"%(
193
                    ''.join(size_buffer)))
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
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:
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
200
        raise ProtocolError("Could not decode Netstring size as int: '%s'"%(
201
                ''.join(size_buffer)))
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
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] != ',':
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
215
        raise ProtocolError("Netstring did not end with ','")
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
216
    buf.append(recv_data[:-1])
217
218
    return ''.join(buf)