webXMPP

Check-in [c0c170c411]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Continue workup for UDP based notification handling. To make testing easier, support directly configured accounts in place of an account server.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | master | trunk
Files: files | file ages | folders
SHA3-256:c0c170c411c067805639ad458fc555367a0a0a3b8771c79999936339c97f6ead
User & Date: vandyswa@gmail.com 2017-06-25 15:55:05
Context
2017-06-26
23:34
Bringup check-in: 40e22b5fdb user: vandyswa@gmail.com tags: master, trunk
2017-06-25
15:55
Continue workup for UDP based notification handling. To make testing easier, support directly configured accounts in place of an account server. check-in: c0c170c411 user: vandyswa@gmail.com tags: master, trunk
2017-06-23
15:42
Split out packet part from general notification dispatch check-in: d06467d45f user: vandyswa@gmail.com tags: master, trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to etc/config.

31
32
33
34
35
36
37





38
39
40
41
42
43
44


45
46
47
48
49
50
51
52
53



54
55
56

    # SSL config
    publicCert certs/server.crt
    privateKey certs/server.key
serve http
    # TBD, permit only SMS services into this interface
    port 8092






# # messages to display at client
nmsg 15


# Username into HTTP server, with password
user test1 password1


     # Account on an XMPP server, with password
    account xmpp test99@xmpp.testing.org PASSWORD123

    # Our phone # for SMS services
    # sms-key/sms-password is used when *we* send, basic
    #  HTTP authen
    account sms 1234567890 sms-key sms-password
	sms Person1 123-456-7890
	sms Person2 999-111-1234




# Filter acceptable source IP for SMS delivery
sms 52.88.246.140 52.10.220.50







>
>
>
>
>






|
>
>









>
>
>



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

    # SSL config
    publicCert certs/server.crt
    privateKey certs/server.key
serve http
    # TBD, permit only SMS services into this interface
    port 8092

# UDP Push, usually same port # as HTTP SSL (but HTTP is TCP,
#  this is UDP)
serve udp
    port 8091

# # messages to display at client
nmsg 15


# Username into HTTP server, with password
user test1
    password password1

     # Account on an XMPP server, with password
    account xmpp test99@xmpp.testing.org PASSWORD123

    # Our phone # for SMS services
    # sms-key/sms-password is used when *we* send, basic
    #  HTTP authen
    account sms 1234567890 sms-key sms-password
	sms Person1 123-456-7890
	sms Person2 999-111-1234

    # Enable UDP push for this user
    account udp push_password1

# Filter acceptable source IP for SMS delivery
sms 52.88.246.140 52.10.220.50

Added local.py.

























>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
#
# local.py
#	Support routines when not running under an account server
#
import pdb

class Local_Server_mixin(object):
    def auth_local(self):
	pdb.set_trace()
	res = self.auth_cookie()
	if res is True:
	    return True

Changes to main.py.

4
5
6
7
8
9
10

11
12

13
14
15
16
17
18
19
..
79
80
81
82
83
84
85






86
87
88
89
90
91
92
93
94
95
96
97
98
99
...
134
135
136
137
138
139
140





141
142
143

144
145
146
147
148
149
150
...
180
181
182
183
184
185
186






187
188
189
190
191
192
193
194
195
196
197
198
199
#
import sys, threading, time
from user import User
from get import GET_mixin
from post import POST_mixin
from put import PUT_mixin
from fcm import FCM_mixin

from chore.authen import Authen_mixin, Authen_Server_mixin
import chore


# Seconds between search for timeouts
TIME_GRANULARITY = 5

# Web interface handling
class App_Handler(chore.handlers.Chore_Handler, GET_mixin,
	POST_mixin, PUT_mixin, Authen_mixin):
................................................................................
    c.args.add( ("user", "account") )
    c.mults.add( ("user", "account") )

    # Sub-sub-config of an SMS account, known numbers
    c.args.add( ("user", "account", "sms") )
    c.mults.add( ("user", "account", "sms") )







    # Now parse our input with this kind of config
    res = c.load_cfg(fn)
    return res

# Root of all Web/XMPP server state
class WebXMPP(chore.server.Server, FCM_mixin,
	Authen_Server_mixin):

    def __init__(self, cfg):

	Authen_Server_mixin.__init__(self)

	# Sanity--poll times
	if not cfg.get("poll1"):
