~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
22
import cjson
23
import cStringIO
1197 by Matt Giuca
ivle.chat, ivle.database, ivle.makeuser: Replaced use of md5 library with
24
import hashlib
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
25
import sys
26
import os
27
import socket
28
import traceback
29
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
30
SOCKETTIMEOUT = 60
31
BLOCKSIZE = 1024
32
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
33
class Terminate(Exception):
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
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"""
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
42
class ProtocolError(Exception):
43
    """Exception thrown when client violates the the chat protocol"""
44
    pass
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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
1099.1.176 by William Grant
ivle.chat now closes all file descriptors when daemonising.
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
1099.1.178 by William Grant
ivle.chat now opens /dev/null as std{in,out,err} when daemonising.
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
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
85
    if initializer:
86
        initializer()
87
88
    while True:
89
        (conn, addr) = s.accept()
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
90
        conn.settimeout(SOCKETTIMEOUT)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
91
        try:
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
92
            # Grab the input and try to decode
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
93
            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
94
            try:
1707 by Matt Giuca
Fixed broken console, due to function called with not enough arguments.
95
                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
96
            except ProtocolError:
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
97
                conn.close()
98
                continue
99
100
            response = handler(content)
101
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
102
            send_netstring(conn, cjson.encode(response))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
103
104
            conn.close()
105
106
        except Terminate, t:
107
            # Try and send final response and then terminate
108
            if t.final_response:
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
109
                send_netstring(conn, cjson.encode(t.final_response))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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
            }
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
122
            send_netstring(conn, cjson.encode(json_exc))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
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))
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
129
    sok.settimeout(SOCKETTIMEOUT)
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
130
131
    json = encode(msg, magic)
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
132
133
    send_netstring(sok, json)
134
    inp = recv_netstring(sok)
135
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
136
    sok.close()
137
138
    if decode:
139
        return cjson.decode(inp)
140
    else:
141
        return inp
142
1697 by David Coles
chat: Factor out the encoding and decoding functions so we can test them and possibly reuse elsewhere
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
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
170
171
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
172
    """ Sends a netstring to a socket
173
    """
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
174
    netstring = "%d:%s,"%(len(data),data)
175
    sok.sendall(netstring)
176
177
178
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
179
    """ Attempts to recieve a Netstring from a socket.
180
    Throws a ProtocolError if the received data violates the Netstring 
181
    protocol.
182
    """
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
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:
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
189
            raise ProtocolError(
190
                    "Could not read Netstring size in first 9 bytes: '%s'"%(
191
                    ''.join(size_buffer)))
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
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:
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
198
        raise ProtocolError("Could not decode Netstring size as int: '%s'"%(
199
                ''.join(size_buffer)))
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
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] != ',':
1630 by David Coles
ivle.console should capture ivle.chat.ProtocolErrors as a ConsoleError
213
        raise ProtocolError("Netstring did not end with ','")
1621 by David Coles
Modify chat protocol to use Netstrings to track message boundaries rather than
214
    buf.append(recv_data[:-1])
215
216
    return ''.join(buf)