Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gteissier
GitHub Repository: gteissier/erl-matter
Path: blob/master/erldp.py
271 views
1
#!/usr/bin/env python3
2
3
from struct import pack, unpack
4
import asyncio
5
from hashlib import md5
6
from random import choice
7
from string import ascii_uppercase
8
9
10
# TCP framing: 2-bytes length followed by the message itself
11
# The length field is the length of the message, not including length field
12
async def send_frame(w, msg):
13
assert(len(msg) < 2**16)
14
data = pack('!H', len(msg)) + msg
15
w.write(data)
16
await w.drain()
17
18
async def recv_deframe(r):
19
data = await r.readexactly(2)
20
assert(len(data) == 2)
21
(total_length,) = unpack('!H', data)
22
data = await r.readexactly(total_length)
23
return data
24
25
26
27
# Erlang distribution primitive messages
28
async def send_name_v5(w, name, flags_low=0x7499c, creation=0xdeadbeef):
29
assert(len(name) < 2**16)
30
msg = pack('!cIH', b'n', flags_low, len(name)) + name
31
await send_frame(w, msg)
32
33
async def send_name_v6(w, name, flags=0x1074f9c, creation=0xdeadbeef):
34
assert(len(name) < 2**16)
35
msg = pack('!cQIH', b'N', flags, creation, len(name)) + name
36
await send_frame(w, msg)
37
38
async def recv_status(r):
39
msg = await recv_deframe(r)
40
assert(msg[0] == ord('s'))
41
return msg[1:]
42
43
async def recv_challenge_v6(r):
44
msg = await recv_deframe(r)
45
assert(msg[0] == ord('N'))
46
47
(flags, challenge, creation, nlen) = unpack('!QIIH', msg[1:19])
48
name = msg[19:]
49
assert(nlen == len(name))
50
return (flags, challenge, creation, name)
51
52
async def recv_challenge_v5(r):
53
# (length, tag, version, flags, challenge) = unpack('!HcHII', data[:13])
54
msg = await recv_deframe(r)
55
assert(msg[0] == ord('n'))
56
57
(version, flags, challenge) = unpack('!HII', msg[1:11])
58
return (version, flags, challenge)
59
60
async def send_complement(w, flags_high, creation):
61
msg = pack('!cII', b'c', flags_high, creation)
62
await send_frame(w, msg)
63
64
async def send_challenge_reply(w, digest, challenge=0):
65
msg = pack('!cI', b'r', challenge) + digest
66
await send_frame(w, msg)
67
68
async def recv_challenge_ack(r):
69
msg = await recv_deframe(r)
70
assert(msg[0] == ord('a'))
71
assert(len(msg[1:]) == 16)
72
73
74
75
def compute_digest(cookie, challenge):
76
challenge = '%u' % challenge
77
challenge = challenge.encode()
78
79
m = md5()
80
m.update(cookie)
81
m.update(challenge)
82
return m.digest()
83
84
85
def rand_id(n=6):
86
return ''.join([choice(ascii_uppercase) for c in range(n)]) + '@nowhere'
87
88
89
async def authenticate(host, port, cookie):
90
r, w = await asyncio.open_connection(host, port)
91
92
await send_name_v6(w, rand_id().encode())
93
94
reason = await recv_status(r)
95
assert(reason == b'ok')
96
97
(flags, challenge, creation, name) = await recv_challenge_v6(r)
98
99
digest = compute_digest(cookie, challenge)
100
101
await send_challenge_reply(w, digest)
102
103
try:
104
await recv_challenge_ack(r)
105
except:
106
return
107
108
return (r, w)
109
110
111
112
if __name__ == '__main__':
113
import argparse
114
parser = argparse.ArgumentParser()
115
parser.add_argument('--host', default='127.0.0.1')
116
parser.add_argument('port', type=int)
117
parser.add_argument('cookie')
118
args = parser.parse_args()
119
120
asyncio.run(authenticate(args.host, args.port, args.cookie.encode()))
121
122
123