]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/introducer/common.py
introducer: stop tracking hints for subscribed clients
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / introducer / common.py
1
2 import re, simplejson
3 from allmydata.util import keyutil, base32, rrefutil
4
5 def make_index(ann, key_s):
6     """Return something that can be used as an index (e.g. a tuple of
7     strings), such that two messages that refer to the same 'thing' will have
8     the same index. This is a tuple of (service-name, signing-key, None) for
9     signed announcements, or (service-name, None, tubid_s) for unsigned
10     announcements."""
11
12     service_name = str(ann["service-name"])
13     if key_s:
14         return (service_name, key_s, None)
15     else:
16         tubid_s = get_tubid_string_from_ann(ann)
17         return (service_name, None, tubid_s)
18
19 def get_tubid_string_from_ann(ann):
20     return get_tubid_string(str(ann.get("anonymous-storage-FURL")
21                                 or ann.get("FURL")))
22
23 def get_tubid_string(furl):
24     m = re.match(r'pb://(\w+)@', furl)
25     assert m
26     return m.group(1).lower()
27
28 def convert_announcement_v1_to_v2(ann_t):
29     (furl, service_name, ri_name, nickname, ver, oldest) = ann_t
30     assert type(furl) is str
31     assert type(service_name) is str
32     # ignore ri_name
33     assert type(nickname) is str
34     assert type(ver) is str
35     assert type(oldest) is str
36     ann = {"version": 0,
37            "nickname": nickname.decode("utf-8", "replace"),
38            "app-versions": {},
39            "my-version": ver,
40            "oldest-supported": oldest,
41
42            "service-name": service_name,
43            "anonymous-storage-FURL": furl,
44            "permutation-seed-base32": get_tubid_string(furl),
45            }
46     msg = simplejson.dumps(ann).encode("utf-8")
47     return (msg, None, None)
48
49 def convert_announcement_v2_to_v1(ann_v2):
50     (msg, sig, pubkey) = ann_v2
51     ann = simplejson.loads(msg)
52     assert ann["version"] == 0
53     ann_t = (str(ann["anonymous-storage-FURL"]),
54              str(ann["service-name"]),
55              "remoteinterface-name is unused",
56              ann["nickname"].encode("utf-8"),
57              str(ann["my-version"]),
58              str(ann["oldest-supported"]),
59              )
60     return ann_t
61
62
63 def sign_to_foolscap(ann, sk):
64     # return (bytes, None, None) or (bytes, sig-str, pubkey-str). A future
65     # HTTP-based serialization will use JSON({msg:b64(JSON(msg).utf8),
66     # sig:v0-b64(sig), pubkey:v0-b64(pubkey)}) .
67     msg = simplejson.dumps(ann).encode("utf-8")
68     if sk:
69         sig = "v0-"+base32.b2a(sk.sign(msg))
70         vk_bytes = sk.get_verifying_key_bytes()
71         ann_t = (msg, sig, "v0-"+base32.b2a(vk_bytes))
72     else:
73         ann_t = (msg, None, None)
74     return ann_t
75
76 class UnknownKeyError(Exception):
77     pass
78
79 def unsign_from_foolscap(ann_t):
80     (msg, sig_vs, claimed_key_vs) = ann_t
81     key_vs = None
82     if sig_vs and claimed_key_vs:
83         if not sig_vs.startswith("v0-"):
84             raise UnknownKeyError("only v0- signatures recognized")
85         if not claimed_key_vs.startswith("v0-"):
86             raise UnknownKeyError("only v0- keys recognized")
87         claimed_key = keyutil.parse_pubkey("pub-"+claimed_key_vs)
88         sig_bytes = base32.a2b(keyutil.remove_prefix(sig_vs, "v0-"))
89         claimed_key.verify(sig_bytes, msg)
90         key_vs = claimed_key_vs
91     ann = simplejson.loads(msg.decode("utf-8"))
92     return (ann, key_vs)
93
94 class SubscriberDescriptor:
95     """This describes a subscriber, for status display purposes. It contains
96     the following attributes:
97
98     .service_name: what they subscribed to (string)
99     .when: time when they subscribed (seconds since epoch)
100     .nickname: their self-provided nickname, or "?" (unicode)
101     .version: their self-provided version (string)
102     .app_versions: versions of each library they use (dict str->str)
103     .remote_address: the external address from which they connected (string)
104     .tubid: for subscribers connecting with Foolscap, their tubid (string)
105     """
106
107     def __init__(self, service_name, when,
108                  nickname, version, app_versions,
109                  remote_address, tubid):
110         self.service_name = service_name
111         self.when = when
112         self.nickname = nickname
113         self.version = version
114         self.app_versions = app_versions
115         self.remote_address = remote_address
116         self.tubid = tubid
117
118 class AnnouncementDescriptor:
119     """This describes an announcement, for status display purposes. It
120     contains the following attributes, which will be empty ("" for
121     strings) if the client did not provide them:
122
123      .when: time the announcement was first received (seconds since epoch)
124      .index: the announcements 'index', a tuple of (string-or-None).
125              The server remembers one announcement per index.
126      .canary: a Referenceable on the announcer, so the server can learn
127               when they disconnect (for the status display)
128      .announcement: raw dictionary of announcement data
129      .service_name: which service they are announcing (string)
130      .version: 'my-version' portion of announcement (string)
131      .nickname: their self-provided nickname, or "" (unicode)
132      .serverid: the server identifier. This is a pubkey (for V2 clients),
133                 or a tubid (for V1 clients).
134      .advertised_addresses: which hosts they listen on (list of strings)
135                             if the announcement included a key for
136                             'anonymous-storage-FURL', else an empty list.
137     """
138
139     def __init__(self, when, index, canary, ann_d):
140         self.when = when
141         self.index = index
142         self.canary = canary
143         self.announcement = ann_d
144         self.service_name = ann_d["service-name"]
145         self.version = ann_d.get("my-version", "")
146         self.nickname = ann_d.get("nickname", u"")
147         (service_name, key_s, tubid_s) = index
148         self.serverid = key_s or tubid_s
149         furl = ann_d.get("anonymous-storage-FURL")
150         if furl:
151             self.advertised_addresses = rrefutil.hosts_for_furl(furl)
152         else:
153             self.advertised_addresses = []