Line data Source code
1 : /* Copyright (c) 2001 Matej Pfajfar.
2 : * Copyright (c) 2001-2004, Roger Dingledine.
3 : * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
4 : * Copyright (c) 2007-2021, The Tor Project, Inc. */
5 : /* See LICENSE for licensing information */
6 :
7 : /**
8 : * \file versions.c
9 : * \brief Code to manipulate, parse, and compare Tor versions.
10 : */
11 : #include "core/or/or.h"
12 :
13 : #include "core/or/protover.h"
14 : #include "core/or/versions.h"
15 : #include "lib/crypt_ops/crypto_util.h"
16 :
17 : #include "core/or/tor_version_st.h"
18 :
19 : /**
20 : * Return the approximate date when this release came out, or was
21 : * scheduled to come out, according to the APPROX_RELEASE_DATE set in
22 : * configure.ac
23 : **/
24 : time_t
25 5 : tor_get_approx_release_date(void)
26 : {
27 5 : char tbuf[ISO_TIME_LEN+1];
28 5 : tor_snprintf(tbuf, sizeof(tbuf),
29 : "%s 00:00:00", APPROX_RELEASE_DATE);
30 5 : time_t result = 0;
31 5 : int r = parse_iso_time(tbuf, &result);
32 5 : if (BUG(r < 0)) {
33 0 : result = 0;
34 : }
35 5 : return result;
36 : }
37 :
38 : /** Return VS_RECOMMENDED if <b>myversion</b> is contained in
39 : * <b>versionlist</b>. Else, return VS_EMPTY if versionlist has no
40 : * entries. Else, return VS_OLD if every member of
41 : * <b>versionlist</b> is newer than <b>myversion</b>. Else, return
42 : * VS_NEW_IN_SERIES if there is at least one member of <b>versionlist</b> in
43 : * the same series (major.minor.micro) as <b>myversion</b>, but no such member
44 : * is newer than <b>myversion.</b>. Else, return VS_NEW if every member of
45 : * <b>versionlist</b> is older than <b>myversion</b>. Else, return
46 : * VS_UNRECOMMENDED.
47 : *
48 : * (versionlist is a comma-separated list of version strings,
49 : * optionally prefixed with "Tor". Versions that can't be parsed are
50 : * ignored.)
51 : */
52 : version_status_t
53 17 : tor_version_is_obsolete(const char *myversion, const char *versionlist)
54 : {
55 17 : tor_version_t mine, other;
56 17 : int found_newer = 0, found_older = 0, found_newer_in_series = 0,
57 17 : found_any_in_series = 0, r, same;
58 17 : version_status_t ret = VS_UNRECOMMENDED;
59 17 : smartlist_t *version_sl;
60 :
61 17 : log_debug(LD_CONFIG,"Checking whether version '%s' is in '%s'",
62 : myversion, versionlist);
63 :
64 17 : if (tor_version_parse(myversion, &mine)) {
65 0 : log_err(LD_BUG,"I couldn't parse my own version (%s)", myversion);
66 0 : tor_assert(0);
67 : }
68 17 : version_sl = smartlist_new();
69 17 : smartlist_split_string(version_sl, versionlist, ",", SPLIT_SKIP_SPACE, 0);
70 :
71 17 : if (!strlen(versionlist)) { /* no authorities cared or agreed */
72 0 : ret = VS_EMPTY;
73 0 : goto done;
74 : }
75 :
76 50 : SMARTLIST_FOREACH_BEGIN(version_sl, const char *, cp) {
77 35 : if (!strcmpstart(cp, "Tor "))
78 26 : cp += 4;
79 :
80 35 : if (tor_version_parse(cp, &other)) {
81 : /* Couldn't parse other; it can't be a match. */
82 : } else {
83 34 : same = tor_version_same_series(&mine, &other);
84 34 : if (same)
85 11 : found_any_in_series = 1;
86 34 : r = tor_version_compare(&mine, &other);
87 34 : if (r==0) {
88 2 : ret = VS_RECOMMENDED;
89 2 : goto done;
90 32 : } else if (r<0) {
91 20 : found_newer = 1;
92 20 : if (same)
93 5 : found_newer_in_series = 1;
94 : } else if (r>0) {
95 : found_older = 1;
96 : }
97 : }
98 33 : } SMARTLIST_FOREACH_END(cp);
99 :
100 : /* We didn't find the listed version. Is it new or old? */
101 15 : if (found_any_in_series && !found_newer_in_series && found_newer) {
102 : ret = VS_NEW_IN_SERIES;
103 12 : } else if (found_newer && !found_older) {
104 : ret = VS_OLD;
105 5 : } else if (found_older && !found_newer) {
106 : ret = VS_NEW;
107 : } else {
108 2 : ret = VS_UNRECOMMENDED;
109 : }
110 :
111 17 : done:
112 54 : SMARTLIST_FOREACH(version_sl, char *, version, tor_free(version));
113 17 : smartlist_free(version_sl);
114 17 : return ret;
115 : }
116 :
117 : /** Extract a Tor version from a <b>platform</b> line from a router
118 : * descriptor, and place the result in <b>router_version</b>.
119 : *
120 : * Return 1 on success, -1 on parsing failure, and 0 if the
121 : * platform line does not indicate some version of Tor.
122 : *
123 : * If <b>strict</b> is non-zero, finding any weird version components
124 : * (like negative numbers) counts as a parsing failure.
125 : */
126 : int
127 224 : tor_version_parse_platform(const char *platform,
128 : tor_version_t *router_version,
129 : int strict)
130 : {
131 224 : char tmp[128];
132 224 : char *s, *s2, *start;
133 :
134 224 : if (strcmpstart(platform,"Tor ")) /* nonstandard Tor; say 0. */
135 : return 0;
136 :
137 197 : start = (char *)eat_whitespace(platform+3);
138 197 : if (!*start) return -1;
139 197 : s = (char *)find_whitespace(start); /* also finds '\0', which is fine */
140 197 : s2 = (char*)eat_whitespace(s);
141 197 : if (!strcmpstart(s2, "(r") || !strcmpstart(s2, "(git-"))
142 12 : s = (char*)find_whitespace(s2);
143 :
144 197 : if ((size_t)(s-start+1) >= sizeof(tmp)) /* too big, no */
145 : return -1;
146 197 : strlcpy(tmp, start, s-start+1);
147 :
148 197 : if (tor_version_parse(tmp, router_version)<0) {
149 16 : log_info(LD_DIR,"Router version '%s' unparseable.",tmp);
150 16 : return -1;
151 : }
152 :
153 181 : if (strict) {
154 2 : if (router_version->major < 0 ||
155 2 : router_version->minor < 0 ||
156 2 : router_version->micro < 0 ||
157 2 : router_version->patchlevel < 0 ||
158 2 : router_version->svn_revision < 0) {
159 0 : return -1;
160 : }
161 : }
162 :
163 : return 1;
164 : }
165 :
166 : /** Parse the Tor version of the platform string <b>platform</b>,
167 : * and compare it to the version in <b>cutoff</b>. Return 1 if
168 : * the router is at least as new as the cutoff, else return 0.
169 : */
170 : int
171 220 : tor_version_as_new_as(const char *platform, const char *cutoff)
172 : {
173 220 : tor_version_t cutoff_version, router_version;
174 220 : int r;
175 220 : tor_assert(platform);
176 :
177 220 : if (tor_version_parse(cutoff, &cutoff_version)<0) {
178 4 : log_warn(LD_BUG,"cutoff version '%s' unparseable.",cutoff);
179 4 : return 0;
180 : }
181 :
182 216 : r = tor_version_parse_platform(platform, &router_version, 0);
183 216 : if (r == 0) {
184 : /* nonstandard Tor; be safe and say yes */
185 : return 1;
186 195 : } else if (r < 0) {
187 : /* unparseable version; be safe and say yes. */
188 : return 1;
189 : }
190 :
191 : /* Here's why we don't need to do any special handling for svn revisions:
192 : * - If neither has an svn revision, we're fine.
193 : * - If the router doesn't have an svn revision, we can't assume that it
194 : * is "at least" any svn revision, so we need to return 0.
195 : * - If the target version doesn't have an svn revision, any svn revision
196 : * (or none at all) is good enough, so return 1.
197 : * - If both target and router have an svn revision, we compare them.
198 : */
199 :
200 179 : return tor_version_compare(&router_version, &cutoff_version) >= 0;
201 : }
202 :
203 : /** Parse a tor version from <b>s</b>, and store the result in <b>out</b>.
204 : * Return 0 on success, -1 on failure. */
205 : int
206 2169 : tor_version_parse(const char *s, tor_version_t *out)
207 : {
208 2169 : char *eos=NULL;
209 2169 : const char *cp=NULL;
210 2169 : int ok = 1;
211 : /* Format is:
212 : * "Tor " ? NUM dot NUM [ dot NUM [ ( pre | rc | dot ) NUM ] ] [ - tag ]
213 : */
214 2169 : tor_assert(s);
215 2169 : tor_assert(out);
216 :
217 2169 : memset(out, 0, sizeof(tor_version_t));
218 2169 : out->status = VER_RELEASE;
219 2169 : if (!strcasecmpstart(s, "Tor "))
220 21 : s += 4;
221 :
222 2169 : cp = s;
223 :
224 : #define NUMBER(m) \
225 : do { \
226 : if (!cp || *cp < '0' || *cp > '9') \
227 : return -1; \
228 : out->m = (int)tor_parse_uint64(cp, 10, 0, INT32_MAX, &ok, &eos); \
229 : if (!ok) \
230 : return -1; \
231 : if (!eos || eos == cp) \
232 : return -1; \
233 : cp = eos; \
234 : } while (0)
235 :
236 : #define DOT() \
237 : do { \
238 : if (*cp != '.') \
239 : return -1; \
240 : ++cp; \
241 : } while (0)
242 :
243 2169 : NUMBER(major);
244 2158 : DOT();
245 2152 : NUMBER(minor);
246 2129 : if (*cp == 0)
247 : return 0;
248 2096 : else if (*cp == '-')
249 1 : goto status_tag;
250 2095 : DOT();
251 2095 : NUMBER(micro);
252 :
253 : /* Get status */
254 2095 : if (*cp == 0) {
255 : return 0;
256 2066 : } else if (*cp == '.') {
257 2041 : ++cp;
258 25 : } else if (*cp == '-') {
259 18 : goto status_tag;
260 7 : } else if (0==strncmp(cp, "pre", 3)) {
261 3 : out->status = VER_PRE;
262 3 : cp += 3;
263 4 : } else if (0==strncmp(cp, "rc", 2)) {
264 4 : out->status = VER_RC;
265 4 : cp += 2;
266 : } else {
267 : return -1;
268 : }
269 :
270 2048 : NUMBER(patchlevel);
271 :
272 2067 : status_tag:
273 : /* Get status tag. */
274 2067 : if (*cp == '-' || *cp == '.')
275 237 : ++cp;
276 2067 : eos = (char*) find_whitespace(cp);
277 2067 : if (eos-cp >= (int)sizeof(out->status_tag))
278 0 : strlcpy(out->status_tag, cp, sizeof(out->status_tag));
279 : else {
280 2067 : memcpy(out->status_tag, cp, eos-cp);
281 2067 : out->status_tag[eos-cp] = 0;
282 : }
283 2067 : cp = eat_whitespace(eos);
284 :
285 2067 : if (!strcmpstart(cp, "(r")) {
286 11 : cp += 2;
287 11 : out->svn_revision = (int) strtol(cp,&eos,10);
288 2056 : } else if (!strcmpstart(cp, "(git-")) {
289 19 : char *close_paren = strchr(cp, ')');
290 19 : int hexlen;
291 19 : char digest[DIGEST_LEN];
292 19 : if (! close_paren)
293 3 : return -1;
294 19 : cp += 5;
295 19 : if (close_paren-cp > HEX_DIGEST_LEN)
296 : return -1;
297 18 : hexlen = (int)(close_paren-cp);
298 18 : memwipe(digest, 0, sizeof(digest));
299 18 : if (hexlen == 0 || (hexlen % 2) == 1)
300 : return -1;
301 17 : if (base16_decode(digest, hexlen/2, cp, hexlen) != hexlen/2)
302 : return -1;
303 16 : memcpy(out->git_tag, digest, hexlen/2);
304 16 : out->git_tag_len = hexlen/2;
305 : }
306 :
307 : return 0;
308 : #undef NUMBER
309 : #undef DOT
310 : }
311 :
312 : /** Compare two tor versions; Return <0 if a < b; 0 if a ==b, >0 if a >
313 : * b. */
314 : int
315 1029 : tor_version_compare(tor_version_t *a, tor_version_t *b)
316 : {
317 1029 : int i;
318 1029 : tor_assert(a);
319 1029 : tor_assert(b);
320 :
321 : /* We take this approach to comparison to ensure the same (bogus!) behavior
322 : * on all inputs as we would have seen before bug #21278 was fixed. The
323 : * only important difference here is that this method doesn't cause
324 : * a signed integer underflow.
325 : */
326 : #define CMP(field) do { \
327 : unsigned aval = (unsigned) a->field; \
328 : unsigned bval = (unsigned) b->field; \
329 : int result = (int) (aval - bval); \
330 : if (result < 0) \
331 : return -1; \
332 : else if (result > 0) \
333 : return 1; \
334 : } while (0)
335 :
336 1029 : CMP(major);
337 1001 : CMP(minor);
338 859 : CMP(micro);
339 817 : CMP(status);
340 814 : CMP(patchlevel);
341 241 : if ((i = strcmp(a->status_tag, b->status_tag)))
342 : return i;
343 240 : CMP(svn_revision);
344 234 : CMP(git_tag_len);
345 230 : if (a->git_tag_len)
346 3 : return fast_memcmp(a->git_tag, b->git_tag, a->git_tag_len);
347 : else
348 : return 0;
349 :
350 : #undef CMP
351 : }
352 :
353 : /** Return true iff versions <b>a</b> and <b>b</b> belong to the same series.
354 : */
355 : int
356 34 : tor_version_same_series(tor_version_t *a, tor_version_t *b)
357 : {
358 34 : tor_assert(a);
359 34 : tor_assert(b);
360 68 : return ((a->major == b->major) &&
361 34 : (a->minor == b->minor) &&
362 26 : (a->micro == b->micro));
363 : }
364 :
365 : /** Helper: Given pointers to two strings describing tor versions, return -1
366 : * if _a precedes _b, 1 if _b precedes _a, and 0 if they are equivalent.
367 : * Used to sort a list of versions. */
368 : static int
369 819 : compare_tor_version_str_ptr_(const void **_a, const void **_b)
370 : {
371 819 : const char *a = *_a, *b = *_b;
372 819 : int ca, cb;
373 819 : tor_version_t va, vb;
374 819 : ca = tor_version_parse(a, &va);
375 819 : cb = tor_version_parse(b, &vb);
376 : /* If they both parse, compare them. */
377 819 : if (!ca && !cb)
378 816 : return tor_version_compare(&va,&vb);
379 : /* If one parses, it comes first. */
380 3 : if (!ca && cb)
381 : return -1;
382 1 : if (ca && !cb)
383 : return 1;
384 : /* If neither parses, compare strings. Also, the directory server admin
385 : ** needs to be smacked upside the head. But Tor is tolerant and gentle. */
386 0 : return strcmp(a,b);
387 : }
388 :
389 : /** Sort a list of string-representations of versions in ascending order. */
390 : void
391 1379 : sort_version_list(smartlist_t *versions, int remove_duplicates)
392 : {
393 1379 : smartlist_sort(versions, compare_tor_version_str_ptr_);
394 :
395 1379 : if (remove_duplicates)
396 1253 : smartlist_uniq(versions, compare_tor_version_str_ptr_, tor_free_);
397 1379 : }
398 :
399 : /** If there are more than this many entries, we're probably under
400 : * some kind of weird DoS. */
401 : static const int MAX_PROTOVER_SUMMARY_MAP_LEN = 1024;
402 :
403 : /**
404 : * Map from protover string to protover_summary_flags_t.
405 : */
406 : static strmap_t *protover_summary_map = NULL;
407 :
408 : /**
409 : * Helper. Given a non-NULL protover string <b>protocols</b>, set <b>out</b>
410 : * to its summary, and memoize the result in <b>protover_summary_map</b>.
411 : *
412 : * If the protover string does not contain any recognised protocols, sets
413 : * protocols_known, but does not set any other flags. (Empty strings are also
414 : * treated this way.)
415 : */
416 : static void
417 679 : memoize_protover_summary(protover_summary_flags_t *out,
418 : const char *protocols)
419 : {
420 679 : if (!protover_summary_map)
421 59 : protover_summary_map = strmap_new();
422 :
423 679 : if (strmap_size(protover_summary_map) >= MAX_PROTOVER_SUMMARY_MAP_LEN) {
424 0 : protover_summary_cache_free_all();
425 0 : tor_assert(protover_summary_map == NULL);
426 0 : protover_summary_map = strmap_new();
427 : }
428 :
429 679 : const protover_summary_flags_t *cached =
430 679 : strmap_get(protover_summary_map, protocols);
431 :
432 679 : if (cached != NULL) {
433 : /* We found a cached entry; no need to parse this one. */
434 582 : memcpy(out, cached, sizeof(protover_summary_flags_t));
435 582 : tor_assert(out->protocols_known);
436 : return;
437 : }
438 :
439 97 : memset(out, 0, sizeof(*out));
440 97 : out->protocols_known = 1;
441 :
442 194 : out->supports_ed25519_link_handshake_compat =
443 97 : protocol_list_supports_protocol(protocols, PRT_LINKAUTH,
444 : PROTOVER_LINKAUTH_ED25519_HANDSHAKE);
445 194 : out->supports_ed25519_link_handshake_any =
446 97 : protocol_list_supports_protocol_or_later(
447 : protocols,
448 : PRT_LINKAUTH,
449 : PROTOVER_LINKAUTH_ED25519_HANDSHAKE);
450 :
451 194 : out->supports_extend2_cells =
452 97 : protocol_list_supports_protocol(protocols, PRT_RELAY,
453 : PROTOVER_RELAY_EXTEND2);
454 194 : out->supports_accepting_ipv6_extends = (
455 97 : protocol_list_supports_protocol(protocols, PRT_RELAY,
456 176 : PROTOVER_RELAY_ACCEPT_IPV6) ||
457 79 : protocol_list_supports_protocol(protocols, PRT_RELAY,
458 : PROTOVER_RELAY_EXTEND_IPV6));
459 194 : out->supports_initiating_ipv6_extends =
460 97 : protocol_list_supports_protocol(protocols, PRT_RELAY,
461 : PROTOVER_RELAY_EXTEND_IPV6);
462 194 : out->supports_canonical_ipv6_conns =
463 97 : protocol_list_supports_protocol(protocols, PRT_RELAY,
464 : PROTOVER_RELAY_CANONICAL_IPV6);
465 :
466 194 : out->supports_ed25519_hs_intro =
467 97 : protocol_list_supports_protocol(protocols, PRT_HSINTRO,
468 : PROTOVER_HS_INTRO_V3);
469 194 : out->supports_establish_intro_dos_extension =
470 97 : protocol_list_supports_protocol(protocols, PRT_HSINTRO,
471 : PROTOVER_HS_INTRO_DOS);
472 :
473 194 : out->supports_v3_rendezvous_point =
474 97 : protocol_list_supports_protocol(protocols, PRT_HSREND,
475 : PROTOVER_HS_RENDEZVOUS_POINT_V3);
476 :
477 194 : out->supports_v3_hsdir =
478 97 : protocol_list_supports_protocol(protocols, PRT_HSDIR,
479 : PROTOVER_HSDIR_V3);
480 :
481 194 : out->supports_hs_setup_padding =
482 97 : protocol_list_supports_protocol(protocols, PRT_PADDING,
483 : PROTOVER_HS_SETUP_PADDING);
484 :
485 97 : protover_summary_flags_t *new_cached = tor_memdup(out, sizeof(*out));
486 97 : cached = strmap_set(protover_summary_map, protocols, new_cached);
487 97 : tor_assert(!cached);
488 : }
489 :
490 : /** Summarize the protocols listed in <b>protocols</b> into <b>out</b>,
491 : * falling back or correcting them based on <b>version</b> as appropriate.
492 : *
493 : * If protocols and version are both NULL or "", returns a summary with no
494 : * flags set.
495 : *
496 : * If the protover string does not contain any recognised protocols, and the
497 : * version is not recognised, sets protocols_known, but does not set any other
498 : * flags. (Empty strings are also treated this way.)
499 : */
500 : void
501 1021 : summarize_protover_flags(protover_summary_flags_t *out,
502 : const char *protocols,
503 : const char *version)
504 : {
505 1021 : tor_assert(out);
506 1021 : memset(out, 0, sizeof(*out));
507 1021 : if (protocols && strcmp(protocols, "")) {
508 679 : memoize_protover_summary(out, protocols);
509 : }
510 1021 : if (version && strcmp(version, "") && !strcmpstart(version, "Tor ")) {
511 130 : if (!out->protocols_known) {
512 : /* The version is a "Tor" version, and where there is no
513 : * list of protocol versions that we should be looking at instead. */
514 :
515 106 : out->supports_extend2_cells =
516 53 : tor_version_as_new_as(version, "0.2.4.8-alpha");
517 53 : out->protocols_known = 1;
518 : } else {
519 : /* Bug #22447 forces us to filter on this version. */
520 77 : if (!tor_version_as_new_as(version, "0.3.0.8")) {
521 22 : out->supports_v3_hsdir = 0;
522 : }
523 : }
524 : }
525 1021 : }
526 :
527 : /**
528 : * Free all space held in the protover_summary_map.
529 : */
530 : void
531 236 : protover_summary_cache_free_all(void)
532 : {
533 236 : strmap_free(protover_summary_map, tor_free_);
534 236 : protover_summary_map = NULL;
535 236 : }
|