................................................................................

	# SMS source filters, if any
	self.smsok = []
	for tup in cfg.get("sms", ()):
	    for a in tup:
		self.smsok.append(chore.net.parseaddr(a))






	# Account server to get logged in & get cookie
	self.authentication.append(Authen_mixin.auth_server)
	self.init_acct_server()

	sys.stderr.write("App init done\n")

    # Time based service
    def run_timer(self):
	while True:
	    time.sleep(TIME_GRANULARITY)
	    tm = time.time()
................................................................................
		return False

	# Mint a new, active user
	u = User(self, uname, cfg["account"])
	self.users[user] = u
	u.start()
	return True







if __name__ == "__main__":
    if len(sys.argv) != 2:
        sys.stderr.write("Usage is: %s <config-file>\n" %
            (sys.argv[0],))
        sys.exit(1)

    # Create the server with config
    app = WebXMPP(load_cfg(sys.argv[1]))
    app.start_http()

    # Main thread serves timers
    app.run_timer()







>


>







 







>
>
>
>
>
>






|







 







>
>
>
>
>
|
|
|
>







 







>
>
>
>
>
>













4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
...
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
...
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#
import sys, threading, time
from user import User
from get import GET_mixin
from post import POST_mixin
from put import PUT_mixin
from fcm import FCM_mixin
from local import Local_Server_mixin
from chore.authen import Authen_mixin, Authen_Server_mixin
import chore
import udp

# Seconds between search for timeouts
TIME_GRANULARITY = 5

# Web interface handling
class App_Handler(chore.handlers.Chore_Handler, GET_mixin,
	POST_mixin, PUT_mixin, Authen_mixin):
................................................................................
    c.args.add( ("user", "account") )
    c.mults.add( ("user", "account") )

    # Sub-sub-config of an SMS account, known numbers
    c.args.add( ("user", "account", "sms") )
    c.mults.add( ("user", "account", "sms") )

    # UDP push, shared secret
    c.onearg.add( ("user", "account", "udp") )

    # If not using an account server
    c.onearg.add( ("user", "password") )

    # Now parse our input with this kind of config
    res = c.load_cfg(fn)
    return res

# Root of all Web/XMPP server state
class WebXMPP(chore.server.Server, FCM_mixin,
	Authen_Server_mixin, Local_Server_mixin):

    def __init__(self, cfg):

	Authen_Server_mixin.__init__(self)

	# Sanity--poll times
	if not cfg.get("poll1"):
................................................................................

	# SMS source filters, if any
	self.smsok = []
	for tup in cfg.get("sms", ()):
	    for a in tup:
		self.smsok.append(chore.net.parseaddr(a))

	# Static/local authentication config?  Can't mix with
	#  an authentication server.
	if any(("password" in acct[1]) for acct in cfg["user"]):
	    self.authentication.append(WebXMPP.auth_local)
	else:
	    # Account server to get logged in & get cookie
	    self.authentication.append(Authen_mixin.auth_server)
	    self.init_acct_server()

	sys.stderr.write("App init done\n")

    # Time based service
    def run_timer(self):
	while True:
	    time.sleep(TIME_GRANULARITY)
	    tm = time.time()
................................................................................
		return False

	# Mint a new, active user
	u = User(self, uname, cfg["account"])
	self.users[user] = u
	u.start()
	return True

    # In addition to Chore's service for HTTP(s), we also
    #  can have a UDP protocol module
    def serve_proto(self, proto, cfg):
	server = udp.UDP(cfg, self)
	return server

if __name__ == "__main__":
    if len(sys.argv) != 2:
        sys.stderr.write("Usage is: %s <config-file>\n" %
            (sys.argv[0],))
        sys.exit(1)

    # Create the server with config
    app = WebXMPP(load_cfg(sys.argv[1]))
    app.start_http()

    # Main thread serves timers
    app.run_timer()

Changes to notified.py.

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#  Level 2 is the sender.  Level 3 is the subject line,
#  level 4 is a message body.  In the above example the
#  client requested the first two items, thus seeing
#  that it's an XMPP message from Joe.  At detail 0, you
#  would only see that there were new events, but no other
#  detail.
#
import socket, sys, json, time, os
import notify2, hashlib
from Crypto.Cipher import AES
import pong

