diff options
author | Nick Mathewson <nickm@torproject.org> | 2018-09-30 18:27:23 -0500 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2018-10-01 00:04:58 -0500 |
commit | 2f5dc486993b88eb23de7f06043991f08e4d0d73 (patch) | |
tree | d56ea5c1cd515f0ce635812f6ec6974d8b337d4a /src/core | |
parent | fec3b3bb9389d9d9f927c370d560fc50ec65c93b (diff) | |
download | tor-2f5dc486993b88eb23de7f06043991f08e4d0d73.tar.gz tor-2f5dc486993b88eb23de7f06043991f08e4d0d73.zip |
Extract the version-managing code from routerparse.c
Leave the versions.h include in routerparse.h for now; I'll remove
it later.
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/include.am | 2 | ||||
-rw-r--r-- | src/core/or/versions.c | 377 | ||||
-rw-r--r-- | src/core/or/versions.h | 40 |
3 files changed, 419 insertions, 0 deletions
diff --git a/src/core/include.am b/src/core/include.am index 45f4cb5c4e..c1f63c751e 100644 --- a/src/core/include.am +++ b/src/core/include.am @@ -46,6 +46,7 @@ LIBTOR_APP_A_SOURCES = \ src/core/or/scheduler_kist.c \ src/core/or/scheduler_vanilla.c \ src/core/or/status.c \ + src/core/or/versions.c \ src/core/proto/proto_cell.c \ src/core/proto/proto_control0.c \ src/core/proto/proto_ext_or.c \ @@ -241,6 +242,7 @@ noinst_HEADERS += \ src/core/or/status.h \ src/core/or/tor_version_st.h \ src/core/or/var_cell_st.h \ + src/core/or/versions.h \ src/core/proto/proto_cell.h \ src/core/proto/proto_control0.h \ src/core/proto/proto_ext_or.h \ diff --git a/src/core/or/versions.c b/src/core/or/versions.c new file mode 100644 index 0000000000..2d24862981 --- /dev/null +++ b/src/core/or/versions.c @@ -0,0 +1,377 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, 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/versions.h" +#include "lib/crypt_ops/crypto_util.h" + +#include "core/or/tor_version_st.h" + +/** Return VS_RECOMMENDED if <b>myversion</b> is contained in + * <b>versionlist</b>. Else, return VS_EMPTY if versionlist has no + * entries. Else, return VS_OLD if every member of + * <b>versionlist</b> is newer than <b>myversion</b>. Else, return + * VS_NEW_IN_SERIES if there is at least one member of <b>versionlist</b> in + * the same series (major.minor.micro) as <b>myversion</b>, but no such member + * is newer than <b>myversion.</b>. Else, return VS_NEW if every member of + * <b>versionlist</b> is older than <b>myversion</b>. 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 <b>platform</b> line from a router + * descriptor, and place the result in <b>router_version</b>. + * + * Return 1 on success, -1 on parsing failure, and 0 if the + * platform line does not indicate some version of Tor. + * + * If <b>strict</b> 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 <b>platform</b>, + * and compare it to the version in <b>cutoff</b>. 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 <b>s</b>, and store the result in <b>out</b>. + * 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 <b>a</b> and <b>b</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_); +} diff --git a/src/core/or/versions.h b/src/core/or/versions.h new file mode 100644 index 0000000000..a2353bcae3 --- /dev/null +++ b/src/core/or/versions.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2018, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +/** + * \file versions.h + * \brief Header file for versions.c. + **/ + +#ifndef TOR_VERSIONS_H +#define TOR_VERSIONS_H + +/** Possible statuses of a version of Tor, given opinions from the directory + * servers. */ +typedef enum version_status_t { + VS_RECOMMENDED=0, /**< This version is listed as recommended. */ + VS_OLD=1, /**< This version is older than any recommended version. */ + VS_NEW=2, /**< This version is newer than any recommended version. */ + VS_NEW_IN_SERIES=3, /**< This version is newer than any recommended version + * in its series, but later recommended versions exist. + */ + VS_UNRECOMMENDED=4, /**< This version is not recommended (general case). */ + VS_EMPTY=5, /**< The version list was empty; no agreed-on versions. */ + VS_UNKNOWN, /**< We have no idea. */ +} version_status_t; + +version_status_t tor_version_is_obsolete(const char *myversion, + const char *versionlist); +int tor_version_parse_platform(const char *platform, + tor_version_t *version_out, + int strict); +int tor_version_as_new_as(const char *platform, const char *cutoff); +int tor_version_parse(const char *s, tor_version_t *out); +int tor_version_compare(tor_version_t *a, tor_version_t *b); +int tor_version_same_series(tor_version_t *a, tor_version_t *b); +void sort_version_list(smartlist_t *lst, int remove_duplicates); + +#endif /* !defined(TOR_VERSIONS_H) */ |