routerparse.c 59.2 KB
Newer Older
Roger Dingledine's avatar
Roger Dingledine committed
1
2
3
/* Copyright (c) 2001 Matej Pfajfar.
 * Copyright (c) 2001-2004, Roger Dingledine.
 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. */
4
5
/* See LICENSE for licensing information */
/* $Id$ */
6
7
const char routerparse_c_id[] =
  "$Id$";
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

/**
 * \file routerparse.c
 * \brief Code to parse and validate router descriptors and directories.
 **/

#include "or.h"

/****************************************************************************/

/** Enumeration of possible token types.  The ones starting with K_
 * correspond to directory 'keywords'.  _UNRECOGNIZED is for an
 * unrecognized keyword; _ERR is an error in the tokenizing process,
 * _EOF is an end-of-file marker, and _NIL is used to encode
 * not-a-token.
 */
typedef enum {
  K_ACCEPT,
  K_DIRECTORY_SIGNATURE,
  K_RECOMMENDED_SOFTWARE,
  K_REJECT,
  K_ROUTER,
  K_SIGNED_DIRECTORY,
  K_SIGNING_KEY,
  K_ONION_KEY,
  K_ROUTER_SIGNATURE,
  K_PUBLISHED,
  K_RUNNING_ROUTERS,
36
  K_ROUTER_STATUS,
37
38
39
40
  K_PLATFORM,
  K_OPT,
  K_BANDWIDTH,
  K_PORTS,
41
  K_CONTACT,
42
  K_NETWORK_STATUS,
Nick Mathewson's avatar
Nick Mathewson committed
43
  K_UPTIME,
44
  K_DIR_SIGNING_KEY,
45
  K_FAMILY,
46
47
48
49
  K_FINGERPRINT,
  K_HIBERNATING,
  K_READ_HISTORY,
  K_WRITE_HISTORY,
50
51
52
53
54
55
56
  K_NETWORK_STATUS_VERSION,
  K_DIR_SOURCE,
  K_DIR_OPTIONS,
  K_CLIENT_VERSIONS,
  K_SERVER_VERSIONS,
  K_R,
  K_S,
57
  K_EVENTDNS,
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
  _UNRECOGNIZED,
  _ERR,
  _EOF,
  _NIL
} directory_keyword;

/** Structure to hold a single directory token.
 *
 * We parse a directory by breaking it into "tokens", each consisting
 * of a keyword, a line full of arguments, and a binary object.  The
 * arguments and object are both optional, depending on the keyword
 * type.
 */
typedef struct directory_token_t {
  directory_keyword tp;        /**< Type of the token. */
  int n_args;                  /**< Number of elements in args */
  char **args;                 /**< Array of arguments from keyword line. */
  char *object_type;           /**< -----BEGIN [object_type]-----*/
76
  size_t object_size;             /**< Bytes in object_body */
77
78
  char *object_body;           /**< Contents of object, base64-decoded. */
  crypto_pk_env_t *key;        /**< For public keys only. */
79
  const char *error;           /**< For _ERR tokens only. */
80
81
82
83
84
85
86
87
} directory_token_t;

/* ********************************************************************** */

/** We use a table of rules to decide how to parse each token type. */

/** Rules for how many arguments a keyword can take. */
typedef enum {
Roger Dingledine's avatar
Roger Dingledine committed
88
89
  NO_ARGS,     /**< No arguments, ever. */
  ARGS,        /**< A list of arguments separated by spaces. */
90
  CONCAT_ARGS, /**< The rest of the line, treated as a single argument. */
91
92
93
94
} arg_syntax;

/** Rules for whether the keyword needs an object. */
typedef enum {
Roger Dingledine's avatar
Roger Dingledine committed
95
96
  NO_OBJ,      /**< No object, ever. */
  NEED_OBJ,    /**< Object is required. */
97
98
  NEED_KEY,    /**< Object is required, and must be a public key. */
  OBJ_OK,      /**< Object is optional. */
99
100
101
102
} obj_syntax;

/** Rules for where a keyword can appear. */
typedef enum {
103
  DIR = 1,        /**< Appears only in directory. */
Roger Dingledine's avatar
Roger Dingledine committed
104
  RTR = 2,        /**< Appears only in router descriptor or runningrouters. */
105
  NETSTATUS = 4,  /**< v2 or later ("versioned") network status. */
106
107
  ANYSIGNED = 7,  /**< Any "full" document (that is, not a router status.) */
  RTRSTATUS = 8,  /**< Router-status portion of a versioned network status. */
Roger Dingledine's avatar
Roger Dingledine committed
108
  ANY = 15,       /**< Appears in any document type. */
109
110
111
112
} where_syntax;