# For DBus sniffing
import dbus, dbus.exceptions, dbus.mainloop.glib
import threading
from gi.repository import GLib








|
|
<







38
39
40
41
42
43
44
45
46

47
48
49
50
51
52
53
#  Level 2 is the sender.  Level 3 is the subject line,
#  level 4 is a message body.  In the above example the
#  client requested the first two items, thus seeing
#  that it's an XMPP message from Joe.  At detail 0, you
#  would only see that there were new events, but no other
#  detail.
#
import sys, json, time, os
import notify2

import pong

# For DBus sniffing
import dbus, dbus.exceptions, dbus.mainloop.glib
import threading
from gi.repository import GLib

Changes to pong.py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
..
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88






89
90
91
92
93
94
95



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124

125
126
127
128
129
130
131
132
133
134



135
136
137
138
139

140
141
142




143
144







145
146


147
148
149


150
151
152
153
154
155
156
157
158
159








































































































































160
161
162
163
164
165
166
...
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241

242
243
244
245
246
247
248
249
#
# pong.py
#	UDP notifications infrastructure
# vim: expandtab
#
# Ugh, Ubuntu Phone seems to have pretty much gone to Python 3, so
#  here we are.
#
# The protocol uses a JSON-encoded string carried over UDP.
# The message structure is documented below, but at a high
#  level the protocol advances in three stages.  First,
#  the client sends a request to the notification server.
#  Among other items, it indicates the last event serial
#  seen.
................................................................................
#  replay.
#
# Each packet thus has a nonce (generated by the system's crypto-
#  quality random number generator) and a SHA-256 derived
#  signature reflecting the shared secret.
#
import socket, sys, json, time, os
import hashlib
from Crypto.Cipher import AES

# How long to wait for initial/immediate response
# (This is the starting timeout, which backs off with each
#  retry.)
WAITRESP = 1

................................................................................
# XOR a byte range into one half its size
def fold(buf):
    assert (len(buf) & 1) == 0
    l = len(buf)/2
    return bytes((c1 ^ c2)
        for c1,c2 in zip(buf[:l], buf[l:]))

# Display bytes in hex
def tohex(buf):
    return ''.join(("%02x" % (c,)) for c in buf)
def fromhex(buf):
    res = bytearray()
    while buf:
        res.append(buf[:2].decode("hex"))
        buf = buf[2:]
    return res







# Views of the packet being sent
class Packet(object):
    def __init__(self, inner, outer, buf):
        self.inner = inner
        self.outer = outer
        self.buf = buf




# State of talking to a server
class PONG(object):
    def __init__(self, server, port, uname, password):
        self.server = server
        self.port = port
        self.uname = uname
        self.password = password
        self.pakseq = 0

        # Used as crypto key
        sh = hashlib.sha256()
        sh.update(cfg.password.encode("utf8"))
        self.hashedpw = sh.digest()

        # Defend against replays
        self.used_nonces = set()

    # SHA256 adapted signature
    def calchash(self, buf, nonce):
        sh = hashlib.sha256()
        sh.update(buf)
        sh.update(nonce)
        sh.update(self.password)
        return fold(fold(sh.digest()))

    # Encode and sign this message
    #
    # We're using 64 bits of nonce, and 64 bits of folded SHA256

    def wrap_msg(self, d, crypt=False):

        # String rep of actual operation
        inner = json.dumps(d)

        # 64 bits of nonce
        nonce = os.urandom(8)

        # And collapse 256 bits of sha256 to 64 bits too
        # (We hash the content, nonce, and shared secret.)



        sig = self.calchash(inner, nonce)

        # Wrap inner into outer
        outer = {"user": self.uname,
            "sig": tohex(sig),

            "nonce": tohex(nonce)}

        # Encrypt?




        if crypt:
            # Pad to 64 byte boundary







            li = len(inner)
            if li % AES.block_size:


                inner += (" " * (AES.block_size - (li % 64)))
            a = AES.new(self.hashedpw, AES.MODE_CFB, nonce)



            inner = a.encrypt(inner)
            outer["crypt"] = "AES"

        # Signed and possibly encrypted inner content
        outer["inner"] = inner

        # As a JSON string
        buf = json.dumps(outer)
        assert len(buf) < MAXMSG
        return Packet(d, outer, buf)









































































































































    # Receive a message, with timeout
    # The message is verified for correct sig
    def recv_msg(self, sock, pakseq, timeout):
        targtm = time.time() + timeout
        while True:
            # No more time
