diff --git a/doc/spec/dir-spec.txt b/doc/spec/dir-spec.txt index 343ae8e726..2301714938 100644 --- a/doc/spec/dir-spec.txt +++ b/doc/spec/dir-spec.txt @@ -704,13 +704,25 @@ $Id$ [Exactly once.] - The start of the Interval for this vote. + The start of the Interval for this vote. XXXX + + "fresh-until" SP YYYY-MM-DD SP HH:MM:SS NL + + [Exactly once.] + + XXXX "valid-until" SP YYYY-MM-DD SP HH:MM:SS NL [Exactly once.] - The end of the Interval for this vote, plus CONSENSUS_DELAY. + The end of the Interval for this vote. XXXX + + "voting-delay" SP VoteSeconds SP DistSeconds NL + + [Exactly once.] + + XXXX "client-versions" SP VersionList NL @@ -968,6 +980,7 @@ $Id$ The digest of the consensus being signed. "valid-after" SP YYYY-MM-DD SP HH:MM:SS NL + "fresh-until" SP YYYY-MM-DD SP HH:MM:SS NL "valid-until" SP YYYY-MM-DD SP HH:MM:SS NL [As in the consensus] diff --git a/src/or/Makefile.am b/src/or/Makefile.am index 497ec3e9be..1048540780 100644 --- a/src/or/Makefile.am +++ b/src/or/Makefile.am @@ -7,7 +7,8 @@ bin_PROGRAMS = tor tor_SOURCES = buffers.c circuitbuild.c circuitlist.c \ circuituse.c command.c config.c \ connection.c connection_edge.c connection_or.c control.c \ - cpuworker.c directory.c dirserv.c dns.c dnsserv.c hibernate.c main.c \ + cpuworker.c directory.c dirserv.c dirvote.c \ + dns.c dnsserv.c hibernate.c main.c \ onion.c policies.c relay.c rendcommon.c rendclient.c rendmid.c \ rendservice.c rephist.c router.c routerlist.c routerparse.c \ eventdns.c \ @@ -23,7 +24,8 @@ tor_LDADD = ../common/libor.a ../common/libor-crypto.a \ test_SOURCES = buffers.c circuitbuild.c circuitlist.c \ circuituse.c command.c config.c \ connection.c connection_edge.c connection_or.c control.c \ - cpuworker.c directory.c dirserv.c dns.c dnsserv.c hibernate.c main.c \ + cpuworker.c directory.c dirserv.c dirvote.c \ + dns.c dnsserv.c hibernate.c main.c \ onion.c policies.c relay.c rendcommon.c rendclient.c rendmid.c \ rendservice.c rephist.c router.c routerlist.c routerparse.c \ eventdns.c \ diff --git a/src/or/dirvote.c b/src/or/dirvote.c new file mode 100644 index 0000000000..413cc93640 --- /dev/null +++ b/src/or/dirvote.c @@ -0,0 +1,48 @@ +/* Copyright 2001-2004 Roger Dingledine. + * Copyright 2004-2007 Roger Dingledine, Nick Mathewson. */ +/* See LICENSE for licensing information */ +/* $Id$ */ +const char dirvote_c_id[] = + "$Id$"; + +#include "or.h" + +/** + * \file dirvote.c + **/ + +/** DOCDOC */ +void +networkstatus_vote_free(networkstatus_vote_t *ns) +{ + int i; + if (!ns) + return; + + tor_free(ns->client_versions); + tor_free(ns->server_versions); + if (ns->known_flags) { + for (i=0; ns->known_flags[i]; ++i) + tor_free(ns->known_flags[i]); + tor_free(ns->known_flags); + } + tor_free(ns->nickname); + tor_free(ns->address); + tor_free(ns->contact); + if (ns->cert) + authority_cert_free(ns->cert); + + if (ns->routerstatus_list) { + SMARTLIST_FOREACH(ns->routerstatus_list, vote_routerstatus_t *, rs, + { + tor_free(rs->version); + tor_free(rs); + }); + + smartlist_free(ns->routerstatus_list); + } + + memset(ns, 11, sizeof(*ns)); + tor_free(ns); +} + diff --git a/src/or/or.h b/src/or/or.h index 9680b87685..8001f2849b 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1294,11 +1294,11 @@ typedef struct networkstatus_t { } networkstatus_t; /** DOCDOC */ -typedef struct ns_vote_routerstatus_t { +typedef struct vote_routerstatus_t { routerstatus_t status; uint64_t flags; char *version; -} ns_vote_routerstatus_t; +} vote_routerstatus_t; /** DOCDOC */ typedef struct networkstatus_vote_t { @@ -1306,16 +1306,24 @@ typedef struct networkstatus_vote_t { time_t valid_after; time_t fresh_until; time_t valid_until; + int vote_seconds; + int dist_seconds; + char *client_versions; char *server_versions; - char **known_flags; + char **known_flags; /* NULL-terminated */ + + char *nickname; char identity_digest[DIGEST_LEN]; char *address; uint32_t addr; uint16_t dir_port; uint16_t or_port; + struct authority_cert_t *cert; + char *contact; + smartlist_t *routerstatus_list; } networkstatus_vote_t; @@ -2701,6 +2709,10 @@ int routerstatus_format_entry(char *buf, size_t buf_len, void dirserv_free_all(void); void cached_dir_decref(cached_dir_t *d); +/********************************* dirvote.c ************************/ + +void networkstatus_vote_free(networkstatus_vote_t *ns); + /********************************* dns.c ***************************/ int dns_init(void); @@ -3365,6 +3377,7 @@ void assert_addr_policy_ok(addr_policy_t *t); void dump_distinct_digest_count(int severity); networkstatus_t *networkstatus_parse_from_string(const char *s); +networkstatus_vote_t *networkstatus_parse_vote_from_string(const char *s); void authority_cert_free(authority_cert_t *cert); authority_cert_t *authority_cert_parse_from_string(const char *s, diff --git a/src/or/routerparse.c b/src/or/routerparse.c index fa30d86d1c..9aac94d1e4 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -67,7 +67,11 @@ typedef enum { K_DIR_KEY_CERTIFICATION, K_VOTE_STATUS, + K_VALID_AFTER, + K_FRESH_UNTIL, K_VALID_UNTIL, + K_VOTING_DELAY, + K_KNOWN_FLAGS, K_VOTE_DIGEST, K_CONSENSUS_DIGEST, @@ -110,7 +114,7 @@ typedef enum { } obj_syntax; #define AT_START 1 -#define AT_END 1 +#define AT_END 2 /** Determines the parsing rules for a single token type. */ typedef struct token_rule_t { @@ -152,9 +156,9 @@ typedef struct token_rule_t { /** An item that must appear exactly once */ #define T1(s,t,a,o) { s, t, a, o, 1, 1, 0 } /** An item that must appear exactly once, at the start of the document */ -#define T1_START(s,t,a,o) { s, t, a, o, 1, 1, 0, AT_START } +#define T1_START(s,t,a,o) { s, t, a, o, 1, 1, AT_START } /** An item that must appear exactly once, at the end of the document */ -#define T1_END(s,t,a,o) { s, t, a, o, 1, 1, 0, AT_END } +#define T1_END(s,t,a,o) { s, t, a, o, 1, 1, AT_END } /** An item that must appear one or more times */ #define T1N(s,t,a,o) { s, t, a, o, 1, INT_MAX, 0 } /** An item that must appear no more than once */ @@ -229,7 +233,7 @@ static token_rule_t netstatus_token_table[] = { T1( "contact", K_CONTACT, CONCAT_ARGS, NO_OBJ ), T1( "dir-signing-key", K_DIR_SIGNING_KEY, NO_ARGS, NEED_KEY_1024 ), T1( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), - T1( "network-status-version", K_NETWORK_STATUS_VERSION, + T1_START("network-status-version", K_NETWORK_STATUS_VERSION, GE(1), NO_OBJ ), T1( "dir-source", K_DIR_SOURCE, GE(3), NO_OBJ ), T01("dir-options", K_DIR_OPTIONS, ARGS, NO_OBJ ), @@ -283,16 +287,15 @@ static token_rule_t dir_key_certificate_table[] = { END_OF_TABLE }; -#if 0 -/* XXXX This stuff is commented out for now so we can avoid warnings about - * unused variables. */ - -static token_rule_t status_vote_table[] = { +static token_rule_t networkstatus_vote_token_table[] = { T1("network-status-version", K_NETWORK_STATUS_VERSION, GE(1), NO_OBJ ), T1("vote-status", K_VOTE_STATUS, GE(1), NO_OBJ ), T1("published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ), + T1("valid-after", K_VALID_AFTER, CONCAT_ARGS, NO_OBJ ), + T1("fresh-until", K_FRESH_UNTIL, CONCAT_ARGS, NO_OBJ ), T1("valid-until", K_VALID_UNTIL, CONCAT_ARGS, NO_OBJ ), + T1("voting-delay", K_VOTING_DELAY, GE(2), NO_OBJ ), T1("known-flags", K_KNOWN_FLAGS, CONCAT_ARGS, NO_OBJ ), T( "fingerprint", K_FINGERPRINT, CONCAT_ARGS, NO_OBJ ), @@ -309,12 +312,18 @@ static token_rule_t status_vote_table[] = { END_OF_TABLE }; +#if 0 +/* XXXX This stuff is commented out for now so we can avoid warnings about + * unused variables. */ + static token_rule_t status_consensus_table[] = { T1("network-status-version", K_NETWORK_STATUS_VERSION, GE(1), NO_OBJ ), T1("vote-status", K_VOTE_STATUS, GE(1), NO_OBJ ), - T1("published", K_PUBLISHED, CONCAT_ARGS, NO_OBJ ), + T1("valid-after", K_VALID_AFTER, CONCAT_ARGS, NO_OBJ ), + T1("fresh-until", K_FRESH_UNTIL, CONCAT_ARGS, NO_OBJ ), T1("valid-until", K_VALID_UNTIL, CONCAT_ARGS, NO_OBJ ), + T1("voting-delay", K_VOTING_DELAY, GE(2), NO_OBJ ), CERTIFICATE_MEMBERS @@ -335,13 +344,13 @@ static token_rule_t status_consensus_table[] = { END_OF_TABLE }; +#endif -static token_rule_t vote_footer_token_table[] = { - T01("consensus-digest", K_CONSENSUS_DIGEST, EQ(1), NO_OBJ ), +static token_rule_t networkstatus_vote_footer_token_table[] = { T( "directory-signature", K_DIRECTORY_SIGNATURE, GE(2), NEED_OBJ ), END_OF_TABLE }; -#endif + #undef T @@ -1450,7 +1459,7 @@ find_start_of_next_routerstatus(const char *s) static routerstatus_t * routerstatus_parse_entry_from_string(const char **s, smartlist_t *tokens, networkstatus_vote_t *vote, - uint64_t *flags_out) + vote_routerstatus_t *vote_rs) { const char *eos; routerstatus_t *rs = NULL; @@ -1458,7 +1467,7 @@ routerstatus_parse_entry_from_string(const char **s, smartlist_t *tokens, char timebuf[ISO_TIME_LEN+1]; struct in_addr in; tor_assert(tokens); - tor_assert(bool_eq(flags_out, vote)); + tor_assert(bool_eq(vote, vote_rs)); eos = find_start_of_next_routerstatus(*s); @@ -1473,7 +1482,11 @@ routerstatus_parse_entry_from_string(const char **s, smartlist_t *tokens, tok = find_first_by_keyword(tokens, K_R); tor_assert(tok); tor_assert(tok->n_args >= 8); - rs = tor_malloc_zero(sizeof(routerstatus_t)); + if (vote_rs) { + rs = &vote_rs->status; + } else { + rs = tor_malloc_zero(sizeof(routerstatus_t)); + } if (!is_legal_nickname(tok->args[0])) { log_warn(LD_DIR, @@ -1516,10 +1529,11 @@ routerstatus_parse_entry_from_string(const char **s, smartlist_t *tokens, tok = find_first_by_keyword(tokens, K_S); if (tok && vote) { int i, j; + vote_rs->flags = 0; for (i=0; i < tok->n_args; ++i) { for (j=0; vote->known_flags[j]; ++j) { if (!strcmp(tok->args[i], vote->known_flags[j])) { - *flags_out |= (1<flags |= (1<version_supports_begindir = tor_version_as_new_as(tok->args[0], "0.2.0.0-alpha-dev (r10070)"); } + if (vote_rs) { + vote_rs->version = tok->args[0]; + tok->args[0] = NULL; /* suppress free() */ + } } if (!strcasecmp(rs->nickname, UNNAMED_ROUTER_NICKNAME)) @@ -1568,7 +1586,7 @@ routerstatus_parse_entry_from_string(const char **s, smartlist_t *tokens, goto done; err: - if (rs) + if (rs && !vote_rs) routerstatus_free(rs); rs = NULL; done: @@ -1597,7 +1615,7 @@ _free_duplicate_routerstatus_entry(void *e) routerstatus_free(e); } -/** Given a versioned (v2 or later) network-status object in s, try to +/** Given a v2 network-status object in s, try to * parse it and return the result. Return NULL on failure. Check the * signature of the network status, but do not (yet) check the signing key for * authority. @@ -1629,7 +1647,12 @@ networkstatus_parse_from_string(const char *s) memcpy(ns->networkstatus_digest, ns_digest, DIGEST_LEN); tok = find_first_by_keyword(tokens, K_NETWORK_STATUS_VERSION); - tor_assert(tok); + tor_assert(tok && tok->n_args >= 1); + if (strcmp(tok->args[0], "2")) { + log_warn(LD_BUG, "Got a non-v2 networkstatus. Version was " + "%s", escaped(tok->args[0])); + goto err; + } tok = find_first_by_keyword(tokens, K_DIR_SOURCE); tor_assert(tok); @@ -1762,6 +1785,200 @@ networkstatus_parse_from_string(const char *s) return ns; } +/** DOCDOC */ +networkstatus_vote_t * +networkstatus_parse_vote_from_string(const char *s) +{ + smartlist_t *tokens = smartlist_create(); + smartlist_t *rs_tokens = NULL, *footer_tokens = NULL; + networkstatus_vote_t *ns = NULL; + char ns_digest[DIGEST_LEN], declared_identity[DIGEST_LEN], + declared_digest[DIGEST_LEN]; + const char *cert, *end_of_cert = NULL, *end_of_header, *end_of_footer; + directory_token_t *tok; + int ok; + struct in_addr in; + + if (router_get_networkstatus_v3_hash(s, ns_digest)) { + log_warn(LD_DIR, "Unable to compute digest of network-status"); + goto err; + } + + end_of_header = find_start_of_next_routerstatus(s); + if (tokenize_string(s, end_of_header, tokens, + networkstatus_vote_token_table)) { + log_warn(LD_DIR, "Error tokenizing network-status vote header."); + goto err; + } + + ns = tor_malloc_zero(sizeof(networkstatus_vote_t)); + if (!(cert = strstr(s, "\ndir-key-certificate-version"))) + goto err; + ++cert; + ns->cert = authority_cert_parse_from_string(cert, &end_of_cert); + if (!ns->cert || !end_of_cert || end_of_cert > end_of_header) + goto err; + + tok = find_first_by_keyword(tokens, K_VOTE_STATUS); + tor_assert(tok); + tor_assert(tok->n_args); + if (strcmp(tok->args[0], "vote")) { + log_warn(LD_DIR, "Unrecognized vote status %s in network-status vote.", + escaped(tok->args[0])); + goto err; + } + + tok = find_first_by_keyword(tokens, K_PUBLISHED); + if (parse_iso_time(tok->args[0], &ns->published)) + goto err; + + tok = find_first_by_keyword(tokens, K_VALID_AFTER); + if (parse_iso_time(tok->args[0], &ns->valid_after)) + goto err; + + tok = find_first_by_keyword(tokens, K_FRESH_UNTIL); + if (parse_iso_time(tok->args[0], &ns->fresh_until)) + goto err; + + tok = find_first_by_keyword(tokens, K_VALID_UNTIL); + if (parse_iso_time(tok->args[0], &ns->valid_until)) + goto err; + + tok = find_first_by_keyword(tokens, K_VOTING_DELAY); + tor_assert(tok->n_args >= 2); + ns->vote_seconds = + (int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &ok, NULL); + if (!ok) + goto err; + ns->dist_seconds = + (int) tor_parse_long(tok->args[0], 10, 0, INT_MAX, &ok, NULL); + if (!ok) + goto err; + + if ((tok = find_first_by_keyword(tokens, K_CLIENT_VERSIONS))) { + ns->client_versions = tok->args[0]; + tok->args[0] = NULL; + } + if ((tok = find_first_by_keyword(tokens, K_SERVER_VERSIONS))) { + ns->server_versions = tok->args[0]; + tok->args[0] = NULL; + } + + tok = find_first_by_keyword(tokens, K_KNOWN_FLAGS); + ns->known_flags = tor_malloc(sizeof(char*)*(tok->n_args+1)); + memcpy(ns->known_flags, tok->args, sizeof(char*)*(tok->n_args)); + ns->known_flags[tok->n_args] = NULL; + tok->n_args = 0; /* suppress free of args members, but not of args itself. */ + + tok = find_first_by_keyword(tokens, K_DIR_SOURCE); + tor_assert(tok); + tor_assert(tok->n_args >= 5); + ns->nickname = tor_strdup(tok->args[0]); + if (strlen(tok->args[1]) != HEX_DIGEST_LEN || + base16_decode(ns->identity_digest, sizeof(ns->identity_digest), + tok->args[1], HEX_DIGEST_LEN) < 0) { + log_warn(LD_DIR, "Error decoding identity digest %s in " + "network-status vote.", escaped(tok->args[1])); + goto err; + } + ns->address = tor_strdup(tok->args[2]); + if (!tor_inet_aton(tok->args[3], &in)) { + log_warn(LD_DIR, "Error decoding IP address %s in network-status vote.", + escaped(tok->args[3])); + goto err; + } + ns->dir_port = (uint64_t) + (int) tor_parse_long(tok->args[4], 10, 0, 65535, &ok, NULL); + if (!ok) + goto err; + + tok = find_first_by_keyword(tokens, K_CONTACT); + if (tok) { + ns->contact = tor_strdup(tok->args[0]); + } + + /* Parse routerstatus lines. */ + rs_tokens = smartlist_create(); + s = end_of_header; + ns->routerstatus_list = smartlist_create(); + while (!strcmpstart(s, "r ")) { + vote_routerstatus_t *rs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + if (routerstatus_parse_entry_from_string(&s, tokens, ns, rs)) + smartlist_add(ns->routerstatus_list, rs); + else { + tor_free(rs->version); + tor_free(rs); + } + } + + /* Parse footer; check signature. */ + footer_tokens = smartlist_create(); + end_of_footer = s + strlen(s); + if (tokenize_string(s, end_of_footer, footer_tokens, + networkstatus_vote_footer_token_table)) { + log_warn(LD_DIR, "Error tokenizing network-status vote footer."); + goto err; + } + if (!(tok = find_first_by_keyword(footer_tokens, K_DIRECTORY_SIGNATURE))) { + log_warn(LD_DIR, "No signature on network-status vote."); + goto err; + } + tor_assert(tok->n_args >= 2); + if (strlen(tok->args[0]) != HEX_DIGEST_LEN || + base16_decode(declared_identity, sizeof(ns->identity_digest), + tok->args[0], HEX_DIGEST_LEN) < 0) { + log_warn(LD_DIR, "Error decoding declared identity %s in " + "network-status vote.", escaped(tok->args[1])); + goto err; + } + if (strlen(tok->args[1]) != HEX_DIGEST_LEN || + base16_decode(declared_digest, sizeof(ns->identity_digest), + tok->args[1], HEX_DIGEST_LEN) < 0) { + log_warn(LD_DIR, "Error decoding declared digest %s in " + "network-status vote.", escaped(tok->args[1])); + goto err; + } + if (memcmp(declared_identity, ns->cert->cache_info.identity_digest, + DIGEST_LEN)) { + log_warn(LD_DIR, "Digest mismatch between declared and actual on " + "network-status vote."); + goto err; + } + if (memcmp(declared_digest, ns_digest, DIGEST_LEN)) { + log_warn(LD_DIR, "Digest mismatch between declared and actual on " + "network-status vote."); + goto err; + } + if (check_signature_token(ns_digest, tok, ns->cert->signing_key, 0, + "network-status vote")) + goto err; + + /* XXXX020 check dates for plausibility. ??? */ + + goto done; + err: + if (ns) + networkstatus_vote_free(ns); + ns = NULL; + done: + if (tokens) { + SMARTLIST_FOREACH(tokens, directory_token_t *, t, token_free(t)); + smartlist_free(tokens); + } + if (rs_tokens) { + SMARTLIST_FOREACH(rs_tokens, directory_token_t *, t, token_free(t)); + smartlist_free(rs_tokens); + } + if (footer_tokens) { + SMARTLIST_FOREACH(footer_tokens, directory_token_t *, t, token_free(t)); + smartlist_free(footer_tokens); + } + + + return ns; +} + + /** Parse the addr policy in the string s and return it. If * assume_action is nonnegative, then insert its action (ADDR_POLICY_ACCEPT or * ADDR_POLICY_REJECT) for items that specify no action.