/** Table mapping keywords to token value and to argument rules. */
static struct {
113
  const char *t; directory_keyword v; arg_syntax s; obj_syntax os; int ws;
114
} token_table[] = {
115
  { "accept",              K_ACCEPT,              ARGS,    NO_OBJ,  RTR },
Nick Mathewson's avatar
Nick Mathewson committed
116
  { "directory-signature", K_DIRECTORY_SIGNATURE, ARGS,    NEED_OBJ,
117
118
119
                                                           DIR|NETSTATUS},
  { "r",                   K_R,                   ARGS,    NO_OBJ, RTRSTATUS },
  { "s",                   K_S,                   ARGS,    NO_OBJ, RTRSTATUS },
120
121
122
123
124
125
126
127
128
129
130
  { "reject",              K_REJECT,              ARGS,    NO_OBJ,  RTR },
  { "router",              K_ROUTER,              ARGS,    NO_OBJ,  RTR },
  { "recommended-software",K_RECOMMENDED_SOFTWARE,ARGS,    NO_OBJ,  DIR },
  { "signed-directory",    K_SIGNED_DIRECTORY,    NO_ARGS, NO_OBJ,  DIR },
  { "signing-key",         K_SIGNING_KEY,         NO_ARGS, NEED_KEY,RTR },
  { "onion-key",           K_ONION_KEY,           NO_ARGS, NEED_KEY,RTR },
  { "router-signature",    K_ROUTER_SIGNATURE,    NO_ARGS, NEED_OBJ,RTR },
  { "running-routers",     K_RUNNING_ROUTERS,     ARGS,    NO_OBJ,  DIR },
  { "router-status",       K_ROUTER_STATUS,       ARGS,    NO_OBJ,  DIR },
  { "bandwidth",           K_BANDWIDTH,           ARGS,    NO_OBJ,  RTR },
  { "platform",            K_PLATFORM,        CONCAT_ARGS, NO_OBJ,  RTR },
131
132
133
  { "published",           K_PUBLISHED,       CONCAT_ARGS, NO_OBJ, ANYSIGNED },
  { "opt",                 K_OPT,             CONCAT_ARGS, OBJ_OK, ANY },
  { "contact",             K_CONTACT,         CONCAT_ARGS, NO_OBJ, ANYSIGNED },
134
135
  { "network-status",      K_NETWORK_STATUS,      NO_ARGS, NO_OBJ,  DIR },
  { "uptime",              K_UPTIME,              ARGS,    NO_OBJ,  RTR },
136
137
  { "dir-signing-key",     K_DIR_SIGNING_KEY,     ARGS,    OBJ_OK,
                                                                DIR|NETSTATUS},
138
  { "family",              K_FAMILY,              ARGS,    NO_OBJ,  RTR },
139
  { "fingerprint",         K_FINGERPRINT,     CONCAT_ARGS, NO_OBJ, ANYSIGNED },
140
141
142
143
144
145
146
147
148
  { "hibernating",         K_HIBERNATING,         ARGS,    NO_OBJ,  RTR },
  { "read-history",        K_READ_HISTORY,        ARGS,    NO_OBJ,  RTR },
  { "write-history",       K_WRITE_HISTORY,       ARGS,    NO_OBJ,  RTR },
  { "network-status-version", K_NETWORK_STATUS_VERSION,
                                                  ARGS,    NO_OBJ, NETSTATUS },
  { "dir-source",          K_DIR_SOURCE,          ARGS,    NO_OBJ, NETSTATUS },
  { "dir-options",         K_DIR_OPTIONS,         ARGS,    NO_OBJ, NETSTATUS },
  { "client-versions",     K_CLIENT_VERSIONS,     ARGS,    NO_OBJ, NETSTATUS },
  { "server-versions",     K_SERVER_VERSIONS,     ARGS,    NO_OBJ, NETSTATUS },
149
  { "eventdns",            K_EVENTDNS,            ARGS,    NO_OBJ, RTR },
Nick Mathewson's avatar
Nick Mathewson committed
150
  { NULL, -1, NO_ARGS, NO_OBJ, ANY }
151
152
153
154
};

/* static function prototypes */
static int router_add_exit_policy(routerinfo_t *router,directory_token_t *tok);
155
static addr_policy_t *router_parse_addr_policy(directory_token_t *tok);
156
157
static addr_policy_t *router_parse_private_addr_policy_private(
                                               directory_token_t *tok);
158
159
160
161
162
163
164
static int router_get_hash_impl(const char *s, char *digest,
                                const char *start_str, const char *end_str);
static void token_free(directory_token_t *tok);
static smartlist_t *find_all_exitpolicy(smartlist_t *s);
static directory_token_t *find_first_by_keyword(smartlist_t *s,
                                                directory_keyword keyword);
static int tokenize_string(const char *start, const char *end,
165
                           smartlist_t *out, where_syntax where);
166
static directory_token_t *get_next_token(const char **s, where_syntax where);
167
168
static int check_directory_signature(const char *digest,
                                     directory_token_t *tok,
169
                                     crypto_pk_env_t *pkey,
170
171
                                     crypto_pk_env_t *declared_key,
                                     int check_authority);
172
static crypto_pk_env_t *find_dir_signing_key(const char *str);
173
static int tor_version_same_series(tor_version_t *a, tor_version_t *b);
174
175

/** Set <b>digest</b> to the SHA-1 digest of the hash of the directory in
176
 * <b>s</b>.  Return 0 on success, -1 on failure.
177
 */
178
179
int
router_get_dir_hash(const char *s, char *digest)
180
181
{
  return router_get_hash_impl(s,digest,
182
                              "signed-directory","\ndirectory-signature");
183
184
185
}

/** Set <b>digest</b> to the SHA-1 digest of the hash of the first router in
186
 * <b>s</b>. Return 0 on success, -1 on failure.
187
 */
188
189
int
router_get_router_hash(const char *s, char *digest)
190
191
{
  return router_get_hash_impl(s,digest,
192
                              "router ","\nrouter-signature");
193
194
}

Nick Mathewson's avatar
Nick Mathewson committed
195
/** Set <b>digest</b> to the SHA-1 digest of the hash of the running-routers
196
 * string in <b>s</b>. Return 0 on success, -1 on failure.
Nick Mathewson's avatar
Nick Mathewson committed
197
 */