................................................................................
            except:
                # Timeout
                log(" timeout")
                return None
            log("pak %s from %s\n" % (buf, who))

            # Valid?
            try:
                # Decode
                outer = json.loads(buf)
                inner = outer["inner"]
                if inner["pakseq"] != pakseq:
                    # Answers to this op?
                    log(" mismatch pakseq")
                    continue
                nonce = outer["nonce"]

                # Protect nonce value
                if nonce in self.used_nonces:
                    log(" dup nonce")
                    continue

                # Crypto?
                if "crypt" in outer:
                    if outer["crypt"] != "AES":
                        log(" unknown crypto")
                        continue
                    a = AES.new(hashedpw, AES.MODE_CFB, fromhex(nonce))
                    inner = a.decrypt(inner)

                # Verify sig
                sig = calchash(inner, outer["nonce"])
                if sig != outer["sig"]:
                    log(" bad sig")
                    continue

                # Convert inner
                inner = json.loads(inner)

            except:
                log(" malformed")
                continue

            # Don't use this nonce again
            self.used_nonces.add(nonce)

            # Here's something signed by our peer
            log(" good pak")
            return Packet(inner, outer, buf)

    # Build message, signed, all that stuff
    # Return JSON encoding of it.
    def msg(self, op, subop, vals=None):
        # Start with any params they specified
        if vals is not None:
            req = dict(vals)
        else:
            req = {}

        # Plug in some standard fields
        req["op"] = op
        req["subop"] = subop
        req["pakseq"] = self.pakseq
        self.pakseq += 1

        # Sign and maybe encrypt

        return self.wrap_msg(req)

    # Ping-Pong message exchange.  We said something to them,
    #  and we expect to hear something back.
    # JSON in each direction.
    def pingpong(self, pak):

        # A new socket each time.  This is an easy way to make






|







 







|







 







|








>
>
>
>
>
>







>
>
>

<

|
<
<
<
<
<
<
<
<
<
<
<




|
|


|





>
|









>
>
>
|

<
<
<
>
|


>
>
>
>
|
<
>
>
>
>
>
>
>

|
>
>
|
<

>
>
|
<








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
>
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
..
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

106
107











108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137



138
139
140
141
142
143
144
145
146

147
148
149
150
151
152
153
154
155
156
157
158

159
160
161
162

163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
...
323
324
325
326
327
328
329
330














































331











332
333
334
335
336
337
338
339
340
#
# pong.py
#	UDP notifications infrastructure
# vim: expandtab
#
# Ugh, Ubuntu Phone seems to have pretty much gone to Python 3, so
#  here we are.  This file is written to load under Python 2 or 3.
#
# The protocol uses a JSON-encoded string carried over UDP.
# The message structure is documented below, but at a high
#  level the protocol advances in three stages.  First,
#  the client sends a request to the notification server.
#  Among other items, it indicates the last event serial
#  seen.
................................................................................
#  replay.
#
# Each packet thus has a nonce (generated by the system's crypto-
#  quality random number generator) and a SHA-256 derived
#  signature reflecting the shared secret.
#
import socket, sys, json, time, os
from hashlib import sha256
from Crypto.Cipher import AES

# How long to wait for initial/immediate response
# (This is the starting timeout, which backs off with each
#  retry.)
WAITRESP = 1

................................................................................
# XOR a byte range into one half its size
def fold(buf):
    assert (len(buf) & 1) == 0
    l = len(buf)/2
    return bytes((c1 ^ c2)
        for c1,c2 in zip(buf[:l], buf[l:]))

# Display bytes in hex and back
def tohex(buf):
    return ''.join(("%02x" % (c,)) for c in buf)
def fromhex(buf):
    res = bytearray()
    while buf:
        res.append(buf[:2].decode("hex"))
        buf = buf[2:]
    return res

# SHA256 rep of password string
def pwhash(pw):
    sh = sha256()
    sh.update(cfg.password.encode("utf8"))
    return tohex(sh.digest())

