versions.c (17834B)
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 tor_get_approx_release_date(void) 26 { 27 char tbuf[ISO_TIME_LEN+1]; 28 tor_snprintf(tbuf, sizeof(tbuf), 29 "%s 00:00:00", APPROX_RELEASE_DATE); 30 time_t result = 0; 31 int r = parse_iso_time(tbuf, &result); 32 if (BUG(r < 0)) { 33 result = 0; 34 } 35 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 tor_version_is_obsolete(const char *myversion, const char *versionlist) 54 { 55 tor_version_t mine, other; 56 int found_newer = 0, found_older = 0, found_newer_in_series = 0, 57 found_any_in_series = 0, r, same; 58 version_status_t ret = VS_UNRECOMMENDED; 59 smartlist_t *version_sl; 60 61 log_debug(LD_CONFIG,"Checking whether version '%s' is in '%s'", 62 myversion, versionlist); 63 64 if (tor_version_parse(myversion, &mine)) { 65 log_err(LD_BUG,"I couldn't parse my own version (%s)", myversion); 66 tor_assert(0); 67 } 68 version_sl = smartlist_new(); 69 smartlist_split_string(version_sl, versionlist, ",", SPLIT_SKIP_SPACE, 0); 70 71 if (!strlen(versionlist)) { /* no authorities cared or agreed */ 72 ret = VS_EMPTY; 73 goto done; 74 } 75 76 SMARTLIST_FOREACH_BEGIN(version_sl, const char *, cp) { 77 if (!strcmpstart(cp, "Tor ")) 78 cp += 4; 79 80 if (tor_version_parse(cp, &other)) { 81 /* Couldn't parse other; it can't be a match. */ 82 } else { 83 same = tor_version_same_series(&mine, &other); 84 if (same) 85 found_any_in_series = 1; 86 r = tor_version_compare(&mine, &other); 87 if (r==0) { 88 ret = VS_RECOMMENDED; 89 goto done; 90 } else if (r<0) { 91 found_newer = 1; 92 if (same) 93 found_newer_in_series = 1; 94 } else if (r>0) { 95 found_older = 1; 96 } 97 } 98 } SMARTLIST_FOREACH_END(cp); 99 100 /* We didn't find the listed version. Is it new or old? */ 101 if (found_any_in_series && !found_newer_in_series && found_newer) { 102 ret = VS_NEW_IN_SERIES; 103 } else if (found_newer && !found_older) { 104 ret = VS_OLD; 105 } else if (found_older && !found_newer) { 106 ret = VS_NEW; 107 } else { 108 ret = VS_UNRECOMMENDED; 109 } 110 111 done: 112 SMARTLIST_FOREACH(version_sl, char *, version, tor_free(version)); 113 smartlist_free(version_sl); 114 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 tor_version_parse_platform(const char *platform, 128 tor_version_t *router_version, 129 int strict) 130 { 131 char tmp[128]; 132 char *s, *s2, *start; 133 134 if (strcmpstart(platform,"Tor ")) /* nonstandard Tor; say 0. */ 135 return 0; 136 137 start = (char *)eat_whitespace(platform+3); 138 if (!*start) return -1; 139 s = (char *)find_whitespace(start); /* also finds '\0', which is fine */ 140 s2 = (char*)eat_whitespace(s); 141 if (!strcmpstart(s2, "(r") || !strcmpstart(s2, "(git-")) 142 s = (char*)find_whitespace(s2); 143 144 if ((size_t)(s-start+1) >= sizeof(tmp)) /* too big, no */ 145 return -1; 146 strlcpy(tmp, start, s-start+1); 147 148 if (tor_version_parse(tmp, router_version)<0) { 149 log_info(LD_DIR,"Router version '%s' unparseable.",tmp); 150 return -1; 151 } 152 153 if (strict) { 154 if (router_version->major < 0 || 155 router_version->minor < 0 || 156 router_version->micro < 0 || 157 router_version->patchlevel < 0 || 158 router_version->svn_revision < 0) { 159 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 tor_version_as_new_as(const char *platform, const char *cutoff) 172 { 173 tor_version_t cutoff_version, router_version; 174 int r; 175 tor_assert(platform); 176 177 if (tor_version_parse(cutoff, &cutoff_version)<0) { 178 log_warn(LD_BUG,"cutoff version '%s' unparseable.",cutoff); 179 return 0; 180 } 181 182 r = tor_version_parse_platform(platform, &router_version, 0); 183 if (r == 0) { 184 /* nonstandard Tor; be safe and say yes */ 185 return 1; 186 } 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 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 tor_version_parse(const char *s, tor_version_t *out) 207 { 208 char *eos=NULL; 209 const char *cp=NULL; 210 int ok = 1; 211 /* Format is: 212 * "Tor " ? NUM dot NUM [ dot NUM [ ( pre | rc | dot ) NUM ] ] [ - tag ] 213 */ 214 tor_assert(s); 215 tor_assert(out); 216 217 memset(out, 0, sizeof(tor_version_t)); 218 out->status = VER_RELEASE; 219 if (!strcasecmpstart(s, "Tor ")) 220 s += 4; 221 222 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 NUMBER(major); 244 DOT(); 245 NUMBER(minor); 246 if (*cp == 0) 247 return 0; 248 else if (*cp == '-') 249 goto status_tag; 250 DOT(); 251 NUMBER(micro); 252 253 /* Get status */ 254 if (*cp == 0) { 255 return 0; 256 } else if (*cp == '.') { 257 ++cp; 258 } else if (*cp == '-') { 259 goto status_tag; 260 } else if (0==strncmp(cp, "pre", 3)) { 261 out->status = VER_PRE; 262 cp += 3; 263 } else if (0==strncmp(cp, "rc", 2)) { 264 out->status = VER_RC; 265 cp += 2; 266 } else { 267 return -1; 268 } 269 270 NUMBER(patchlevel); 271 272 status_tag: 273 /* Get status tag. */ 274 if (*cp == '-' || *cp == '.') 275 ++cp; 276 eos = (char*) find_whitespace(cp); 277 if (eos-cp >= (int)sizeof(out->status_tag)) 278 strlcpy(out->status_tag, cp, sizeof(out->status_tag)); 279 else { 280 memcpy(out->status_tag, cp, eos-cp); 281 out->status_tag[eos-cp] = 0; 282 } 283 cp = eat_whitespace(eos); 284 285 if (!strcmpstart(cp, "(r")) { 286 cp += 2; 287 out->svn_revision = (int) strtol(cp,&eos,10); 288 } else if (!strcmpstart(cp, "(git-")) { 289 const char *close_paren = strchr(cp, ')'); 290 int hexlen; 291 char digest[DIGEST_LEN]; 292 if (! close_paren) 293 return -1; 294 cp += 5; 295 if (close_paren-cp > HEX_DIGEST_LEN) 296 return -1; 297 hexlen = (int)(close_paren-cp); 298 memwipe(digest, 0, sizeof(digest)); 299 if (hexlen == 0 || (hexlen % 2) == 1) 300 return -1; 301 if (base16_decode(digest, hexlen/2, cp, hexlen) != hexlen/2) 302 return -1; 303 memcpy(out->git_tag, digest, hexlen/2); 304 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 tor_version_compare(tor_version_t *a, tor_version_t *b) 316 { 317 int i; 318 tor_assert(a); 319 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 CMP(major); 337 CMP(minor); 338 CMP(micro); 339 CMP(status); 340 CMP(patchlevel); 341 if ((i = strcmp(a->status_tag, b->status_tag))) 342 return i; 343 CMP(svn_revision); 344 CMP(git_tag_len); 345 if (a->git_tag_len) 346 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 tor_version_same_series(tor_version_t *a, tor_version_t *b) 357 { 358 tor_assert(a); 359 tor_assert(b); 360 return ((a->major == b->major) && 361 (a->minor == b->minor) && 362 (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 compare_tor_version_str_ptr_(const void **_a, const void **_b) 370 { 371 const char *a = *_a, *b = *_b; 372 int ca, cb; 373 tor_version_t va, vb; 374 ca = tor_version_parse(a, &va); 375 cb = tor_version_parse(b, &vb); 376 /* If they both parse, compare them. */ 377 if (!ca && !cb) 378 return tor_version_compare(&va,&vb); 379 /* If one parses, it comes first. */ 380 if (!ca && cb) 381 return -1; 382 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 return strcmp(a,b); 387 } 388 389 /** Sort a list of string-representations of versions in ascending order. */ 390 void 391 sort_version_list(smartlist_t *versions, int remove_duplicates) 392 { 393 smartlist_sort(versions, compare_tor_version_str_ptr_); 394 395 if (remove_duplicates) 396 smartlist_uniq(versions, compare_tor_version_str_ptr_, tor_free_); 397 } 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 memoize_protover_summary(protover_summary_flags_t *out, 418 const char *protocols) 419 { 420 if (!protover_summary_map) 421 protover_summary_map = strmap_new(); 422 423 if (strmap_size(protover_summary_map) >= MAX_PROTOVER_SUMMARY_MAP_LEN) { 424 protover_summary_cache_free_all(); 425 tor_assert(protover_summary_map == NULL); 426 protover_summary_map = strmap_new(); 427 } 428 429 const protover_summary_flags_t *cached = 430 strmap_get(protover_summary_map, protocols); 431 432 if (cached != NULL) { 433 /* We found a cached entry; no need to parse this one. */ 434 memcpy(out, cached, sizeof(protover_summary_flags_t)); 435 tor_assert(out->protocols_known); 436 return; 437 } 438 439 memset(out, 0, sizeof(*out)); 440 out->protocols_known = 1; 441 442 out->supports_ed25519_link_handshake_compat = 443 protocol_list_supports_protocol(protocols, PRT_LINKAUTH, 444 PROTOVER_LINKAUTH_ED25519_HANDSHAKE); 445 out->supports_ed25519_link_handshake_any = 446 protocol_list_supports_protocol_or_later( 447 protocols, 448 PRT_LINKAUTH, 449 PROTOVER_LINKAUTH_ED25519_HANDSHAKE); 450 451 out->supports_extend2_cells = 452 protocol_list_supports_protocol(protocols, PRT_RELAY, 453 PROTOVER_RELAY_EXTEND2); 454 out->supports_accepting_ipv6_extends = ( 455 protocol_list_supports_protocol(protocols, PRT_RELAY, 456 PROTOVER_RELAY_ACCEPT_IPV6) || 457 protocol_list_supports_protocol(protocols, PRT_RELAY, 458 PROTOVER_RELAY_EXTEND_IPV6)); 459 out->supports_initiating_ipv6_extends = 460 protocol_list_supports_protocol(protocols, PRT_RELAY, 461 PROTOVER_RELAY_EXTEND_IPV6); 462 out->supports_canonical_ipv6_conns = 463 protocol_list_supports_protocol(protocols, PRT_RELAY, 464 PROTOVER_RELAY_CANONICAL_IPV6); 465 466 out->supports_ed25519_hs_intro = 467 protocol_list_supports_protocol(protocols, PRT_HSINTRO, 468 PROTOVER_HS_INTRO_V3); 469 out->supports_establish_intro_dos_extension = 470 protocol_list_supports_protocol(protocols, PRT_HSINTRO, 471 PROTOVER_HS_INTRO_DOS); 472 473 out->supports_v3_rendezvous_point = 474 protocol_list_supports_protocol(protocols, PRT_HSREND, 475 PROTOVER_HS_RENDEZVOUS_POINT_V3); 476 477 out->supports_v3_hsdir = 478 protocol_list_supports_protocol(protocols, PRT_HSDIR, 479 PROTOVER_HSDIR_V3); 480 481 out->supports_hs_setup_padding = 482 protocol_list_supports_protocol(protocols, PRT_PADDING, 483 PROTOVER_HS_SETUP_PADDING); 484 485 out->supports_congestion_control = 486 protocol_list_supports_protocol(protocols, PRT_FLOWCTRL, 487 PROTOVER_FLOWCTRL_CC) && 488 protocol_list_supports_protocol(protocols, PRT_RELAY, 489 PROTOVER_RELAY_NTOR_V3); 490 491 /* Conflux requires congestion control. */ 492 out->supports_conflux = 493 protocol_list_supports_protocol(protocols, PRT_FLOWCTRL, 494 PROTOVER_FLOWCTRL_CC) && 495 protocol_list_supports_protocol(protocols, PRT_CONFLUX, 496 PROTOVER_CONFLUX_V1); 497 498 out->supports_ntor_v3 = 499 protocol_list_supports_protocol(protocols, PRT_RELAY, 500 PROTOVER_RELAY_NTOR_V3); 501 502 /* CGO requires congestion control and subproto negotiation. */ 503 out->supports_cgo = 504 protocol_list_supports_protocol(protocols, PRT_FLOWCTRL, 505 PROTOVER_FLOWCTRL_CC) && 506 protocol_list_supports_protocol(protocols, PRT_RELAY, 507 PROTOVER_RELAY_NEGOTIATE_SUBPROTO) && 508 protocol_list_supports_protocol(protocols, PRT_RELAY, 509 PROTOVER_RELAY_CRYPT_CGO); 510 511 protover_summary_flags_t *new_cached = tor_memdup(out, sizeof(*out)); 512 cached = strmap_set(protover_summary_map, protocols, new_cached); 513 tor_assert(!cached); 514 } 515 516 /** Summarize the protocols listed in <b>protocols</b> into <b>out</b>, 517 * falling back or correcting them based on <b>version</b> as appropriate. 518 * 519 * If protocols and version are both NULL or "", returns a summary with no 520 * flags set. 521 * 522 * If the protover string does not contain any recognised protocols, and the 523 * version is not recognised, sets protocols_known, but does not set any other 524 * flags. (Empty strings are also treated this way.) 525 */ 526 void 527 summarize_protover_flags(protover_summary_flags_t *out, 528 const char *protocols, 529 const char *version) 530 { 531 tor_assert(out); 532 memset(out, 0, sizeof(*out)); 533 if (protocols && strcmp(protocols, "")) { 534 memoize_protover_summary(out, protocols); 535 } 536 if (version && strcmp(version, "") && !strcmpstart(version, "Tor ")) { 537 if (!out->protocols_known) { 538 /* The version is a "Tor" version, and where there is no 539 * list of protocol versions that we should be looking at instead. */ 540 541 out->supports_extend2_cells = 542 tor_version_as_new_as(version, "0.2.4.8-alpha"); 543 out->protocols_known = 1; 544 } else { 545 /* Bug #22447 forces us to filter on this version. */ 546 if (!tor_version_as_new_as(version, "0.3.0.8")) { 547 out->supports_v3_hsdir = 0; 548 } 549 } 550 } 551 } 552 553 /** 554 * Free all space held in the protover_summary_map. 555 */ 556 void 557 protover_summary_cache_free_all(void) 558 { 559 strmap_free(protover_summary_map, tor_free_); 560 protover_summary_map = NULL; 561 }