198
199
int
router_get_runningrouters_hash(const char *s, char *digest)
200
201
{
  return router_get_hash_impl(s,digest,
202
                              "network-status","\ndirectory-signature");
203
204
}

205
206
/** Set <b>digest</b> to the SHA-1 digest of the hash of the network-status
 * string in <b>s</b>.  Return 0 on success, -1 on failure. */
207
208
209
210
int
router_get_networkstatus_v2_hash(const char *s, char *digest)
{
  return router_get_hash_impl(s,digest,
211
                            "network-status-version","\ndirectory-signature");
212
213
}

214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/** Helper: used to generate signatures for routers, directories and
 * network-status objects.  Given a digest in <b>digest</b> and a secret
 * <b>private_key</b>, generate an PKCS1-padded signature, BASE64-encode it,
 * surround it with -----BEGIN/END----- pairs, and write it to the
 * <b>buf_len</b>-byte buffer at <b>buf</b>.  Return 0 on success, -1 on
 * failure.
 */
int
router_append_dirobj_signature(char *buf, size_t buf_len, const char *digest,
                               crypto_pk_env_t *private_key)
{
  char signature[PK_BYTES];
  int i;

  if (crypto_pk_private_sign(private_key, signature, digest, DIGEST_LEN) < 0) {

230
    log_warn(LD_BUG,"Couldn't sign digest.");
231
232
233
234
235
236
237
    return -1;
  }
  if (strlcat(buf, "-----BEGIN SIGNATURE-----\n", buf_len) >= buf_len)
    goto truncated;

  i = strlen(buf);
  if (base64_encode(buf+i, buf_len-i, signature, 128) < 0) {
238
    log_warn(LD_BUG,"couldn't base64-encode signature");
239
240
241
242
243
244
245
246
247
    tor_free(buf);
    return -1;
  }

  if (strlcat(buf, "-----END SIGNATURE-----\n", buf_len) >= buf_len)
    goto truncated;

  return 0;
 truncated:
248
  log_warn(LD_BUG,"tried to exceed string length.");
249
250
251
  return -1;
}

252
253
254
255
256
257
258
259
/** Return VS_RECOMMENDED if <b>myversion</b> is contained in
 * <b>versionlist</b>.  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 memeber of
 * <b>versionlist</b> is older than <b>myversion</b>.  Else, return
 * VS_UNRECOMMENDED.
260
 *
261
262
 * (versionlist is a comma-separated list of version strings,
 * optionally prefixed with "Tor".  Versions that can't be parsed are
263
 * ignored.)
264
 */
265
266
267
version_status_t
tor_version_is_obsolete(const char *myversion, const char *versionlist)
{
268
  tor_version_t mine, other;
269
270
271
  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;
272
  smartlist_t *version_sl;
273

274
275
  log_debug(LD_CONFIG,"Checking whether version '%s' is in '%s'",
            myversion, versionlist);
276

277
  if (tor_version_parse(myversion, &mine)) {
278
    log_err(LD_BUG,"I couldn't parse my own version (%s)", myversion);
279
280
    tor_assert(0);
  }
281
282
  version_sl = smartlist_create();
  smartlist_split_string(version_sl, versionlist, ",", SPLIT_SKIP_SPACE, 0);
283

284
285
  SMARTLIST_FOREACH(version_sl, const char *, cp, {
    if (!strcmpstart(cp, "Tor "))
286
287
288
289
290
      cp += 4;

    if (tor_version_parse(cp, &other)) {
      /* Couldn't parse other; it can't be a match. */
    } else {
291
292
293
      same = tor_version_same_series(&mine, &other);
      if (same)
        found_any_in_series = 1;
294
295
      r = tor_version_compare(&mine, &other);
      if (r==0) {
296
        ret = VS_RECOMMENDED;
297
        goto done;
298
299
      } else if (r<0) {
        found_newer = 1;
300
301
        if (same)
          found_newer_in_series = 1;
302
303
      } else if (r>0) {
        found_older = 1;
304
305
      }
    }
306
307
  });

308
  /* We didn't find the listed version. Is it new or old? */
309
310
311
312
313
314
  if (found_any_in_series && !found_newer_in_series) {
    ret = VS_NEW_IN_SERIES;
  } else if (found_newer && !found_older) {
    ret = VS_OLD;
  } else if (found_older && !found_newer) {
    ret = VS_NEW;
315
  } else {
316
    ret = VS_UNRECOMMENDED;
317
  }
318
319
320
321
322

 done:
  SMARTLIST_FOREACH(version_sl, char *, version, tor_free(version));
  smartlist_free(version_sl);
  return ret;
323
324
}

325
326
327
328
329
330
331
332
333
334
335
/** Return the combined status of the current version, given that we know of
 * one set of networkstatuses that give us status <b>a</b>, and another that
 * gives us status <b>b</b>.
 *
 * For example, if one authority thinks that we're NEW, and another thinks
 * we're OLD, we're simply UNRECOMMENDED.
 *
 * This function does not handle calculating whether we're RECOMMENDED; that
 * follows a simple majority rule.  This function simply calculates *why*
 * we're not recommended (if we're not).
 */