# Views of the packet being sent
class Packet(object):
    def __init__(self, inner, outer, buf):
        self.inner = inner
        self.outer = outer
        self.buf = buf
        # When appropriate, this is the (host,port) tuple
        #  of a recvfrom()
        self.who = None


class PONG(object):
    def __init__(self):











        # Defend against replays
        self.used_nonces = set()

    # SHA256 adapted signature
    def calchash(self, buf, passwd, nonce):
        sh = sha256()
        sh.update(buf)
        sh.update(nonce)
        sh.update(passwd)
        return fold(fold(sh.digest()))

    # Encode and sign this message
    #
    # We're using 64 bits of nonce, and 64 bits of folded SHA256
    # @d may have its contents modified.
    def wrap_msg(self, uname, d):

        # String rep of actual operation
        inner = json.dumps(d)

        # 64 bits of nonce
        nonce = os.urandom(8)

        # And collapse 256 bits of sha256 to 64 bits too
        # (We hash the content, nonce, and shared secret.)
        passwd = self.get_password(uname)
        if passwd is None:
            return None
        sig = self.calchash(inner, passwd, nonce)




        # Outer signature of inner
        outer = {"sig": tohex(sig), "nonce": tohex(nonce)}

        # Encrypt?
        if "crypto" in d:
            # Move the flag to the outer JSON
            assert d["crypto"] == "AES"
            outer["crypto"] = d["crypto"]
            del d["crypto"]


            # Ask our instance for the hashed version of the
            #  PSK with this uname
            hashedpw = self.get_hashed_password(uname)

            # Pad to block boundary with spaces (JSON's OK with
            #  white space)
            li = len(inner)
            bs = AES.block_size
            resid = li % bs
            if resid:
                inner += (" " * (bs - resid))


            # Encrypt the inner
            a = AES.new(hashedpw, AES.MODE_CFB, nonce)
            inner = hex(a.encrypt(inner))


        # Signed and possibly encrypted inner content
        outer["inner"] = inner

        # As a JSON string
        buf = json.dumps(outer)
        assert len(buf) < MAXMSG
        return Packet(d, outer, buf)

    # Decode JSON of message, return Packet or None
    def unwrap_msg(self, buf):
        try:
            # Decode
            outer = json.loads(buf)
            inner = outer["inner"]
            if inner["pakseq"] != pakseq:
                # Answers to this op?
                log(" mismatch pakseq")
                return None
            nonce = fromhex(outer["nonce"])

            # Protect nonce value
            if nonce in self.used_nonces:
                log(" dup nonce")
                return None

            # Crypto?
            uname = outer["user"]
            passwd = self.get_password(uname)
            if passwd is None:
                log("No password for " + uname)
                return None
            if "crypto" in outer:
                if outer["crypto"] != "AES":
                    log(" unknown crypto " + outer["crypto"])
                    return None
                hashedpw = self.get_hashed_password(uname)
                a = AES.new(hashedpw, AES.MODE_CFB, fromhex(nonce))
                inner = a.decrypt(fromhex(inner))

            # Verify sig
            sig = self.calchash(inner, passwd, outer["nonce"])
            if sig != outer["sig"]:
                log(" bad sig")
                return None

            # Convert inner
            inner = json.loads(inner)

        except:
            log(" malformed")
            return None

        # Don't use this nonce again
        self.used_nonces.add(nonce)

        # Here's something signed by our peer
        log(" good pak")
        return Packet(inner, outer, buf)

class Server(PONG):
    def __init__(self, port):
        self.sock = sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        sock.bind(('', port))

    # Build response message to request in @inpak
    # Like PONG.Client.ping(), this routine also modifies the
    #  dict @resp if it's provided.
    def pong(self, inpak, subop, resp=None):
        if resp is None:
            resp = {}
        inner = inpak.inner
        resp["op"] = inner["op"]
        resp["subop"] = subop
        resp["pakseq"] = inner["pakseq"]

        # Sign/encrypt this response
        return self.wrap_msg(inner["user"], resp)

    # Wait for the next (legit) message
    def next_msg(self):
        while True:
            buf,who = self.sock.recvfrom(MAXMSG)
            log("Msg rx %s %s: %s" % (who, time.asctime(), buf))
            pak = self.decode(buf)
            if pak is not None:
                pak.who = who
                return pak

    # Subclass hooks to look up user account
    def get_password(self, uname):
        assert False, "Not Implemented"
        return None
    def get_hashed_password(self, uname):
        assert False, "Not Implemented"
        return None

    # Craft reply to this request
    def reply(self, inpak, outpak):
        try:
            self.sock.sendto(outpak.buf, inpak.who)
        except:
            log(" reply failed")

