1 /* Copyright 2001-2003 Roger Dingledine, Matej Pfajfar. */
2 /* See LICENSE for licensing information */
8 * \brief Code to parse and validate router descriptors and directories.
13 /****************************************************************************/
15 extern or_options_t options
; /* command-line and config-file options */
17 /** Enumeration of possible token types. The ones starting with K_
18 * correspond to directory 'keywords'. _UNRECOGNIZED is for an
19 * unrecognized keyword; _ERR is an error in the tokenizing process,
20 * _EOF is an end-of-file marker, and _NIL is used to encode
25 K_DIRECTORY_SIGNATURE
,
26 K_RECOMMENDED_SOFTWARE
,
50 /** Structure to hold a single directory token.
52 * We parse a directory by breaking it into "tokens", each consisting
53 * of a keyword, a line full of arguments, and a binary object. The
54 * arguments and object are both optional, depending on the keyword
57 typedef struct directory_token_t
{
58 directory_keyword tp
; /**< Type of the token. */
59 int n_args
; /**< Number of elements in args */
60 char **args
; /**< Array of arguments from keyword line. */
61 char *object_type
; /**< -----BEGIN [object_type]-----*/
62 size_t object_size
; /**< Bytes in object_body */
63 char *object_body
; /**< Contents of object, base64-decoded. */
64 crypto_pk_env_t
*key
; /**< For public keys only. */
65 char *error
; /**< For _ERR tokens only. */
68 /* ********************************************************************** */
70 /** We use a table of rules to decide how to parse each token type. */
72 /** Rules for how many arguments a keyword can take. */
74 NO_ARGS
, /**< (1) no arguments, ever */
75 ARGS
, /**< (2) a list of arguments separated by spaces */
76 CONCAT_ARGS
, /**< or (3) the rest of the line, treated as a single argument. */
79 /** Rules for whether the keyword needs an object. */
81 NO_OBJ
, /**< (1) no object, ever */
82 NEED_OBJ
, /**< (2) object is required */
83 NEED_KEY
, /**< (3) object is required, and must be a public key. */
84 OBJ_OK
, /**< or (4) object is optional. */
87 /** Rules for where a keyword can appear. */
89 ANY
= 0, /**< Appears in router descriptor or in directory sections. */
90 DIR_ONLY
, /**< Appears only in directory. */
91 RTR_ONLY
, /**< Appears only in router descriptor or runningrouters */
94 /** Table mapping keywords to token value and to argument rules. */
96 char *t
; int v
; arg_syntax s
; obj_syntax os
; where_syntax ws
;
98 { "accept", K_ACCEPT
, ARGS
, NO_OBJ
, RTR_ONLY
},
99 { "directory-signature", K_DIRECTORY_SIGNATURE
, ARGS
, NEED_OBJ
,DIR_ONLY
},
100 { "reject", K_REJECT
, ARGS
, NO_OBJ
, RTR_ONLY
},
101 { "router", K_ROUTER
, ARGS
, NO_OBJ
, RTR_ONLY
},
102 { "recommended-software",K_RECOMMENDED_SOFTWARE
,ARGS
, NO_OBJ
, DIR_ONLY
},
103 { "signed-directory", K_SIGNED_DIRECTORY
, NO_ARGS
, NO_OBJ
, DIR_ONLY
},
104 { "signing-key", K_SIGNING_KEY
, NO_ARGS
, NEED_KEY
,RTR_ONLY
},
105 { "onion-key", K_ONION_KEY
, NO_ARGS
, NEED_KEY
,RTR_ONLY
},
106 { "router-signature", K_ROUTER_SIGNATURE
, NO_ARGS
, NEED_OBJ
,RTR_ONLY
},
107 { "running-routers", K_RUNNING_ROUTERS
, ARGS
, NO_OBJ
, DIR_ONLY
},
108 { "ports", K_PORTS
, ARGS
, NO_OBJ
, RTR_ONLY
},
109 { "bandwidth", K_BANDWIDTH
, ARGS
, NO_OBJ
, RTR_ONLY
},
110 { "platform", K_PLATFORM
, CONCAT_ARGS
, NO_OBJ
, RTR_ONLY
},
111 { "published", K_PUBLISHED
, CONCAT_ARGS
, NO_OBJ
, ANY
},
112 { "opt", K_OPT
, CONCAT_ARGS
, OBJ_OK
, ANY
},
113 { "dircacheport", K_DIRCACHEPORT
, ARGS
, NO_OBJ
, RTR_ONLY
},
114 { "contact", K_CONTACT
, CONCAT_ARGS
, NO_OBJ
, ANY
},
115 { "network-status", K_NETWORK_STATUS
, NO_ARGS
, NO_OBJ
, DIR_ONLY
},
116 { "uptime", K_UPTIME
, ARGS
, NO_OBJ
, RTR_ONLY
},
117 { "dir-signing-key", K_DIR_SIGNING_KEY
, ARGS
, OBJ_OK
, DIR_ONLY
},
118 { NULL
, -1, NO_ARGS
, NO_OBJ
, ANY
}
121 /* static function prototypes */
122 static int router_add_exit_policy(routerinfo_t
*router
,directory_token_t
*tok
);
123 static struct exit_policy_t
*router_parse_exit_policy(directory_token_t
*tok
);
124 static int router_get_hash_impl(const char *s
, char *digest
,
125 const char *start_str
, const char *end_str
);
126 static void token_free(directory_token_t
*tok
);
127 static smartlist_t
*find_all_exitpolicy(smartlist_t
*s
);
128 static directory_token_t
*find_first_by_keyword(smartlist_t
*s
,
129 directory_keyword keyword
);
130 static int tokenize_string(const char *start
, const char *end
,
131 smartlist_t
*out
, int is_dir
);
132 static directory_token_t
*get_next_token(const char **s
, where_syntax where
);
133 static int check_directory_signature(const char *digest
,
134 directory_token_t
*tok
,
135 crypto_pk_env_t
*pkey
,
136 crypto_pk_env_t
*declared_key
);
137 static crypto_pk_env_t
*find_dir_signing_key(const char *str
);
139 /** Set <b>digest</b> to the SHA-1 digest of the hash of the directory in
140 * <b>s</b>. Return 0 on success, nonzero on failure.
142 int router_get_dir_hash(const char *s
, char *digest
)
144 return router_get_hash_impl(s
,digest
,
145 "signed-directory","\ndirectory-signature");
148 /** Set <b>digest</b> to the SHA-1 digest of the hash of the first router in
149 * <b>s</b>. Return 0 on success, nonzero on failure.
151 int router_get_router_hash(const char *s
, char *digest
)
153 return router_get_hash_impl(s
,digest
,
154 "router ","\nrouter-signature");
157 /** Set <b>digest</b> to the SHA-1 digest of the hash of the running-routers
158 * string in <b>s</b>. Return 0 on success, nonzero on failure.
160 int router_get_runningrouters_hash(const char *s
, char *digest
)
162 return router_get_hash_impl(s
,digest
,
163 "network-status","\ndirectory-signature");
167 * Find the first instance of "recommended-software ...\n" at the start of
168 * a line; return a newly allocated string containing the "..." portion.
169 * Return NULL if no such instance was found.
172 get_recommended_software_from_directory(const char *str
)
174 #define REC "recommended-software "
175 const char *cp
= str
, *eol
;
176 size_t len
= strlen(REC
);
178 if (strcmpstart(str
, REC
)==0) {
181 cp
= strstr(str
, "\n"REC
);
186 eol
= strchr(cp
, '\n');
189 return tor_strndup(cp
, eol
-cp
);
193 /** Return 1 if <b>myversion</b> is not in <b>versionlist</b>, and if at least
194 * one version of Tor on <b>versionlist</b> is newer than <b>myversion</b>.
195 * Otherwise return 0.
196 * (versionlist is a comma-separated list of version strings,
197 * optionally prefixed with "Tor". Versions that can't be parsed are
199 /* static */ int is_obsolete_version(const char *myversion
,
200 const char *versionlist
) {
202 tor_version_t mine
, other
;
203 int found_newer
= 0, r
, ret
;
204 static int warned_too_new
=0;
205 smartlist_t
*version_sl
;
209 log_fn(LOG_DEBUG
,"checking '%s' in '%s'.", myversion
, versionlist
);
211 if (tor_version_parse(myversion
, &mine
)) {
212 log_fn(LOG_ERR
, "I couldn't parse my own version (%s)", myversion
);
215 version_sl
= smartlist_create();
216 smartlist_split_string(version_sl
, versionlist
, ",", SPLIT_SKIP_SPACE
, 0);
218 SMARTLIST_FOREACH(version_sl
, const char *, cp
, {
219 if (!strcmpstart(cp
, "Tor "))
222 if (tor_version_parse(cp
, &other
)) {
223 /* Couldn't parse other; it can't be a match. */
225 r
= tor_version_compare(&mine
, &other
);
236 if (!warned_too_new
) {
237 log_fn(LOG_WARN
, "This version of Tor (%s) is newer than any on the recommended list (%s)",
238 myversion
, versionlist
);
247 SMARTLIST_FOREACH(version_sl
, char *, version
, tor_free(version
));
248 smartlist_free(version_sl
);
252 /* Return 0 if myversion is supported; else log a message and return
253 * -1 (or exit if ignoreversions is false) */
254 int check_software_version_against_directory(const char *directory
,
258 v
= get_recommended_software_from_directory(directory
);
260 log_fn(LOG_WARN
, "No recommended-versions string found in directory");
263 if (!is_obsolete_version(VERSION
, v
)) {
267 log(ignoreversion
? LOG_WARN
: LOG_ERR
,
268 "You are running Tor version %s, which will not work with this network.\n"
270 VERSION
, strchr(v
,',') ? "one of " : "", v
);
274 log(LOG_WARN
, "IgnoreVersion is set. If it breaks, we told you so.");
280 return -1; /* never reached */
284 /** Parse a directory from <b>str</b> and, when done, store the
285 * resulting routerlist in *<b>dest</b>, freeing the old value if necessary.
286 * If <b>pkey</b> is provided, we check the directory signature with pkey.
288 int /* Should be static; exposed for unit tests */
289 router_parse_routerlist_from_directory(const char *str
,
291 crypto_pk_env_t
*pkey
,
294 directory_token_t
*tok
;
295 char digest
[DIGEST_LEN
];
296 routerlist_t
*new_dir
= NULL
;
297 char *versions
= NULL
;
298 smartlist_t
*good_nickname_list
= NULL
;
301 const char *end
, *cp
;
302 smartlist_t
*tokens
= NULL
;
303 char dirnickname
[MAX_NICKNAME_LEN
+1];
304 crypto_pk_env_t
*declared_key
= NULL
;
306 if (router_get_dir_hash(str
, digest
)) {
307 log_fn(LOG_WARN
, "Unable to compute digest of directory");
310 log_fn(LOG_DEBUG
,"Received directory hashes to %s",hex_str(digest
,4));
312 /* Check signature first, before we try to tokenize. */
314 while (cp
&& (end
= strstr(cp
+1, "\ndirectory-signature")))
316 if (cp
== str
|| !cp
) {
317 log_fn(LOG_WARN
, "No signature found on directory."); goto err
;
320 tokens
= smartlist_create();
321 if (tokenize_string(cp
,strchr(cp
,'\0'),tokens
,1)) {
322 log_fn(LOG_WARN
, "Error tokenizing directory signature"); goto err
;
324 if (smartlist_len(tokens
) != 1) {
325 log_fn(LOG_WARN
, "Unexpected number of tokens in signature"); goto err
;
327 tok
=smartlist_get(tokens
,0);
328 if(tok
->tp
!= K_DIRECTORY_SIGNATURE
) {
329 log_fn(LOG_WARN
,"Expected a single directory signature"); goto err
;
331 declared_key
= find_dir_signing_key(str
);
332 if (check_directory_signature(digest
, tok
, pkey
, declared_key
)<0)
335 /* now we know tok->n_args == 1, so it's safe to access tok->args[0] */
336 strlcpy(dirnickname
, tok
->args
[0], sizeof(dirnickname
));
338 SMARTLIST_FOREACH(tokens
, directory_token_t
*, tok
, token_free(tok
));
339 smartlist_free(tokens
);
342 /* Now that we know the signature is okay, check the version. */
344 check_software_version_against_directory(str
, options
.IgnoreVersion
);
346 /* Now try to parse the first part of the directory. */
347 if ((end
= strstr(str
,"\nrouter "))) {
349 } else if ((end
= strstr(str
, "\ndirectory-signature"))) {
352 end
= str
+ strlen(str
);
355 tokens
= smartlist_create();
356 if (tokenize_string(str
,end
,tokens
,1)) {
357 log_fn(LOG_WARN
, "Error tokenizing directory"); goto err
;
359 if (smartlist_len(tokens
) < 1) {
360 log_fn(LOG_WARN
, "Impossibly short directory header"); goto err
;
362 if ((tok
= find_first_by_keyword(tokens
, _UNRECOGNIZED
))) {
363 log_fn(LOG_WARN
, "Unrecognized keyword \"%s\" in directory header; can't parse directory.",
368 tok
= smartlist_get(tokens
,0);
369 if (tok
->tp
!= K_SIGNED_DIRECTORY
) {
370 log_fn(LOG_WARN
, "Directory doesn't start with signed-directory.");
374 if (!(tok
= find_first_by_keyword(tokens
, K_PUBLISHED
))) {
375 log_fn(LOG_WARN
, "Missing published time on directory.");
378 tor_assert(tok
->n_args
== 1);
380 if (parse_iso_time(tok
->args
[0], &published_on
) < 0) {
384 if (!(tok
= find_first_by_keyword(tokens
, K_RECOMMENDED_SOFTWARE
))) {
385 log_fn(LOG_WARN
, "Missing recommended-software line from directory.");
388 if (tok
->n_args
!= 1) {
389 log_fn(LOG_WARN
, "Invalid recommended-software line"); goto err
;
391 versions
= tor_strdup(tok
->args
[0]);
393 if (!(tok
= find_first_by_keyword(tokens
, K_RUNNING_ROUTERS
))) {
394 log_fn(LOG_WARN
, "Missing running-routers line from directory.");
398 good_nickname_list
= smartlist_create();
399 for (i
=0; i
<tok
->n_args
; ++i
) {
400 smartlist_add(good_nickname_list
, tok
->args
[i
]);
402 tok
->n_args
= 0; /* Don't free the strings in good_nickname_list yet. */
404 /* Read the router list from s, advancing s up past the end of the last
407 if (router_parse_list_from_string(&str
, &new_dir
,
408 good_nickname_list
, published_on
)) {
409 log_fn(LOG_WARN
, "Error reading routers from directory");
413 new_dir
->software_versions
= versions
; versions
= NULL
;
414 new_dir
->published_on
= published_on
;
416 SMARTLIST_FOREACH(tokens
, directory_token_t
*, tok
, token_free(tok
));
417 smartlist_free(tokens
);
420 /* Determine if my routerinfo is considered verified. */
422 static int have_warned_about_unverified_status
= 0;
423 routerinfo_t
*me
= router_get_my_routerinfo();
425 if(router_update_status_from_smartlist(me
, published_on
,
426 good_nickname_list
)==1 &&
427 me
->is_verified
== 0 && !have_warned_about_unverified_status
) {
428 log_fn(LOG_WARN
,"Dirserver %s lists your server as unverified. Please consider sending your identity fingerprint to the tor-ops.", dirnickname
);
429 have_warned_about_unverified_status
= 1;
435 routerlist_free(*dest
);
443 routerlist_free(new_dir
);
446 if (declared_key
) crypto_free_pk_env(declared_key
);
448 SMARTLIST_FOREACH(tokens
, directory_token_t
*, tok
, token_free(tok
));
449 smartlist_free(tokens
);
451 if (good_nickname_list
) {
452 SMARTLIST_FOREACH(good_nickname_list
, char *, n
, tor_free(n
));
453 smartlist_free(good_nickname_list
);
459 router_parse_runningrouters(const char *str
)
461 char digest
[DIGEST_LEN
];
462 running_routers_t
*new_list
= NULL
;
463 directory_token_t
*tok
;
466 crypto_pk_env_t
*declared_key
= NULL
;
467 smartlist_t
*tokens
= NULL
;
469 if (router_get_runningrouters_hash(str
, digest
)) {
470 log_fn(LOG_WARN
, "Unable to compute digest of directory");
473 tokens
= smartlist_create();
474 if (tokenize_string(str
,str
+strlen(str
),tokens
,1)) {
475 log_fn(LOG_WARN
, "Error tokenizing directory"); goto err
;
477 if ((tok
= find_first_by_keyword(tokens
, _UNRECOGNIZED
))) {
478 log_fn(LOG_WARN
, "Unrecognized keyword \"%s\"; can't parse running-routers",
482 tok
= smartlist_get(tokens
,0);
483 if (tok
->tp
!= K_NETWORK_STATUS
) {
484 log_fn(LOG_WARN
, "Network-status starts with wrong token");
488 if (!(tok
= find_first_by_keyword(tokens
, K_PUBLISHED
))) {
489 log_fn(LOG_WARN
, "Missing published time on directory.");
492 tor_assert(tok
->n_args
== 1);
493 if (parse_iso_time(tok
->args
[0], &published_on
) < 0) {
497 if (!(tok
= find_first_by_keyword(tokens
, K_RUNNING_ROUTERS
))) {
498 log_fn(LOG_WARN
, "Missing running-routers line from directory.");
502 new_list
= tor_malloc_zero(sizeof(running_routers_t
));
503 new_list
->published_on
= published_on
;
504 new_list
->running_routers
= smartlist_create();
505 for (i
=0;i
<tok
->n_args
;++i
) {
506 smartlist_add(new_list
->running_routers
, tok
->args
[i
]);
509 if (!(tok
= find_first_by_keyword(tokens
, K_DIRECTORY_SIGNATURE
))) {
510 log_fn(LOG_WARN
, "Missing signature on directory");
513 declared_key
= find_dir_signing_key(str
);
514 if (check_directory_signature(digest
, tok
, NULL
, declared_key
) < 0)
519 running_routers_free(new_list
);
522 if (declared_key
) crypto_free_pk_env(declared_key
);
524 SMARTLIST_FOREACH(tokens
, directory_token_t
*, tok
, token_free(tok
));
525 smartlist_free(tokens
);
530 /** Given a directory or running-routers string in <b>str</b>, try to
531 * find the its dir-signing-key token (if any). If this token is
532 * present, extract and return the key. Return NULL on failure. */
533 static crypto_pk_env_t
*find_dir_signing_key(const char *str
)
536 directory_token_t
*tok
;
537 crypto_pk_env_t
*key
= NULL
;
539 /* Is there a dir-signing-key in the directory? */
540 cp
= strstr(str
, "\nopt dir-signing-key");
542 cp
= strstr(str
, "\ndir-signing-key");
545 ++cp
; /* Now cp points to the start of the token. */
547 tok
= get_next_token(&cp
, DIR_ONLY
);
549 log_fn(LOG_WARN
, "Unparseable dir-signing-key token");
552 if (tok
->tp
!= K_DIR_SIGNING_KEY
) {
553 log_fn(LOG_WARN
, "Dir-signing-key token did not parse as expected");
559 tok
->key
= NULL
; /* steal reference. */
560 } else if (tok
->n_args
>= 1) {
561 key
= crypto_pk_DER64_decode_public_key(tok
->args
[0]);
563 log_fn(LOG_WARN
, "Unparseable dir-signing-key argument");
567 log_fn(LOG_WARN
, "Dir-signing-key token contained no key");
575 /** Return true iff <b>key</b> is allowed to sign directories.
577 static int dir_signing_key_is_trusted(crypto_pk_env_t
*key
)
579 char digest
[DIGEST_LEN
];
581 if (crypto_pk_get_digest(key
, digest
) < 0) {
582 log_fn(LOG_WARN
, "Error computing dir-signing-key digest");
585 if(!router_digest_is_trusted_dir(digest
)) {
586 log_fn(LOG_WARN
, "Listed dir-signing-key is not trusted");
592 /** Check whether the K_DIRECTORY_SIGNATURE token in <b>tok</b> has a
593 * good signature for <b>digest</b>.
595 * If <b>declared_key</b> is set, the directory has declared what key
596 * was used to sign it, so we will use that key only if it is an
597 * authoritative directory signing key.
599 * Otherwise, try to look up the router whose nickname is given in the
600 * directory-signature token. If this fails, or the named router is
601 * not authoritative, try to use pkey.
603 * (New callers should always use <b>declared_key</b> when possible;
604 * <b>pkey is only for debugging.)
606 static int check_directory_signature(const char *digest
,
607 directory_token_t
*tok
,
608 crypto_pk_env_t
*pkey
,
609 crypto_pk_env_t
*declared_key
)
611 char signed_digest
[PK_BYTES
];
613 crypto_pk_env_t
*_pkey
= NULL
;
615 if (tok
->n_args
!= 1) {
616 log_fn(LOG_WARN
, "Too many or too few arguments to directory-signature");
621 if (dir_signing_key_is_trusted(declared_key
))
622 _pkey
= declared_key
;
625 r
= router_get_by_nickname(tok
->args
[0]);
626 log_fn(LOG_DEBUG
, "Got directory signed by %s", tok
->args
[0]);
627 if (r
&& r
->is_trusted_dir
) {
628 _pkey
= r
->identity_pkey
;
629 } else if (!r
&& pkey
) {
630 /* pkey provided for debugging purposes. */
633 log_fn(LOG_WARN
, "Directory was signed by unrecognized server %s",
636 } else if (r
&& !r
->is_trusted_dir
) {
637 log_fn(LOG_WARN
, "Directory was signed by non-trusted server %s",
643 if (strcmp(tok
->object_type
, "SIGNATURE") || tok
->object_size
!= 128) {
644 log_fn(LOG_WARN
, "Bad object type or length on directory signature");
650 if (crypto_pk_public_checksig(_pkey
, tok
->object_body
, 128, signed_digest
)
652 log_fn(LOG_WARN
, "Error reading directory: invalid signature.");
655 log_fn(LOG_DEBUG
,"Signed directory hash starts %s", hex_str(signed_digest
,4));
656 if (memcmp(digest
, signed_digest
, 20)) {
657 log_fn(LOG_WARN
, "Error reading directory: signature does not match.");
663 /** Given a string *<b>s</b> containing a concatenated sequence of router
664 * descriptors, parses them and stores the result in *<b>dest</b>. If
665 * good_nickname_list is provided, then routers are marked as
666 * running/nonrunning and verified/unverified based on their status in the
667 * list. Otherwise, all routers are marked running and verified. Advances
668 * *s to a point immediately following the last router entry. Returns 0 on
669 * success and -1 on failure.
672 router_parse_list_from_string(const char **s
, routerlist_t
**dest
,
673 smartlist_t
*good_nickname_list
,
676 routerinfo_t
*router
;
677 smartlist_t
*routers
;
682 routers
= smartlist_create();
685 *s
= eat_whitespace(*s
);
686 /* Don't start parsing the rest of *s unless it contains a router. */
687 if (strcmpstart(*s
, "router ")!=0)
689 if ((end
= strstr(*s
+1, "\nrouter "))) {
691 } else if ((end
= strstr(*s
+1, "\ndirectory-signature"))) {
697 router
= router_parse_entry_from_string(*s
, end
);
700 log_fn(LOG_WARN
, "Error reading router; skipping");
704 if (good_nickname_list
) {
705 router_update_status_from_smartlist(router
, published_on
,
708 router
->is_running
= 1; /* start out assuming all dirservers are up */
709 router
->is_verified
= 1;
710 router
->status_set_at
= time(NULL
);
712 smartlist_add(routers
, router
);
713 log_fn(LOG_DEBUG
,"just added router #%d.",smartlist_len(routers
));
717 routerlist_free(*dest
);
718 *dest
= tor_malloc(sizeof(routerlist_t
));
719 (*dest
)->routers
= routers
;
720 (*dest
)->software_versions
= NULL
;
726 /** Helper function: reads a single router entry from *<b>s</b> ...
727 * *<b>end</b>. Mallocs a new router and returns it if all goes well, else
730 routerinfo_t
*router_parse_entry_from_string(const char *s
,
732 routerinfo_t
*router
= NULL
;
733 char signed_digest
[128];
735 smartlist_t
*tokens
= NULL
, *exit_policy_tokens
= NULL
;
736 directory_token_t
*tok
;
738 int ports_set
, bw_set
;
744 if (router_get_router_hash(s
, digest
) < 0) {
745 log_fn(LOG_WARN
, "Couldn't compute router hash.");
748 tokens
= smartlist_create();
749 if (tokenize_string(s
,end
,tokens
,0)) {
750 log_fn(LOG_WARN
, "Error tokeninzing router descriptor."); goto err
;
753 if (smartlist_len(tokens
) < 2) {
754 log_fn(LOG_WARN
, "Impossibly short router descriptor.");
757 if ((tok
= find_first_by_keyword(tokens
, _UNRECOGNIZED
))) {
758 log_fn(LOG_WARN
, "Unrecognized keyword \"%s\"; skipping descriptor.",
763 tok
= smartlist_get(tokens
,0);
764 if (tok
->tp
!= K_ROUTER
) {
765 log_fn(LOG_WARN
,"Entry does not start with \"router\"");
769 router
= tor_malloc_zero(sizeof(routerinfo_t
));
770 router
->onion_pkey
= router
->identity_pkey
= NULL
;
771 ports_set
= bw_set
= 0;
773 if (tok
->n_args
== 2 || tok
->n_args
== 5 || tok
->n_args
== 6) {
774 router
->nickname
= tor_strdup(tok
->args
[0]);
775 if (!is_legal_nickname(router
->nickname
)) {
776 log_fn(LOG_WARN
,"Router nickname is invalid");
779 router
->address
= tor_strdup(tok
->args
[1]);
782 if (tok
->n_args
>= 5) {
783 router
->or_port
= (uint16_t) tor_parse_long(tok
->args
[2],10,0,65535,NULL
,NULL
);
784 router
->socks_port
= (uint16_t) tor_parse_long(tok
->args
[3],10,0,65535,NULL
,NULL
);
785 router
->dir_port
= (uint16_t) tor_parse_long(tok
->args
[4],10,0,65535,NULL
,NULL
);
789 log_fn(LOG_WARN
,"Wrong # of arguments to \"router\" (%d)",tok
->n_args
);
793 tok
= find_first_by_keyword(tokens
, K_PORTS
);
794 if (tok
&& ports_set
) {
795 log_fn(LOG_WARN
,"Redundant ports line");
798 if (tok
->n_args
!= 3) {
799 log_fn(LOG_WARN
,"Wrong # of arguments to \"ports\"");
802 router
->or_port
= tor_parse_long(tok
->args
[0],10,0,65535,NULL
,NULL
);
803 router
->socks_port
= tor_parse_long(tok
->args
[1],10,0,65535,NULL
,NULL
);
804 router
->dir_port
= tor_parse_long(tok
->args
[2],10,0,65535,NULL
,NULL
);
808 tok
= find_first_by_keyword(tokens
, K_DIRCACHEPORT
);
810 if (router
->dir_port
)
811 log_fn(LOG_WARN
,"Redundant dircacheport line");
812 if (tok
->n_args
!= 1) {
813 log_fn(LOG_WARN
,"Wrong # of arguments to \"dircacheport\"");
816 router
->dir_port
= tor_parse_long(tok
->args
[0],10,1,65535,NULL
,NULL
);
819 tok
= find_first_by_keyword(tokens
, K_BANDWIDTH
);
821 log_fn(LOG_WARN
,"Redundant bandwidth line");
824 /* XXX set this to "< 3" once 0.0.7 is obsolete */
825 if (tok
->n_args
< 2) {
826 log_fn(LOG_WARN
,"Not enough arguments to \"bandwidth\"");
829 router
->bandwidthrate
= tor_parse_long(tok
->args
[0],10,0,INT_MAX
,NULL
,NULL
);
830 router
->bandwidthburst
= tor_parse_long(tok
->args
[1],10,0,INT_MAX
,NULL
,NULL
);
832 router
->bandwidthcapacity
= tor_parse_long(tok
->args
[2],10,0,INT_MAX
,NULL
,NULL
);
836 if ((tok
= find_first_by_keyword(tokens
, K_UPTIME
))) {
837 if (tok
->n_args
!= 1) {
838 log_fn(LOG_WARN
, "Unrecognized number of args on K_UPTIME; skipping.");
840 router
->uptime
= tor_parse_long(tok
->args
[0],10,0,LONG_MAX
,NULL
,NULL
);
844 if (!(tok
= find_first_by_keyword(tokens
, K_PUBLISHED
))) {
845 log_fn(LOG_WARN
, "Missing published time"); goto err
;
847 tor_assert(tok
->n_args
== 1);
848 if (parse_iso_time(tok
->args
[0], &router
->published_on
) < 0)
851 if (!(tok
= find_first_by_keyword(tokens
, K_ONION_KEY
))) {
852 log_fn(LOG_WARN
, "Missing onion key"); goto err
;
854 /* XXX Check key length */
855 router
->onion_pkey
= tok
->key
;
856 tok
->key
= NULL
; /* Prevent free */
858 if (!(tok
= find_first_by_keyword(tokens
, K_SIGNING_KEY
))) {
859 log_fn(LOG_WARN
, "Missing identity key"); goto err
;
861 /* XXX Check key length */
862 router
->identity_pkey
= tok
->key
;
863 tok
->key
= NULL
; /* Prevent free */
864 if (crypto_pk_get_digest(router
->identity_pkey
,router
->identity_digest
)){
865 log_fn(LOG_WARN
, "Couldn't calculate key digest"); goto err
;
868 if ((tok
= find_first_by_keyword(tokens
, K_PLATFORM
))) {
869 router
->platform
= tor_strdup(tok
->args
[0]);
872 exit_policy_tokens
= find_all_exitpolicy(tokens
);
873 SMARTLIST_FOREACH(exit_policy_tokens
, directory_token_t
*, t
,
874 if (router_add_exit_policy(router
,t
)<0) {
875 log_fn(LOG_WARN
,"Error in exit policy"); goto err
;}
878 if (!(tok
= find_first_by_keyword(tokens
, K_ROUTER_SIGNATURE
))) {
879 log_fn(LOG_WARN
, "Missing router signature"); goto err
;
881 if (strcmp(tok
->object_type
, "SIGNATURE") || tok
->object_size
!= 128) {
882 log_fn(LOG_WARN
, "Bad object type or length on router signature");
885 if ((t
=crypto_pk_public_checksig(router
->identity_pkey
, tok
->object_body
,
886 128, signed_digest
)) != 20) {
887 log_fn(LOG_WARN
, "Invalid signature %d",t
); goto err
;
889 if (memcmp(digest
, signed_digest
, 20)) {
890 log_fn(LOG_WARN
, "Mismatched signature"); goto err
;
894 log_fn(LOG_WARN
,"No ports declared; failing."); goto err
;
897 log_fn(LOG_WARN
,"No bandwidth declared; failing."); goto err
;
899 if(!router
->or_port
) {
900 log_fn(LOG_WARN
,"or_port unreadable or 0. Failing.");
903 if (!router
->bandwidthrate
) {
904 log_fn(LOG_WARN
,"bandwidthrate unreadable or 0. Failing.");
907 if (!router
->platform
) {
908 router
->platform
= tor_strdup("<unknown>");
911 log_fn(LOG_DEBUG
,"or_port %d, socks_port %d, dir_port %d, bandwidthrate %u, bandwidthburst %u.",
912 router
->or_port
, router
->socks_port
, router
->dir_port
,
913 (unsigned) router
->bandwidthrate
, (unsigned) router
->bandwidthburst
);
920 routerinfo_free(router
);
924 SMARTLIST_FOREACH(tokens
, directory_token_t
*, tok
, token_free(tok
));
925 smartlist_free(tokens
);
927 if (exit_policy_tokens
) {
928 smartlist_free(exit_policy_tokens
);
933 /** Parse the exit policy in the string <b>s</b> and return it.
935 struct exit_policy_t
*
936 router_parse_exit_policy_from_string(const char *s
)
938 directory_token_t
*tok
= NULL
;
941 struct exit_policy_t
*r
;
944 /* *s might not end with \n, so we need to extend it with one. */
946 cp
= tmp
= tor_malloc(len
+2);
947 for (idx
= 0; idx
< len
; ++idx
) {
948 tmp
[idx
] = tolower(s
[idx
]);
952 tok
= get_next_token(&cp
, RTR_ONLY
);
953 if (tok
->tp
== _ERR
) {
954 log_fn(LOG_WARN
, "Error reading exit policy: %s", tok
->error
);
957 if (tok
->tp
!= K_ACCEPT
&& tok
->tp
!= K_REJECT
) {
958 log_fn(LOG_WARN
, "Expected 'accept' or 'reject'.");
962 /* Now that we've gotten an exit policy, add it to the router. */
963 r
= router_parse_exit_policy(tok
);
973 int router_add_exit_policy_from_string(routerinfo_t
*router
, const char *s
)
975 struct exit_policy_t
*newe
, *tmpe
;
976 newe
= router_parse_exit_policy_from_string(s
);
979 for (tmpe
= router
->exit_policy
; tmpe
; tmpe
=tmpe
->next
)
987 static int router_add_exit_policy(routerinfo_t
*router
,directory_token_t
*tok
)
989 struct exit_policy_t
*newe
, **tmpe
;
990 newe
= router_parse_exit_policy(tok
);
993 for (tmpe
= &router
->exit_policy
; *tmpe
; tmpe
=&((*tmpe
)->next
))
1000 /** Given a K_ACCEPT or K_REJECT token and a router, create and return
1001 * a new exit_policy_t corresponding to the token. */
1002 static struct exit_policy_t
*
1003 router_parse_exit_policy(directory_token_t
*tok
) {
1005 struct exit_policy_t
*newe
;
1007 char *arg
, *address
, *mask
, *port
, *endptr
;
1010 tor_assert(tok
->tp
== K_REJECT
|| tok
->tp
== K_ACCEPT
);
1012 if (tok
->n_args
!= 1)
1016 newe
= tor_malloc_zero(sizeof(struct exit_policy_t
));
1018 newe
->string
= tor_malloc(8+strlen(arg
));
1019 if (tok
->tp
== K_REJECT
) {
1020 strcpy(newe
->string
, "reject ");
1021 newe
->policy_type
= EXIT_POLICY_REJECT
;
1023 strcpy(newe
->string
, "accept ");
1024 newe
->policy_type
= EXIT_POLICY_ACCEPT
;
1026 strcat(newe
->string
, arg
); /* can't overflow */
1029 mask
= strchr(arg
,'/');
1030 port
= strchr(mask
?mask
:arg
,':');
1031 /* Break 'arg' into separate strings. 'arg' was already strdup'd by
1032 * _router_get_next_token, so it's safe to modify.
1039 if (strcmp(address
, "*") == 0) {
1041 } else if (tor_inet_aton(address
, &in
) != 0) {
1042 newe
->addr
= ntohl(in
.s_addr
);
1044 log_fn(LOG_WARN
, "Malformed IP %s in exit policy; rejecting.",
1046 goto policy_read_failed
;
1049 if (strcmp(address
, "*") == 0)
1052 newe
->msk
= 0xFFFFFFFFu
;
1055 bits
= (int) strtol(mask
, &endptr
, 10);
1057 /* strtol handled the whole mask. */
1058 if (bits
< 0 || bits
> 32) {
1059 log_fn(LOG_WARN
, "Bad number of mask bits on exit policy; rejecting.");
1060 goto policy_read_failed
;
1062 newe
->msk
= ~((1<<(32-bits
))-1);
1063 } else if (tor_inet_aton(mask
, &in
) != 0) {
1064 newe
->msk
= ntohl(in
.s_addr
);
1066 log_fn(LOG_WARN
, "Malformed mask %s on exit policy; rejecting.",
1068 goto policy_read_failed
;
1071 if (!port
|| strcmp(port
, "*") == 0) {
1073 newe
->prt_max
= 65535;
1076 newe
->prt_min
= (uint16_t) tor_parse_long(port
, 10, 1, 65535,
1078 if (*endptr
== '-') {
1081 newe
->prt_max
= (uint16_t) tor_parse_long(port
, 10, 1, 65535, NULL
,
1083 if (*endptr
|| !newe
->prt_max
) {
1084 log_fn(LOG_WARN
, "Malformed port %s on exit policy; rejecting.",
1087 } else if (*endptr
|| !newe
->prt_min
) {
1088 log_fn(LOG_WARN
, "Malformed port %s on exit policy; rejecting.",
1090 goto policy_read_failed
;
1092 newe
->prt_max
= newe
->prt_min
;
1094 if (newe
->prt_min
> newe
->prt_max
) {
1095 log_fn(LOG_WARN
,"Insane port range on exit policy; rejecting.");
1096 goto policy_read_failed
;
1100 in
.s_addr
= htonl(newe
->addr
);
1101 address
= tor_strdup(inet_ntoa(in
));
1102 in
.s_addr
= htonl(newe
->msk
);
1103 log_fn(LOG_DEBUG
,"%s %s/%s:%d-%d",
1104 newe
->policy_type
== EXIT_POLICY_REJECT
? "reject" : "accept",
1105 address
, inet_ntoa(in
), newe
->prt_min
, newe
->prt_max
);
1112 tor_assert(newe
->string
);
1113 log_fn(LOG_WARN
,"Couldn't parse line '%s'. Dropping", newe
->string
);
1114 tor_free(newe
->string
);
1120 * Low-level tokenizer for router descriptors and directories.
1123 /** Free all resources allocated for <b>tok</b> */
1125 token_free(directory_token_t
*tok
)
1130 for (i
= 0; i
< tok
->n_args
; ++i
) {
1131 tor_free(tok
->args
[i
]);
1133 tor_free(tok
->args
);
1135 tor_free(tok
->object_type
);
1136 tor_free(tok
->object_body
);
1138 crypto_free_pk_env(tok
->key
);
1142 /** Helper function: read the next token from *s, advance *s to the end
1143 * of the token, and return the parsed token. If 'where' is DIR_ONLY
1144 * or RTR_ONLY, reject all tokens of the wrong type.
1146 static directory_token_t
*
1147 get_next_token(const char **s
, where_syntax where
) {
1148 const char *next
, *obstart
;
1149 int i
, done
, allocated
, is_opt
;
1150 directory_token_t
*tok
;
1152 obj_syntax o_syn
= NO_OBJ
;
1154 #define RET_ERR(msg) \
1155 do { if (tok) token_free(tok); \
1156 tok = tor_malloc_zero(sizeof(directory_token_t));\
1159 goto done_tokenizing; } while (0)
1161 tok
= tor_malloc_zero(sizeof(directory_token_t
));
1164 *s
= eat_whitespace(*s
);
1169 next
= find_whitespace(*s
);
1171 tok
->error
= "Unexpected EOF"; return tok
;
1173 /* It's a keyword... but which one? */
1174 is_opt
= !strncmp("opt", *s
, next
-*s
);
1176 *s
= eat_whitespace(next
);
1179 next
= find_whitespace(*s
);
1180 if (!**s
|| !next
) {
1181 RET_ERR("opt without keyword");
1184 for (i
= 0; token_table
[i
].t
; ++i
) {
1185 if (!strncmp(token_table
[i
].t
, *s
, next
-*s
)) {
1186 /* We've found the keyword. */
1187 tok
->tp
= token_table
[i
].v
;
1188 a_syn
= token_table
[i
].s
;
1189 o_syn
= token_table
[i
].os
;
1190 if (token_table
[i
].ws
!= ANY
&& token_table
[i
].ws
!= where
) {
1191 if (where
== DIR_ONLY
) {
1192 RET_ERR("Found a router-only token in a directory section");
1194 RET_ERR("Found a directory-only token in a router descriptor");
1197 if (a_syn
== ARGS
) {
1198 /* This keyword takes multiple arguments. */
1200 done
= (*next
== '\n');
1202 tok
->args
= tor_malloc(sizeof(char*)*32);
1203 *s
= eat_whitespace_no_nl(next
);
1204 while (**s
!= '\n' && !done
) {
1205 next
= find_whitespace(*s
);
1208 if (i
== allocated
) {
1210 tok
->args
= tor_realloc(tok
->args
,sizeof(char*)*allocated
);
1212 tok
->args
[i
++] = tor_strndup(*s
,next
-*s
);
1213 *s
= eat_whitespace_no_nl(next
+1);
1216 } else if (a_syn
== CONCAT_ARGS
) {
1217 /* The keyword takes the line as a single argument */
1218 *s
= eat_whitespace_no_nl(next
);
1219 next
= strchr(*s
, '\n');
1221 RET_ERR("Unexpected EOF");
1222 tok
->args
= tor_malloc(sizeof(char*));
1223 tok
->args
[0] = tor_strndup(*s
,next
-*s
);
1225 *s
= eat_whitespace_no_nl(next
+1);
1227 /* The keyword takes no arguments. */
1228 tor_assert(a_syn
== NO_ARGS
);
1229 *s
= eat_whitespace_no_nl(next
);
1231 RET_ERR("Unexpected arguments");
1234 *s
= eat_whitespace_no_nl(*s
+1);
1239 if (tok
->tp
== _ERR
) {
1242 *s
= eat_whitespace_no_nl(next
);
1243 next
= strchr(*s
,'\n');
1245 RET_ERR("Unexpected EOF");
1246 tok
->args
= tor_malloc(sizeof(char*));
1247 tok
->args
[0] = tor_strndup(*s
,next
-*s
);
1249 *s
= eat_whitespace_no_nl(next
+1);
1252 tok
->tp
= _UNRECOGNIZED
;
1253 next
= strchr(*s
, '\n');
1255 RET_ERR("Unexpected EOF");
1257 tok
->args
= tor_malloc(sizeof(char*));
1258 tok
->args
[0] = tor_strndup(*s
,next
-*s
);
1264 *s
= eat_whitespace(*s
);
1265 if (strcmpstart(*s
, "-----BEGIN ")) {
1266 goto done_tokenizing
;
1269 *s
+= 11; /* length of "-----BEGIN ". */
1270 next
= strchr(*s
, '\n');
1271 if (next
-*s
< 6 || strcmpstart(next
-5, "-----\n")) {
1272 RET_ERR("Malformed object: bad begin line");
1274 tok
->object_type
= tor_strndup(*s
, next
-*s
-5);
1276 next
= strstr(*s
, "-----END ");
1278 RET_ERR("Malformed object: missing end line");
1280 if (!strcmp(tok
->object_type
, "RSA PUBLIC KEY")) {
1281 if (strcmpstart(next
, "-----END RSA PUBLIC KEY-----\n"))
1282 RET_ERR("Malformed object: mismatched end line");
1283 next
= strchr(next
,'\n')+1;
1284 tok
->key
= crypto_new_pk_env();
1285 if (crypto_pk_read_public_key_from_string(tok
->key
, obstart
, next
-obstart
))
1286 RET_ERR("Couldn't parse public key.");
1289 tok
->object_body
= tor_malloc(next
-*s
); /* really, this is too much RAM. */
1290 i
= base64_decode(tok
->object_body
, 256, *s
, next
-*s
);
1292 RET_ERR("Malformed object: bad base64-encoded data");
1294 tok
->object_size
= i
;
1295 *s
= next
+ 9; /* length of "-----END ". */
1296 i
= strlen(tok
->object_type
);
1297 if (strncmp(*s
, tok
->object_type
, i
) || strcmpstart(*s
+i
, "-----\n")) {
1298 RET_ERR("Malformed object: mismatched end tag");
1305 if (tok
->object_body
)
1306 RET_ERR("Unexpected object for keyword");
1308 RET_ERR("Unexpected public key for keyword");
1311 if (!tok
->object_body
)
1312 RET_ERR("Missing object for keyword");
1316 RET_ERR("Missing public key for keyword");
1325 for (i
= 0; token_table
[i
].t
; ++i
) {
1326 if (token_table
[i
].v
== tok
->tp
) {
1327 fputs(token_table
[i
].t
, stdout
);
1333 if (tok
->tp
== _UNRECOGNIZED
) fputs("UNRECOGNIZED", stdout
);
1334 if (tok
->tp
== _ERR
) fputs("ERR",stdout
);
1335 if (tok
->tp
== _EOF
) fputs("EOF",stdout
);
1336 if (tok
->tp
== _NIL
) fputs("_NIL",stdout
);
1338 for(i
= 0; i
< tok
->n_args
; ++i
) {
1339 fprintf(stdout
," \"%s\"", tok
->args
[i
]);
1341 if (tok
->error
) { fprintf(stdout
," *%s*", tok
->error
); }
1350 /** Read all tokens from a string between <b>start</b> and <b>end</b>, and add
1351 * them to <b>out</b>. If <b>is_dir</b> is true, reject all non-directory
1352 * tokens; else reject all non-routerdescriptor tokens.
1355 tokenize_string(const char *start
, const char *end
, smartlist_t
*out
,
1359 directory_token_t
*tok
= NULL
;
1360 where_syntax where
= is_dir
? DIR_ONLY
: RTR_ONLY
;
1362 while (*s
< end
&& (!tok
|| tok
->tp
!= _EOF
)) {
1363 tok
= get_next_token(s
, where
);
1364 if (tok
->tp
== _ERR
) {
1365 log_fn(LOG_WARN
, "parse error: %s", tok
->error
);
1368 smartlist_add(out
, tok
);
1369 *s
= eat_whitespace(*s
);
1375 /** Find the first token in <b>s</b> whose keyword is <b>keyword</b>; return
1376 * NULL if no such keyword is found.
1378 static directory_token_t
*
1379 find_first_by_keyword(smartlist_t
*s
, directory_keyword keyword
)
1381 SMARTLIST_FOREACH(s
, directory_token_t
*, t
, if (t
->tp
== keyword
) return t
);
1385 /** Return a newly allocated smartlist of all accept or reject tokens in
1388 static smartlist_t
*
1389 find_all_exitpolicy(smartlist_t
*s
)
1391 smartlist_t
*out
= smartlist_create();
1392 SMARTLIST_FOREACH(s
, directory_token_t
*, t
,
1393 if (t
->tp
== K_ACCEPT
|| t
->tp
== K_REJECT
)
1394 smartlist_add(out
,t
));
1398 /** Compute the SHA digest of the substring of <b>s</b> taken from the first
1399 * occurrence of <b>start_str</b> through the first newline after the first
1400 * subsequent occurrence of <b>end_str</b>; store the 20-byte result in
1401 * <b>digest</b>; return 0 on success.
1403 * If no such substring exists, return -1.
1405 static int router_get_hash_impl(const char *s
, char *digest
,
1406 const char *start_str
,
1407 const char *end_str
)
1410 start
= strstr(s
, start_str
);
1412 log_fn(LOG_WARN
,"couldn't find \"%s\"",start_str
);
1415 if (start
!= s
&& *(start
-1) != '\n') {
1416 log_fn(LOG_WARN
, "first occurrence of \"%s\" is not at the start of a line",
1420 end
= strstr(start
+strlen(start_str
), end_str
);
1422 log_fn(LOG_WARN
,"couldn't find \"%s\"",end_str
);
1425 end
= strchr(end
+strlen(end_str
), '\n');
1427 log_fn(LOG_WARN
,"couldn't find EOL");
1432 if (crypto_digest(start
, end
-start
, digest
)) {
1433 log_fn(LOG_WARN
,"couldn't compute digest");
1440 /** Parse the Tor version of the platform string <b>platform</b>,
1441 * and compare it to the version in <b>cutoff</b>. Return 1 if
1442 * the router is at least as new as the cutoff, else return 0.
1444 int tor_version_as_new_as(const char *platform
, const char *cutoff
) {
1445 tor_version_t cutoff_version
, router_version
;
1449 if(tor_version_parse(cutoff
, &cutoff_version
)<0) {
1450 log_fn(LOG_WARN
,"Bug: cutoff version '%s' unparsable.",cutoff
);
1453 if(strcmpstart(platform
,"Tor ")) /* nonstandard Tor; be safe and say yes */
1456 start
= (char *)eat_whitespace(platform
+3);
1457 if (!*start
) return 0;
1458 s
= (char *)find_whitespace(start
); /* also finds '\0', which is fine */
1459 if((size_t)(s
-start
+1) >= sizeof(tmp
)) /* too big, no */
1461 strlcpy(tmp
, start
, s
-start
+1);
1463 if(tor_version_parse(tmp
, &router_version
)<0) {
1464 log_fn(LOG_INFO
,"Router version '%s' unparsable.",tmp
);
1465 return 1; /* be safe and say yes */
1468 return tor_version_compare(&router_version
, &cutoff_version
) >= 0;
1472 int tor_version_parse(const char *s
, tor_version_t
*out
)
1474 char *eos
=NULL
, *cp
=NULL
;
1476 * NUM dot NUM dot NUM [ ( pre | rc | dot ) NUM [ -cvs ] ]
1478 tor_assert(s
&& out
);
1479 memset(out
, 0, sizeof(tor_version_t
));
1482 out
->major
= strtol(s
,&eos
,10);
1483 if (!eos
|| eos
==s
|| *eos
!= '.') return -1;
1487 out
->minor
= strtol(cp
,&eos
,10);
1488 if (!eos
|| eos
==cp
|| *eos
!= '.') return -1;
1492 out
->micro
= strtol(cp
,&eos
,10);
1493 if (!eos
|| eos
==cp
) return -1;
1495 out
->status
= VER_RELEASE
;
1496 out
->patchlevel
= 0;
1497 out
->cvs
= IS_NOT_CVS
;
1504 out
->status
= VER_RELEASE
;
1506 } else if (0==strncmp(cp
, "pre", 3)) {
1507 out
->status
= VER_PRE
;
1509 } else if (0==strncmp(cp
, "rc", 2)) {
1510 out
->status
= VER_RC
;
1516 /* Get patchlevel */
1517 out
->patchlevel
= strtol(cp
,&eos
,10);
1518 if (!eos
|| eos
==cp
) return -1;
1521 /* Get cvs status. */
1523 out
->cvs
= IS_NOT_CVS
;
1524 } else if (0==strcmp(cp
, "-cvs")) {
1533 /** Compare two tor versions; Return <0 if a < b; 0 if a ==b, >0 if a >
1535 int tor_version_compare(tor_version_t
*a
, tor_version_t
*b
)
1539 if ((i
= a
->major
- b
->major
))
1541 else if ((i
= a
->minor
- b
->minor
))
1543 else if ((i
= a
->micro
- b
->micro
))
1545 else if ((i
= a
->status
- b
->status
))
1547 else if ((i
= a
->patchlevel
- b
->patchlevel
))
1549 else if ((i
= a
->cvs
- b
->cvs
))
1558 indent-tabs-mode:nil