336
337
version_status_t
version_status_join(version_status_t a, version_status_t b)
338
{
339
340
341
342
343
344
345
346
347
348
349
350
351
352
  if (a == b)
    return a;
  else if (a == VS_UNRECOMMENDED || b == VS_UNRECOMMENDED)
    return VS_UNRECOMMENDED;
  else if (a == VS_RECOMMENDED)
    return b;
  else if (b == VS_RECOMMENDED)
    return a;
  /* Okay.  Neither is 'recommended' or 'unrecommended', and they differ. */
  else if (a == VS_OLD || b == VS_OLD)
    return VS_UNRECOMMENDED;
  /* One is VS_NEW, the other is VS_NEW_IN_SERIES */
  else
    return VS_NEW_IN_SERIES;
353
}
354

355
356
/** Read a signed directory from <b>str</b>.  If it's well-formed, return 0.
 * Otherwise, return -1.  If we're a directory cache, cache it.
357
 */
358
359
int
router_parse_directory(const char *str)
360
361
{
  directory_token_t *tok;
362
363
  char digest[DIGEST_LEN];
  time_t published_on;
364
  int r;
365
  const char *end, *cp;
366
  smartlist_t *tokens = NULL;
367
  crypto_pk_env_t *declared_key = NULL;
368

369
370
  /* XXXX This could be simplified a lot, but it will all go away
   * once pre-0.1.1.8 is obsolete, and for now it's better not to
Roger Dingledine's avatar
Roger Dingledine committed
371
   * touch it. */
372

373
  if (router_get_dir_hash(str, digest)) {
374
    log_warn(LD_DIR, "Unable to compute digest of directory");
375
376
    goto err;
  }
377
  log_debug(LD_DIR,"Received directory hashes to %s",hex_str(digest,4));
378

379
380
381
382
383
  /* Check signature first, before we try to tokenize. */
  cp = str;
  while (cp && (end = strstr(cp+1, "\ndirectory-signature")))
    cp = end;
  if (cp == str || !cp) {
384
    log_warn(LD_DIR, "No signature found on directory."); goto err;
385
386
387
  }
  ++cp;
  tokens = smartlist_create();
388
  if (tokenize_string(cp,strchr(cp,'\0'),tokens,DIR)) {
389
    log_warn(LD_DIR, "Error tokenizing directory signature"); goto err;
390
391
  }
  if (smartlist_len(tokens) != 1) {
392
    log_warn(LD_DIR, "Unexpected number of tokens in signature"); goto err;
393
  }
394
  tok=smartlist_get(tokens,0);
395
  if (tok->tp != K_DIRECTORY_SIGNATURE) {
396
    log_warn(LD_DIR,"Expected a single directory signature"); goto err;
397
  }
398
  declared_key = find_dir_signing_key(str);
399
  if (check_directory_signature(digest, tok, NULL, declared_key, 1)<0)
400
    goto err;
401

402
403
404
405
406
  SMARTLIST_FOREACH(tokens, directory_token_t *, tok, token_free(tok));
  smartlist_free(tokens);
  tokens = NULL;

  /* Now try to parse the first part of the directory. */
407
408
409
410
411
412
413
414
415
  if ((end = strstr(str,"\nrouter "))) {
    ++end;
  } else if ((end = strstr(str, "\ndirectory-signature"))) {
    ++end;
  } else {
    end = str + strlen(str);
  }

  tokens = smartlist_create();
416
  if (tokenize_string(str,end,tokens,DIR)) {
417
    log_warn(LD_DIR, "Error tokenizing directory"); goto err;
418
419
420
  }

  if (!(tok = find_first_by_keyword(tokens, K_PUBLISHED))) {
421
    log_warn(LD_DIR, "Missing published time on directory.");
422
423
424
425
    goto err;
  }
  tor_assert(tok->n_args == 1);

426
  if (parse_iso_time(tok->args[0], &published_on) < 0) {
427
428
429
     goto err;
  }

430
431
  /* Now that we know the signature is okay, and we have a
   * publication time, cache the directory. */
432
  if (get_options()->DirPort && !get_options()->V1AuthoritativeDir)
433
    dirserv_set_cached_directory(str, published_on, 0);
434

435
436
437
438
439
  r = 0;
  goto done;
 err:
  r = -1;
 done:
440
  if (declared_key) crypto_free_pk_env(declared_key);
441
442
443
444
445
446
447
  if (tokens) {
    SMARTLIST_FOREACH(tokens, directory_token_t *, tok, token_free(tok));
    smartlist_free(tokens);
  }
  return r;
}

448
449
450
/** Read a signed router status statement from <b>str</b>.  If it's
 * well-formed, return 0.  Otherwise, return -1.  If we're a directory cache,
 * cache it.*/