class Client(PONG):
    def __init__(self, server, port, uname, password):
        self.server = server
        self.port = port
        self.uname = uname
        self.password = password
        self.pakseq = 0

        # Used as crypto key (calculated on first use)
        self.hashedpw = None

    # Password access
    def get_password(self, uname):
        assert uname == self.uname
        return self.password
    def get_hashed_password(self, uname):
        assert uname == self.uname
        if self.hashedpw is None:
            self.hashedpw = pwhash(self.password)
        return self.hashedpw

    # Build request, signed, all that stuff
    # Return JSON encoding of it.
    # Note this routine scribbles on the dict @req if it is
    #  passed in.
    def ping(self, op, subop, req=None):
        # Start with any params they specified
        if req is None:
            req = {}

        # Plug in some standard fields
        assert hasattr(self, "uname"), "TBD: server->client request?"
        req["user"] = self.uname
        req["op"] = op
        req["subop"] = subop
        req["pakseq"] = self.pakseq
        self.pakseq += 1

        # Sign and maybe encrypt
        return self.wrap_msg(self.uname, req)

    # Receive a message, with timeout
    # The message is verified for correct sig
    def recv_msg(self, sock, pakseq, timeout):
        targtm = time.time() + timeout
        while True:
            # No more time
................................................................................
            except:
                # Timeout
                log(" timeout")
                return None
            log("pak %s from %s\n" % (buf, who))

            # Valid?
            pak = self.unwrap_msg(buf)














































            if pak is not None:











                pak.who = who
                return pak

    # Ping-Pong message exchange.  We said something to them,
    #  and we expect to hear something back.
    # JSON in each direction.
    def pingpong(self, pak):

        # A new socket each time.  This is an easy way to make

Added udp.py.

















































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#
# udp.py
#	Handle UDP based push notifications
#
# (This is the server side, running in the cloud.)
#
import sys
from chore import pong
import pdb

# During startup, get configured long polling interval
#  for timeouts.
TIMEOUT = None

# Log some debug
def log(s):
    sys.stderr.write("%s\n" % (s,))

# Dict of known host names (key), their PSK (value)
# In parallel, key->hashed(password) for crypto
touched = None
accounts = {}
hashed = {}

# This is the PONG server, subclassed with our hooks for looking up
#  passwords
class Serve(pong.Server):
    def get_password(self, uname):
	global accounts
	return accounts.get(uname)

    # Lazy calculation of hashed password
    def get_hashed_password(self, uname):
	global accounts, hashed

	# Already calculated
	res = hashed.get(uname)
	if res is not None:
	    return res

	# Unknown user?
	pw = hashed.get(uname)
	if pw is None:
	    return None

	# Calculate hash
	hashedpw = pong.pwhash(pw)
	# Cache
	hashed[uname] = hashedpw
	# Return answer
	return hashedpw

class UDP(object):
    def __init__(self, cfg, approot):
	global TIMEOUT

	self.approot = approot
	TIMEOUT = approot.config["poll1"]
	port = self.port = cfg["port"]
        self.pong = Serve(port)

    # Operational parameters
    def handle_params(self, pak):
	inner = pak.inner
	subop = inner["subop"]

	# Tell them about the operation parameters
	if subop == "get":
	    resp = pong.msg("params", "got", {
		"timeout": TIMEOUT,
	    })
	    XXX
	    self.pong.reply(pak, resp)
	    return

	log(" param unknown subop: " + subop)

    # Dispatch the latest request
    def handle(self, pak):
	inner = pak.inner
	op = inner["op"]

	# Notifications
	if op == "notify":
	    self.handle_notify(pak)
	    return

	# Parameters
	if op == "params":
	    self.handle_params(pak)
	    return

	log(" unknown op: " + op)

    # This is the UDP port service loop.  It runs in its own
    #  thread, interacting with the rest of the server
    #  via self.approot
    def run(self):
	pdb.set_trace()
	while True:

	    # Next request
            pak = self.pong.next_msg()
            self.handle(pak)