|  | @@ -4785,40 +4785,78 @@ microdescs_parse_from_string(const char *s, const char *eos,
 | 
	
		
			
				|  |  |    return result;
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -/** 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.
 | 
	
		
			
				|  |  | +/** 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_as_new_as(const char *platform, const char *cutoff)
 | 
	
		
			
				|  |  | +tor_version_parse_platform(const char *platform,
 | 
	
		
			
				|  |  | +                           tor_version_t *router_version,
 | 
	
		
			
				|  |  | +                           int strict)
 | 
	
		
			
				|  |  |  {
 | 
	
		
			
				|  |  | -  tor_version_t cutoff_version, router_version;
 | 
	
		
			
				|  |  | -  char *s, *s2, *start;
 | 
	
		
			
				|  |  |    char tmp[128];
 | 
	
		
			
				|  |  | +  char *s, *s2, *start;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  tor_assert(platform);
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  if (tor_version_parse(cutoff, &cutoff_version)<0) {
 | 
	
		
			
				|  |  | -    log_warn(LD_BUG,"cutoff version '%s' unparseable.",cutoff);
 | 
	
		
			
				|  |  | +  if (strcmpstart(platform,"Tor ")) /* nonstandard Tor; say 0. */
 | 
	
		
			
				|  |  |      return 0;
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -  if (strcmpstart(platform,"Tor ")) /* nonstandard Tor; be safe and say yes */
 | 
	
		
			
				|  |  | -    return 1;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    start = (char *)eat_whitespace(platform+3);
 | 
	
		
			
				|  |  | -  if (!*start) return 0;
 | 
	
		
			
				|  |  | +  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 0;
 | 
	
		
			
				|  |  | +    return -1;
 | 
	
		
			
				|  |  |    strlcpy(tmp, start, s-start+1);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  if (tor_version_parse(tmp, &router_version)<0) {
 | 
	
		
			
				|  |  | +  if (tor_version_parse(tmp, router_version)<0) {
 | 
	
		
			
				|  |  |      log_info(LD_DIR,"Router version '%s' unparseable.",tmp);
 | 
	
		
			
				|  |  | -    return 1; /* be safe and say yes */
 | 
	
		
			
				|  |  | +    return -1;
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  if (strict) {
 | 
	
		
			
				|  |  | +    if (router_version->major < 0 ||
 | 
	
		
			
				|  |  | +        router_version->minor < 0 ||
 | 
	
		
			
				|  |  | +        router_version->minor < 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:
 | 
	
	
		
			
				|  | @@ -4945,26 +4983,37 @@ tor_version_compare(tor_version_t *a, tor_version_t *b)
 | 
	
		
			
				|  |  |    int i;
 | 
	
		
			
				|  |  |    tor_assert(a);
 | 
	
		
			
				|  |  |    tor_assert(b);
 | 
	
		
			
				|  |  | -  if ((i = a->major - b->major))
 | 
	
		
			
				|  |  | -    return i;
 | 
	
		
			
				|  |  | -  else if ((i = a->minor - b->minor))
 | 
	
		
			
				|  |  | -    return i;
 | 
	
		
			
				|  |  | -  else if ((i = a->micro - b->micro))
 | 
	
		
			
				|  |  | -    return i;
 | 
	
		
			
				|  |  | -  else if ((i = a->status - b->status))
 | 
	
		
			
				|  |  | -    return i;
 | 
	
		
			
				|  |  | -  else if ((i = a->patchlevel - b->patchlevel))
 | 
	
		
			
				|  |  | -    return i;
 | 
	
		
			
				|  |  | -  else if ((i = strcmp(a->status_tag, b->status_tag)))
 | 
	
		
			
				|  |  | -    return i;
 | 
	
		
			
				|  |  | -  else if ((i = a->svn_revision - b->svn_revision))
 | 
	
		
			
				|  |  | -    return i;
 | 
	
		
			
				|  |  | -  else if ((i = a->git_tag_len - b->git_tag_len))
 | 
	
		
			
				|  |  | -    return i;
 | 
	
		
			
				|  |  | -  else if (a->git_tag_len)
 | 
	
		
			
				|  |  | -    return fast_memcmp(a->git_tag, b->git_tag, a->git_tag_len);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  /* 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;
 | 
	
		
			
				|  |  | +     return 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#undef CMP
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  /** Return true iff versions <b>a</b> and <b>b</b> belong to the same series.
 |