451
452
int
router_parse_runningrouters(const char *str)
453
454
455
456
{
  char digest[DIGEST_LEN];
  directory_token_t *tok;
  time_t published_on;
457
  int r = -1;
458
  crypto_pk_env_t *declared_key = NULL;
459
460
461
  smartlist_t *tokens = NULL;

  if (router_get_runningrouters_hash(str, digest)) {
462
    log_warn(LD_DIR, "Unable to compute digest of running-routers");
463
464
465
    goto err;
  }
  tokens = smartlist_create();
466
  if (tokenize_string(str,str+strlen(str),tokens,DIR)) {
467
    log_warn(LD_DIR, "Error tokenizing running-routers"); goto err;
468
469
  }
  if ((tok = find_first_by_keyword(tokens, _UNRECOGNIZED))) {
470
471
    log_warn(LD_DIR, "Unrecognized keyword %s; can't parse running-routers",
             escaped(tok->args[0]));
472
473
474
475
    goto err;
  }
  tok = smartlist_get(tokens,0);
  if (tok->tp != K_NETWORK_STATUS) {
476
    log_warn(LD_DIR, "Network-status starts with wrong token");
477
478
479
480
    goto err;
  }

  if (!(tok = find_first_by_keyword(tokens, K_PUBLISHED))) {
481
    log_warn(LD_DIR, "Missing published time on running-routers.");
482
483
484
    goto err;
  }
  tor_assert(tok->n_args == 1);
485
  if (parse_iso_time(tok->args[0], &published_on) < 0) {
486
487
488
     goto err;
  }
  if (!(tok = find_first_by_keyword(tokens, K_DIRECTORY_SIGNATURE))) {
489
    log_warn(LD_DIR, "Missing signature on running-routers");
490
491
    goto err;
  }
492
  declared_key = find_dir_signing_key(str);
493
  if (check_directory_signature(digest, tok, NULL, declared_key, 1) < 0)
494
495
    goto err;

496
497
498
499
500
501
  /* Now that we know the signature is okay, and we have a
   * publication time, cache the list. */
  if (get_options()->DirPort && !get_options()->V1AuthoritativeDir)
    dirserv_set_cached_directory(str, published_on, 1);

  r = 0;
502
 err:
503
  if (declared_key) crypto_free_pk_env(declared_key);
504
505
506
507
  if (tokens) {
    SMARTLIST_FOREACH(tokens, directory_token_t *, tok, token_free(tok));
    smartlist_free(tokens);
  }
508
  return r;
509
510
}

511
512
513
/** Given a directory or running-routers string in <b>str</b>, try to
 * find the its dir-signing-key token (if any).  If this token is
 * present, extract and return the key.  Return NULL on failure. */
514
515
static crypto_pk_env_t *
find_dir_signing_key(const char *str)
516
517
518
519
520
521
522
523
524
525
526
527
528
{
  const char *cp;
  directory_token_t *tok;
  crypto_pk_env_t *key = NULL;

  /* Is there a dir-signing-key in the directory? */
  cp = strstr(str, "\nopt dir-signing-key");
  if (!cp)
    cp = strstr(str, "\ndir-signing-key");
  if (!cp)
    return NULL;
  ++cp; /* Now cp points to the start of the token. */

529
  tok = get_next_token(&cp, DIR);
530
  if (!tok) {
531
    log_warn(LD_DIR, "Unparseable dir-signing-key token");
532
533
534
    return NULL;
  }
  if (tok->tp != K_DIR_SIGNING_KEY) {
535
    log_warn(LD_DIR, "Dir-signing-key token did not parse as expected");
536
537
538
539
540
541
542
    return NULL;
  }

  if (tok->key) {
    key = tok->key;
    tok->key = NULL; /* steal reference. */
  } else {
543
    log_warn(LD_DIR, "Dir-signing-key token contained no key");
544
545
546
547
548
549
550
551
552
    return NULL;
  }

  token_free(tok);
  return key;
}

/** Return true iff <b>key</b> is allowed to sign directories.
 */
553
554
static int
dir_signing_key_is_trusted(crypto_pk_env_t *key)
555
556
557
558
{
  char digest[DIGEST_LEN];
  if (!key) return 0;
  if (crypto_pk_get_digest(key, digest) < 0) {
559
    log_warn(LD_DIR, "Error computing dir-signing-key digest");
560
561
    return 0;
  }
562
  if (!router_digest_is_trusted_dir(digest)) {
563
    log_warn(LD_DIR, "Listed dir-signing-key is not trusted");
564
565
566
567
568
569
570
571
572
573
    return 0;
  }
  return 1;
}

/** Check whether the K_DIRECTORY_SIGNATURE token in <b>tok</b> has a
 * good signature for <b>digest</b>.
 *
 * If <b>declared_key</b> is set, the directory has declared what key
 * was used to sign it, so we will use that key only if it is an
574
 * authoritative directory signing key or if check_authority is 0.
575
 *
576
 * Otherwise, if pkey is provided, try to use it.
577
578
 *
 * (New callers should always use <b>declared_key</b> when possible;
579
 * <b>pkey</b> is only for debugging.)
580
 */
581
582
583
584
static int
check_directory_signature(const char *digest,
                          directory_token_t *tok,
                          crypto_pk_env_t *pkey,
585
586
                          crypto_pk_env_t *declared_key,
                          int check_authority)
587
588
{
  char signed_digest[PK_BYTES];
589
  crypto_pk_env_t *_pkey = NULL;
590
591

  if (tok->n_args != 1) {
592
    log_warn(LD_DIR, "Too many or too few arguments to directory-signature");
593
    return -1;
594
  }
595

596
  if (declared_key) {
597
    if (!check_authority || dir_signing_key_is_trusted(declared_key))
598
      _pkey = declared_key;
599
  }
600
601
602
603
  if (!_pkey && pkey) {
    /* pkey provided for debugging purposes */
    _pkey = pkey;
  }
604
  if (!_pkey) {
605
606
607
    log_warn(LD_DIR,
             "Obsolete directory format (dir signing key not present) or "
             "signing key not trusted--rejecting.");
608
    return -1;
609
  }
610

611
  if (strcmp(tok->object_type, "SIGNATURE") || tok->object_size != 128) {
612
    log_warn(LD_DIR, "Bad object type or length on directory signature");
613
    return -1;
614
  }
615

616
  tor_assert(_pkey);
617

618
  if (crypto_pk_public_checksig(_pkey, signed_digest, tok->object_body, 128)
619
      != 20) {
620
    log_warn(LD_DIR, "Error reading directory: invalid signature.");
621
622
    return -1;
  }
623
624
  log_debug(LD_DIR,"Signed directory hash starts %s",
            hex_str(signed_digest,4));
625
  if (memcmp(digest, signed_digest, DIGEST_LEN)) {
626
    log_warn(LD_DIR, "Error reading directory: signature does not match.");
627
    return -1;
628
  }
629
  return 0;
630
631
}

