/** * Find latest token structure that is anterior or equal to the remote version. */ static const struct tokkey * find_latest(const version_t *rver) { uint i; const struct tokkey *tk; const struct tokkey *result = NULL; for (i = 0; i < G_N_ELEMENTS(token_keys); i++) { tk = &token_keys[i]; if (version_build_cmp(&tk->ver, rver) > 0) break; result = tk; } return result; }
/** * Validate a base64-encoded version token `tokenb64' of `len' bytes. * The `ip' is given only for clock update operations. * * @returns error code, or TOK_OK if token is valid. */ tok_error_t tok_version_valid( const char *version, const char *tokenb64, int len, host_addr_t addr) { time_t now = tm_time(); time_t stamp; uint32 crc; const struct tokkey *tk; const struct tokkey *rtk; const struct tokkey *latest; uint idx; const char *key; SHA1Context ctx; char lvldigest[1024]; char token[TOKEN_VERSION_SIZE]; struct sha1 digest; version_t rver; char *end; int toklen; int lvllen; int lvlsize; uint i; end = strchr(tokenb64, ';'); /* After 25/02/2003 */ toklen = end ? (end - tokenb64) : len; /* * Verify token. */ if (toklen != TOKEN_BASE64_SIZE) return TOK_BAD_LENGTH; if (!base64_decode_into(tokenb64, toklen, token, TOKEN_VERSION_SIZE)) return TOK_BAD_ENCODING; stamp = (time_t) peek_be32(&token); /* * Use that stamp, whose precision is TOKEN_LIFE, to update our * clock skew if necessary. */ clock_update(stamp, TOKEN_LIFE, addr); if (ABS(stamp - clock_loc2gmt(now)) > TOKEN_CLOCK_SKEW) return TOK_BAD_STAMP; if (!version_fill(version, &rver)) /* Remote version */ return TOK_BAD_VERSION; tk = find_tokkey_version(&rver, stamp); /* The keys they used */ if (tk == NULL) return TOK_BAD_KEYS; idx = (uchar) token[6] & 0x1f; /* 5 bits for the index */ if (idx >= tk->count) return TOK_BAD_INDEX; key = tk->keys[idx]; SHA1Reset(&ctx); SHA1Input(&ctx, key, strlen(key)); SHA1Input(&ctx, token, 7); SHA1Input(&ctx, version, strlen(version)); SHA1Result(&ctx, &digest); if (0 != memcmp(&token[7], digest.data, SHA1_RAW_SIZE)) return TOK_INVALID; if (version_build_cmp(&rver, &tk->ver) < 0) return TOK_OLD_VERSION; if (end == NULL) return TOK_MISSING_LEVEL; latest = find_latest(&rver); if (latest == NULL) /* Unknown in our key set */ return TOK_OLD_VERSION; /* * Verify build. * * Build numbers were emitted when we switched to SVN on 2006-08-26 * and stopped being a monotonous increasing function when we switched * to git on 2011-09-11. */ if ( rver.timestamp >= 1156543200 && /* 2006-08-26 */ rver.timestamp < GIT_SWITCH /* 2011-09-11 */ ) { if (0 == rver.build) return TOK_MISSING_BUILD; if (rver.build < latest->ver.build) return TOK_WRONG_BUILD; } /* * Verify level. */ lvllen = len - toklen - 2; /* Forget about "; " */ end += 2; /* Skip "; " */ if (UNSIGNED(lvllen) >= sizeof(lvldigest) || lvllen <= 0) return TOK_BAD_LEVEL_LENGTH; if (lvllen & 0x3) return TOK_BAD_LEVEL_LENGTH; lvllen = base64_decode_into(end, lvllen, lvldigest, sizeof(lvldigest)); if (lvllen == 0 || (lvllen & 0x1)) return TOK_BAD_LEVEL_ENCODING; g_assert(lvllen >= 2); g_assert((lvllen & 0x1) == 0); /* * Only check the highest keys we can check. */ lvllen /= 2; /* # of keys held remotely */ lvlsize = G_N_ELEMENTS(token_keys) - (tk - token_keys); lvlsize = MIN(lvllen, lvlsize); g_assert(lvlsize >= 1); rtk = tk + (lvlsize - 1); /* Keys at that level */ crc = crc32_update(0, token, TOKEN_VERSION_SIZE); crc = tok_crc(crc, rtk); lvlsize--; /* Move to 0-based offset */ if (peek_be16(&lvldigest[2*lvlsize]) != crc) return TOK_INVALID_LEVEL; for (i = 0; i < G_N_ELEMENTS(token_keys); i++) { rtk = &token_keys[i]; if (rtk->ver.timestamp > rver.timestamp) { rtk--; /* `rtk' could not exist remotely */ break; } } if (lvlsize < rtk - tk) return TOK_SHORT_LEVEL; return TOK_OK; }
/** * Check version of servent, and if it's a gtk-gnutella more recent than we * are, record that fact and change the status bar. * * The `addr' is being passed solely for the tok_version_valid() call. * * @returns TRUE if we properly checked the version, FALSE if we got something * looking as gtk-gnutella but which failed the token-based sanity checks. */ bool version_check(const char *str, const char *token, const host_addr_t addr) { version_t their_version; version_ext_t their_version_ext; version_t *target_version; int cmp; const char *version; const char *end; bool extended; if (!version_parse(str, &their_version, &end)) return TRUE; /* Not gtk-gnutella, or unparseable */ /* * Check for extended version information (git commit ID, dirty status). */ ZERO(&their_version_ext); extended = version_ext_parse(end, &their_version_ext); if (!extended) { /* Structure could have been partially filled */ ZERO(&their_version_ext); } /* * Is their version a development one, or a release? */ if (their_version.tag == 'u') target_version = &last_dev_version; else target_version = &last_rel_version; cmp = version_cmp(target_version, &their_version); if (GNET_PROPERTY(version_debug) > 1) version_dump(str, &their_version, cmp == 0 ? "=" : cmp > 0 ? "-" : "+"); /* * Check timestamp. */ version_stamp(str, &their_version); their_version_ext.version = their_version; /* Struct copy */ if (GNET_PROPERTY(version_debug) > 3) g_debug("VERSION time=%d", (int) their_version.timestamp); /* * If version claims something older than TOKEN_START_DATE, then * there must be a token present. */ if (delta_time(their_version.timestamp, 0) >= TOKEN_START_DATE) { tok_error_t error; if (token == NULL) { if (GNET_PROPERTY(version_debug)) { g_debug("got GTKG vendor string \"%s\" without token!", str); } return FALSE; /* Can't be correct */ } error = tok_version_valid(str, token, strlen(token), addr); /* * Unfortunately, if our token has expired, we can no longer * validate the tokens of the remote peers, since they are using * a different set of keys. * * This means an expired GTKG will blindly trust well-formed remote * tokens at face value. But it's their fault, they should not run * an expired version! * --RAM, 2005-12-21 */ if (error == TOK_BAD_KEYS && tok_is_ancient(tm_time())) error = TOK_OK; /* Our keys have expired, cannot validate */ if (error != TOK_OK) { if (GNET_PROPERTY(version_debug)) { g_debug("vendor string \"%s\" [%s] has wrong token " "\"%s\": %s ", str, host_addr_to_string(addr), token, tok_strerror(error)); } return FALSE; } /* * OK, so now we know we can "trust" this version string as being * probably genuine. It makes sense to extract version information * out of it. */ } if (cmp > 0) /* We're more recent */ return TRUE; /* * If timestamp is greater and we were comparing against a stable * release, and cmp == 0, then this means an update in SVN about * a "released" version, probably alpha/beta. */ if ( cmp == 0 && (delta_time(their_version.timestamp, target_version->timestamp) > 0 || their_version.build > target_version->build) && target_version == &last_rel_version ) { if (GNET_PROPERTY(version_debug) > 3) g_debug("VERSION is a SVN update of a release"); if (version_build_cmp(&last_dev_version, &their_version) > 0) { if (GNET_PROPERTY(version_debug) > 3) g_debug("VERSION is less recent than latest dev we know"); return TRUE; } target_version = &last_dev_version; } /* * Their version is more recent, but is unstable -- only continue if * our version is also unstable. */ if (cmp < 0 && their_version.tag == 'u' && our_version.tag != 'u') return TRUE; if ( delta_time(their_version.timestamp, target_version->timestamp) < 0 || their_version.build <= target_version->build ) return TRUE; if ( delta_time(their_version.timestamp, our_version.timestamp) == 0 && their_version.build <= our_version.build ) return TRUE; /* * We found a more recent version than the last version seen. */ if (GNET_PROPERTY(version_debug) > 1) g_debug("more recent %s VERSION \"%s\"", target_version == &last_dev_version ? "dev" : "rel", str); *target_version = their_version; /* struct copy */ /* * Signal new version to user. * * Unless they run a development version, don't signal development * updates to them: they're probably not interested. */ version = version_ext_str(&their_version_ext, FALSE); /* No OS name */ g_message("more recent %s version of gtk-gnutella: %s", target_version == &last_dev_version ? "development" : "released", version); if (target_version == &last_rel_version) version_new_found(version, TRUE); else if (our_version.tag == 'u') version_new_found(version, FALSE); return TRUE; }