/* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. * Copyright (c) 2007-2019, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file versions.c * \brief Code to manipulate, parse, and compare Tor versions. */ #include "core/or/or.h" #include "core/or/protover.h" #include "core/or/versions.h" #include "lib/crypt_ops/crypto_util.h" #include "core/or/tor_version_st.h" /** Return VS_RECOMMENDED if myversion is contained in * versionlist. Else, return VS_EMPTY if versionlist has no * entries. Else, return VS_OLD if every member of * versionlist is newer than myversion. Else, return * VS_NEW_IN_SERIES if there is at least one member of versionlist in * the same series (major.minor.micro) as myversion, but no such member * is newer than myversion.. Else, return VS_NEW if every member of * versionlist is older than myversion. Else, return * VS_UNRECOMMENDED. * * (versionlist is a comma-separated list of version strings, * optionally prefixed with "Tor". Versions that can't be parsed are * ignored.) */ version_status_t tor_version_is_obsolete(const char *myversion, const char *versionlist) { tor_version_t mine, other; int found_newer = 0, found_older = 0, found_newer_in_series = 0, found_any_in_series = 0, r, same; version_status_t ret = VS_UNRECOMMENDED; smartlist_t *version_sl; log_debug(LD_CONFIG,"Checking whether version '%s' is in '%s'", myversion, versionlist); if (tor_version_parse(myversion, &mine)) { log_err(LD_BUG,"I couldn't parse my own version (%s)", myversion); tor_assert(0); } version_sl = smartlist_new(); smartlist_split_string(version_sl, versionlist, ",", SPLIT_SKIP_SPACE, 0); if (!strlen(versionlist)) { /* no authorities cared or agreed */ ret = VS_EMPTY; goto done; } SMARTLIST_FOREACH_BEGIN(version_sl, const char *, cp) { if (!strcmpstart(cp, "Tor ")) cp += 4; if (tor_version_parse(cp, &other)) { /* Couldn't parse other; it can't be a match. */ } else { same = tor_version_same_series(&mine, &other); if (same) found_any_in_series = 1; r = tor_version_compare(&mine, &other); if (r==0) { ret = VS_RECOMMENDED; goto done; } else if (r<0) { found_newer = 1; if (same) found_newer_in_series = 1; } else if (r>0) { found_older = 1; } } } SMARTLIST_FOREACH_END(cp); /* We didn't find the listed version. Is it new or old? */ if (found_any_in_series && !found_newer_in_series && found_newer) { ret = VS_NEW_IN_SERIES; } else if (found_newer && !found_older) { ret = VS_OLD; } else if (found_older && !found_newer) { ret = VS_NEW; } else { ret = VS_UNRECOMMENDED; } done: SMARTLIST_FOREACH(version_sl, char *, version, tor_free(version)); smartlist_free(version_sl); return ret; } /** Extract a Tor version from a platform line from a router * descriptor, and place the result in router_version. * * Return 1 on success, -1 on parsing failure, and 0 if the * platform line does not indicate some version of Tor. * * If strict is non-zero, finding any weird version components * (like negative numbers) counts as a parsing failure. */ int tor_version_parse_platform(const char *platform, tor_version_t *router_version, int strict) { char tmp[128]; char *s, *s2, *start; if (strcmpstart(platform,"Tor ")) /* nonstandard Tor; say 0. */ return 0; start = (char *)eat_whitespace(platform+3); if (!*start) return -1; s = (char *)find_whitespace(start); /* also finds '\0', which is fine */ s2 = (char*)eat_whitespace(s); if (!strcmpstart(s2, "(r") || !strcmpstart(s2, "(git-")) s = (char*)find_whitespace(s2); if ((size_t)(s-start+1) >= sizeof(tmp)) /* too big, no */ return -1; strlcpy(tmp, start, s-start+1); if (tor_version_parse(tmp, router_version)<0) { log_info(LD_DIR,"Router version '%s' unparseable.",tmp); return -1; } if (strict) { if (router_version->major < 0 || router_version->minor < 0 || router_version->micro < 0 || router_version->patchlevel < 0 || router_version->svn_revision < 0) { return -1; } } return 1; } /** Parse the Tor version of the platform string platform, * and compare it to the version in cutoff. Return 1 if * the router is at least as new as the cutoff, else return 0. */ int tor_version_as_new_as(const char *platform, const char *cutoff) { tor_version_t cutoff_version, router_version; int r; tor_assert(platform); if (tor_version_parse(cutoff, &cutoff_version)<0) { log_warn(LD_BUG,"cutoff version '%s' unparseable.",cutoff); return 0; } r = tor_version_parse_platform(platform, &router_version, 0); if (r == 0) { /* nonstandard Tor; be safe and say yes */ return 1; } else if (r < 0) { /* unparseable version; be safe and say yes. */ return 1; } /* Here's why we don't need to do any special handling for svn revisions: * - If neither has an svn revision, we're fine. * - If the router doesn't have an svn revision, we can't assume that it * is "at least" any svn revision, so we need to return 0. * - If the target version doesn't have an svn revision, any svn revision * (or none at all) is good enough, so return 1. * - If both target and router have an svn revision, we compare them. */ return tor_version_compare(&router_version, &cutoff_version) >= 0; } /** Parse a tor version from s, and store the result in out. * Return 0 on success, -1 on failure. */ int tor_version_parse(const char *s, tor_version_t *out) { char *eos=NULL; const char *cp=NULL; int ok = 1; /* Format is: * "Tor " ? NUM dot NUM [ dot NUM [ ( pre | rc | dot ) NUM ] ] [ - tag ] */ tor_assert(s); tor_assert(out); memset(out, 0, sizeof(tor_version_t)); out->status = VER_RELEASE; if (!strcasecmpstart(s, "Tor ")) s += 4; cp = s; #define NUMBER(m) \ do { \ if (!cp || *cp < '0' || *cp > '9') \ return -1; \ out->m = (int)tor_parse_uint64(cp, 10, 0, INT32_MAX, &ok, &eos); \ if (!ok) \ return -1; \ if (!eos || eos == cp) \ return -1; \ cp = eos; \ } while (0) #define DOT() \ do { \ if (*cp != '.') \ return -1; \ ++cp; \ } while (0) NUMBER(major); DOT(); NUMBER(minor); if (*cp == 0) return 0; else if (*cp == '-') goto status_tag; DOT(); NUMBER(micro); /* Get status */ if (*cp == 0) { return 0; } else if (*cp == '.') { ++cp; } else if (*cp == '-') { goto status_tag; } else if (0==strncmp(cp, "pre", 3)) { out->status = VER_PRE; cp += 3; } else if (0==strncmp(cp, "rc", 2)) { out->status = VER_RC; cp += 2; } else { return -1; } NUMBER(patchlevel); status_tag: /* Get status tag. */ if (*cp == '-' || *cp == '.') ++cp; eos = (char*) find_whitespace(cp); if (eos-cp >= (int)sizeof(out->status_tag)) strlcpy(out->status_tag, cp, sizeof(out->status_tag)); else { memcpy(out->status_tag, cp, eos-cp); out->status_tag[eos-cp] = 0; } cp = eat_whitespace(eos); if (!strcmpstart(cp, "(r")) { cp += 2; out->svn_revision = (int) strtol(cp,&eos,10); } else if (!strcmpstart(cp, "(git-")) { char *close_paren = strchr(cp, ')'); int hexlen; char digest[DIGEST_LEN]; if (! close_paren) return -1; cp += 5; if (close_paren-cp > HEX_DIGEST_LEN) return -1; hexlen = (int)(close_paren-cp); memwipe(digest, 0, sizeof(digest)); if ( hexlen == 0 || (hexlen % 2) == 1) return -1; if (base16_decode(digest, hexlen/2, cp, hexlen) != hexlen/2) return -1; memcpy(out->git_tag, digest, hexlen/2); out->git_tag_len = hexlen/2; } return 0; #undef NUMBER #undef DOT } /** Compare two tor versions; Return <0 if a < b; 0 if a ==b, >0 if a > * b. */ int tor_version_compare(tor_version_t *a, tor_version_t *b) { int i; tor_assert(a); tor_assert(b); /* We take this approach to comparison to ensure the same (bogus!) behavior * on all inputs as we would have seen before bug #21278 was fixed. The * only important difference here is that this method doesn't cause * a signed integer underflow. */ #define CMP(field) do { \ unsigned aval = (unsigned) a->field; \ unsigned bval = (unsigned) b->field; \ int result = (int) (aval - bval); \ if (result < 0) \ return -1; \ else if (result > 0) \ return 1; \ } while (0) CMP(major); CMP(minor); CMP(micro); CMP(status); CMP(patchlevel); if ((i = strcmp(a->status_tag, b->status_tag))) return i; CMP(svn_revision); CMP(git_tag_len); if (a->git_tag_len) return fast_memcmp(a->git_tag, b->git_tag, a->git_tag_len); else return 0; #undef CMP } /** Return true iff versions a and b belong to the same series. */ int tor_version_same_series(tor_version_t *a, tor_version_t *b) { tor_assert(a); tor_assert(b); return ((a->major == b->major) && (a->minor == b->minor) && (a->micro == b->micro)); } /** Helper: Given pointers to two strings describing tor versions, return -1 * if _a precedes _b, 1 if _b precedes _a, and 0 if they are equivalent. * Used to sort a list of versions. */ static int compare_tor_version_str_ptr_(const void **_a, const void **_b) { const char *a = *_a, *b = *_b; int ca, cb; tor_version_t va, vb; ca = tor_version_parse(a, &va); cb = tor_version_parse(b, &vb); /* If they both parse, compare them. */ if (!ca && !cb) return tor_version_compare(&va,&vb); /* If one parses, it comes first. */ if (!ca && cb) return -1; if (ca && !cb) return 1; /* If neither parses, compare strings. Also, the directory server admin ** needs to be smacked upside the head. But Tor is tolerant and gentle. */ return strcmp(a,b); } /** Sort a list of string-representations of versions in ascending order. */ void sort_version_list(smartlist_t *versions, int remove_duplicates) { smartlist_sort(versions, compare_tor_version_str_ptr_); if (remove_duplicates) smartlist_uniq(versions, compare_tor_version_str_ptr_, tor_free_); } /** Summarize the protocols listed in protocols into out, * falling back or correcting them based on version as appropriate. */ void summarize_protover_flags(protover_summary_flags_t *out, const char *protocols, const char *version) { tor_assert(out); memset(out, 0, sizeof(*out)); if (protocols) { out->protocols_known = 1; out->supports_extend2_cells = protocol_list_supports_protocol(protocols, PRT_RELAY, 2); out->supports_ed25519_link_handshake_compat = protocol_list_supports_protocol(protocols, PRT_LINKAUTH, 3); out->supports_ed25519_link_handshake_any = protocol_list_supports_protocol_or_later(protocols, PRT_LINKAUTH, 3); out->supports_ed25519_hs_intro = protocol_list_supports_protocol(protocols, PRT_HSINTRO, 4); out->supports_v3_hsdir = protocol_list_supports_protocol(protocols, PRT_HSDIR, PROTOVER_HSDIR_V3); out->supports_v3_rendezvous_point = protocol_list_supports_protocol(protocols, PRT_HSREND, PROTOVER_HS_RENDEZVOUS_POINT_V3); } if (version && !strcmpstart(version, "Tor ")) { if (!out->protocols_known) { /* The version is a "Tor" version, and where there is no * list of protocol versions that we should be looking at instead. */ out->supports_extend2_cells = tor_version_as_new_as(version, "0.2.4.8-alpha"); out->protocols_known = 1; } else { /* Bug #22447 forces us to filter on this version. */ if (!tor_version_as_new_as(version, "0.3.0.8")) { out->supports_v3_hsdir = 0; } } } }