632
/** Given a string *<b>s</b> containing a concatenated sequence of router
633
 * descriptors, parses them and stores the result in <b>dest</b>.  All routers
634
 * are marked running and valid.  Advances *s to a point immediately
635
636
 * following the last router entry.  Ignore any trailing router entries that
 * are not complete. Returns 0 on success and -1 on failure.
637
 * DOCDOC saved_location
638
639
 */
int
640
router_parse_list_from_string(const char **s, smartlist_t *dest,
641
                              saved_location_t saved_location)
642
643
{
  routerinfo_t *router;
644
  const char *end, *cp, *start;
645

646
647
  tor_assert(s);
  tor_assert(*s);
648
  tor_assert(dest);
649

650
  start = *s;
651
652
653
  while (1) {
    *s = eat_whitespace(*s);
    /* Don't start parsing the rest of *s unless it contains a router. */
654
    if (strcmpstart(*s, "router ")!=0)
655
656
      break;
    if ((end = strstr(*s+1, "\nrouter "))) {
657
      cp = end;
658
659
      end++;
    } else if ((end = strstr(*s+1, "\ndirectory-signature"))) {
660
      cp = end;
661
662
      end++;
    } else {
663
664
665
666
667
668
669
670
671
      cp = end = *s+strlen(*s);
    }

    while (cp > *s && (!*cp || TOR_ISSPACE(*cp)))
      --cp;
    /* cp now points to the last non-space character in this descriptor. */

    while (cp > *s  && *cp != '\n')
      --cp;
Roger Dingledine's avatar
Roger Dingledine committed
672
    /* cp now points to the first \n before the last non-blank line in this
673
674
675
     * descriptor */

    if (strcmpstart(cp, "\n-----END SIGNATURE-----\n")) {
676
      log_info(LD_DIR, "Ignoring truncated router descriptor.");
677
      *s = end;
678
      continue;
679
680
    }

681
682
    router = router_parse_entry_from_string(*s, end,
                                            saved_location != SAVED_IN_CACHE);
683

684
    if (!router) {
685
      log_warn(LD_DIR, "Error reading router; skipping");
686
      *s = end;
687
688
      continue;
    }
689
690
    if (saved_location != SAVED_NOWHERE) {
      router->cache_info.saved_location = saved_location;
691
692
      router->cache_info.saved_offset = *s - start;
    }
693
    *s = end;
694
    smartlist_add(dest, router);
695
696
697
698
699
700
701
702
  }

  return 0;
}

/** Helper function: reads a single router entry from *<b>s</b> ...
 * *<b>end</b>.  Mallocs a new router and returns it if all goes well, else
 * returns NULL.
703
 * DOCDOC cache_copy
704
 */
705
routerinfo_t *
706
707
router_parse_entry_from_string(const char *s, const char *end,
                               int cache_copy)
708
{
709
710
711
712
713
714
  routerinfo_t *router = NULL;
  char signed_digest[128];
  char digest[128];
  smartlist_t *tokens = NULL, *exit_policy_tokens = NULL;
  directory_token_t *tok;
  int t;
715
  struct in_addr in;
716
717
718
719
720

  if (!end) {
    end = s + strlen(s);
  }

721
722
723
724
  /* point 'end' to a point immediately after the final newline. */
  while (end > s+2 && *(end-1) == '\n' && *(end-2) == '\n')
    --end;

725
  if (router_get_router_hash(s, digest) < 0) {
726
    log_warn(LD_DIR, "Couldn't compute router hash.");
727
728
729
    return NULL;
  }
  tokens = smartlist_create();
730
  if (tokenize_string(s,end,tokens,RTR)) {
731
    log_warn(LD_DIR, "Error tokeninzing router descriptor.");
Roger Dingledine's avatar
Roger Dingledine committed
732
    goto err;
733
734
735
  }

  if (smartlist_len(tokens) < 2) {
736
    log_warn(LD_DIR, "Impossibly short router descriptor.");
737
738
739
    goto err;
  }
  if ((tok = find_first_by_keyword(tokens, _UNRECOGNIZED))) {
740
    log_warn(LD_DIR,
741
             "Unrecognized critical keyword %s; skipping descriptor. "
742
             "(It may be from another version of Tor.)",
743
             escaped(tok->args[0]));
744
745
746
747
748
    goto err;
  }

  tok = smartlist_get(tokens,0);
  if (tok->tp != K_ROUTER) {
749
    log_warn(LD_DIR,"Entry does not start with \"router\"");
750
751
752
753
    goto err;
  }

  router = tor_malloc_zero(sizeof(routerinfo_t));
754
  router->routerlist_index = -1;
755
756
  if (cache_copy)
    router->cache_info.signed_descriptor_body = tor_strndup(s, end-s);
757
758
  router->cache_info.signed_descriptor_len = end-s;
  memcpy(router->cache_info.signed_descriptor_digest, digest, DIGEST_LEN);
759

760
  if (tok->n_args >= 5) {
761
    router->nickname = tor_strdup(tok->args[0]);
762
    if (!is_legal_nickname(router->nickname)) {
763
      log_warn(LD_DIR,"Router nickname is invalid");
764
765
766
      goto err;
    }
    router->address = tor_strdup(tok->args[1]);
767
    if (!tor_inet_aton(router->address, &in)) {
768
      log_warn(LD_DIR,"Router address is not an IP.");
769
770
771
      goto err;
    }
    router->addr = ntohl(in.s_addr);
772

773
774
775
776
    router->or_port =
      (uint16_t) tor_parse_long(tok->args[2],10,0,65535,NULL,NULL);
    router->dir_port =
      (uint16_t) tor_parse_long(tok->args[4],10,0,65535,NULL,NULL);
777
  } else {
778
    log_warn(LD_DIR,"Wrong # of arguments to \"router\" (%d)",tok->n_args);
779
780
781
782
    goto err;
  }

  tok = find_first_by_keyword(tokens, K_BANDWIDTH);
783
  if (!tok) {
784
    log_warn(LD_DIR,"No bandwidth declared; failing.");
785
    goto err;
786
  } else {
787
    if (tok->n_args < 3) {
788
789
      log_warn(LD_DIR,
               "Not enough arguments to \"bandwidth\" in server descriptor.");
790
791
      goto err;
    }
792
793
794
795
796
797
    router->bandwidthrate =
      tor_parse_long(tok->args[0],10,0,INT_MAX,NULL,NULL);
    router->bandwidthburst =
      tor_parse_long(tok->args[1],10,0,INT_MAX,NULL,NULL);
    router->bandwidthcapacity =
      tor_parse_long(tok->args[2],10,0,INT_MAX,NULL,NULL);
798
799
  }

Nick Mathewson's avatar
Nick Mathewson committed
800
801
  if ((tok = find_first_by_keyword(tokens, K_UPTIME))) {
    if (tok->n_args != 1) {
802
      log_warn(LD_DIR, "Unrecognized number of args on K_UPTIME; skipping.");
Nick Mathewson's avatar
Nick Mathewson committed
803
    } else {
804
      router->uptime = tor_parse_long(tok->args[0],10,0,LONG_MAX,NULL,NULL);
Nick Mathewson's avatar
Nick Mathewson committed
805
806
807
    }
  }

808
809
  if ((tok = find_first_by_keyword(tokens, K_HIBERNATING))) {
    if (tok->n_args < 1) {
810
      log_warn(LD_DIR, "Too few args on 'hibernating' keyword. Skipping.");
811
812
813
814
815
816
    } else {
      router->is_hibernating
        = (tor_parse_long(tok->args[0],10,0,LONG_MAX,NULL,NULL) != 0);
    }
  }

817
  if (!(tok = find_first_by_keyword(tokens, K_PUBLISHED))) {
818
    log_warn(LD_DIR, "Missing published time on router descriptor"); goto err;
819
820
  }
  tor_assert(tok->n_args == 1);
821
  if (parse_iso_time(tok->args[0], &router->cache_info.published_on) < 0)
822
    goto err;
823
824

  if (!(tok = find_first_by_keyword(tokens, K_ONION_KEY))) {
825
    log_warn(LD_DIR, "Missing onion key"); goto err;
826
  }
827
  if (crypto_pk_keysize(tok->key) != PK_BYTES) {
828
829
    log_warn(LD_DIR, "Wrong size on onion key: %d bits!",
             (int)crypto_pk_keysize(tok->key)*8);
830
831
    goto err;
  }
832
833
834
835
  router->onion_pkey = tok->key;
  tok->key = NULL; /* Prevent free */

  if (!(tok = find_first_by_keyword(tokens, K_SIGNING_KEY))) {
836
    log_warn(LD_DIR, "Missing identity key"); goto err;
837
  }
838
  if (crypto_pk_keysize(tok->key) != PK_BYTES) {
839
    log_warn(LD_DIR, "Wrong size on identity key: %d bits!",
840
           (int)crypto_pk_keysize(tok->key)*8);
841
842
    goto err;
  }
843
844
  router->identity_pkey = tok->key;
  tok->key = NULL; /* Prevent free */
845
846
  if (crypto_pk_get_digest(router->identity_pkey,
                           router->cache_info.identity_digest)) {
847
    log_warn(LD_DIR, "Couldn't calculate key digest"); goto err;
848
  }
849

850
851
852
853
  if ((tok = find_first_by_keyword(tokens, K_FINGERPRINT))) {
    /* If there's a fingerprint line, it must match the identity digest. */
    char d[DIGEST_LEN];
    if (tok->n_args < 1) {
854
      log_warn(LD_DIR, "Too few arguments to router fingerprint");
855
856
857
858
      goto err;
    }
    tor_strstrip(tok->args[0], " ");
    if (base16_decode(d, DIGEST_LEN, tok->args[0], strlen(tok->args[0]))) {
859
      log_warn(LD_DIR, "Couldn't decode router fingerprint %s",
860
               escaped(tok->args[0]));
861
862
863
      goto err;
    }
    if (memcmp(d,router->cache_info.identity_digest, DIGEST_LEN)!=0) {
864
865
      log_warn(LD_DIR, "Fingerprint '%s' does not match identity digest.",
               tok->args[0]);
866
867
868
869
      goto err;
    }
  }

870
871
872
873
  if ((tok = find_first_by_keyword(tokens, K_PLATFORM))) {
    router->platform = tor_strdup(tok->args[0]);
  }

874
875
876
877
  if ((tok = find_first_by_keyword(tokens, K_CONTACT))) {
    router->contact_info = tor_strdup(tok->args[0]);
  }

878
879
880
881
882
883
884
  if ((tok = find_first_by_keyword(tokens, K_EVENTDNS))) {
    router->has_old_dnsworkers = tok->n_args && !strcmp(tok->args[0], "0");
  } else if (router->platform) {
    if (! tor_version_as_new_as(router->platform, "0.1.2.2-alpha"))
      router->has_old_dnsworkers = 1;
  }

885
886
887
  exit_policy_tokens = find_all_exitpolicy(tokens);
  SMARTLIST_FOREACH(exit_policy_tokens, directory_token_t *, t,
                    if (router_add_exit_policy(router,t)<0) {
888
                      log_warn(LD_DIR,"Error in exit policy");
Roger Dingledine's avatar
Roger Dingledine committed
889
890
                      goto err;
                    });
891

892
893
894
895
896
  if ((tok = find_first_by_keyword(tokens, K_FAMILY)) && tok->n_args) {
    int i;
    router->declared_family = smartlist_create();
    for (i=0;i<tok->n_args;++i) {
      if (!is_legal_nickname_or_hexdigest(tok->args[i])) {
897
898
        log_warn(LD_DIR, "Illegal nickname %s in family line",
                 escaped(tok->args[i]));
899
900
901
902
903
        goto err;
      }
      smartlist_add(router->declared_family, tor_strdup(tok->args[i]));
    }
  }
904

905
  if (!(tok = find_first_by_keyword(tokens, K_ROUTER_SIGNATURE))) {
906
    log_warn(LD_DIR, "Missing router signature");
Roger Dingledine's avatar
Roger Dingledine committed
907
    goto err;
908
909
  }
  if (strcmp(tok->object_type, "SIGNATURE") || tok->object_size != 128) {
910
    log_warn(LD_DIR, "Bad object type or length on router signature");
911
912
    goto err;
  }
913
914
  if ((t=crypto_pk_public_checksig(router->identity_pkey, signed_digest,
                                   tok->object_body, 128)) != 20) {
915
    log_warn(LD_DIR, "Invalid signature %d",t);
Roger Dingledine's avatar
Roger Dingledine committed
916
    goto err;
917
  }
918
  if (memcmp(digest, signed_digest, DIGEST_LEN)) {
919
    log_warn(LD_DIR, "Mismatched signature");
Roger Dingledine's avatar
Roger Dingledine committed
920
    goto err;
921
922
  }

923
  if (!router->or_port) {
924
    log_warn(LD_DIR,"or_port unreadable or 0. Failing.");
925
926
927
    goto err;
  }
  if (!router->bandwidthrate) {
928
    log_warn(LD_DIR,"bandwidthrate unreadable or 0. Failing.");
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
    goto err;
  }
  if (!router->platform) {
    router->platform = tor_strdup("<unknown>");
  }

  goto done;

 err:
  routerinfo_free(router);
  router = NULL;
 done:
  if (tokens) {
    SMARTLIST_FOREACH(tokens, directory_token_t *, tok, token_free(tok));
    smartlist_free(tokens);
  }
  if (exit_policy_tokens) {
    smartlist_free(exit_policy_tokens);
  }
  return router;
}

951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
/** Helper: given a string <b>s</b>, return the start of the next router-status
 * object (starting with "r " at the start of a line).  If none is found,
 * return the start of the next directory signature.  If none is found, return
 * the end of the string. */
static INLINE const char *
find_start_of_next_routerstatus(const char *s)
{
  const char *eos = strstr(s, "\nr ");
  if (!eos)
    eos = strstr(s, "\ndirectory-signature");
  if (eos)
    return eos+1;
  else
    return s + strlen(s);
}

/** Given a string at *<b>s</b>, containing a routerstatus object, and an
 * empty smartlist at <b>tokens</b>, parse and return the first router status
 * object in the string, and advance *<b>s</b> to just after the end of the
 * router status.  Return NULL and advance *<b>s</b> on error. */
static routerstatus_t *
routerstatus_parse_entry_from_string(const char **s, smartlist_t *tokens)
{
  const char *eos;
  routerstatus_t *rs = NULL;
  directory_token_t *tok;
  char timebuf[ISO_TIME_LEN+1];
  struct in_addr in;

  tor_assert(tokens);

  eos = find_start_of_next_routerstatus(*s);

  if (tokenize_string(*s, eos, tokens, RTRSTATUS)) {
985
    log_warn(LD_DIR, "Error tokenizing router status");
986
987
988
    goto err;
  }
  if (smartlist_len(tokens) < 1) {
989
    log_warn(LD_DIR, "Impossibly short router status");
990
991
992
    goto err;
  }
  if ((tok = find_first_by_keyword(tokens, _UNRECOGNIZED))) {
993
994
    log_warn(LD_DIR, "Unrecognized keyword %s in router status; skipping.",
             escaped(tok->args[0]));
995
996
997
    goto err;
  }
  if (!(tok = find_first_by_keyword(tokens, K_R))) {
998
    log_warn(LD_DIR, "Missing 'r' keywork in router status; skipping.");
999
1000
    goto err;
  }
For faster browsing, not all history is shown. View entire blame