make sure router descriptor doesnt eat the directory-signature
[tor.git] / src / or / dirserv.c
blob73ff278e0c7ea14171e711bb8fac3776934550e1
1 /* Copyright 2001,2002,2003 Roger Dingledine, Matej Pfajfar. */
2 /* See LICENSE for licensing information */
3 /* $Id$ */
5 #include "or.h"
7 extern or_options_t options; /* command-line and config-file options */
9 static int the_directory_is_dirty = 1;
10 static char *the_directory = NULL;
11 static int the_directory_len = -1;
13 /************** Fingerprint handling code ************/
15 typedef struct fingerprint_entry_t {
16 char *nickname;
17 char *fingerprint;
18 } fingerprint_entry_t;
20 static fingerprint_entry_t fingerprint_list[MAX_ROUTERS_IN_DIR];
21 static int n_fingerprints = 0;
23 static void
24 add_fingerprint_to_dir(const char *nickname, const char *fp)
26 int i;
27 for (i = 0; i < n_fingerprints; ++i) {
28 if (!strcasecmp(fingerprint_list[i].nickname,nickname)) {
29 free(fingerprint_list[i].fingerprint);
30 fingerprint_list[i].fingerprint = strdup(fp);
31 return;
34 fingerprint_list[n_fingerprints].nickname = strdup(nickname);
35 fingerprint_list[n_fingerprints].fingerprint = strdup(fp);
36 ++n_fingerprints;
39 int
40 dirserv_add_own_fingerprint(const char *nickname, crypto_pk_env_t *pk)
42 char fp[FINGERPRINT_LEN+1];
43 if (crypto_pk_get_fingerprint(pk, fp)<0) {
44 log_fn(LOG_ERR, "Error computing fingerprint");
45 return -1;
47 add_fingerprint_to_dir(nickname, fp);
48 return 0;
51 /* return 0 on success, -1 on failure */
52 int
53 dirserv_parse_fingerprint_file(const char *fname)
55 FILE *file;
56 char line[FINGERPRINT_LEN+MAX_NICKNAME_LEN+20+1];
57 char *nickname, *fingerprint;
58 fingerprint_entry_t fingerprint_list_tmp[MAX_ROUTERS_IN_DIR];
59 int n_fingerprints_tmp = 0;
60 int i, result;
62 if(!(file = fopen(fname, "r"))) {
63 log_fn(LOG_WARNING, "Cannot open fingerprint file %s", fname);
64 return -1;
66 while( (result=parse_line_from_file(line, sizeof(line),file,&nickname,&fingerprint)) > 0) {
67 if (strlen(nickname) > MAX_NICKNAME_LEN) {
68 log(LOG_WARNING, "Nickname %s too long in fingerprint file. Skipping.", nickname);
69 continue;
71 if(strlen(fingerprint) != FINGERPRINT_LEN ||
72 !crypto_pk_check_fingerprint_syntax(fingerprint)) {
73 log_fn(LOG_WARNING, "Invalid fingerprint (nickname %s, fingerprint %s). Skipping.",
74 nickname, fingerprint);
75 continue;
77 for (i = 0; i < n_fingerprints_tmp; ++i) {
78 if (0==strcasecmp(fingerprint_list_tmp[i].nickname, nickname)) {
79 log(LOG_WARNING, "Duplicate nickname %s. Skipping.",nickname);
80 break; /* out of the for. the 'if' below means skip to the next line. */
83 if(i == n_fingerprints_tmp) { /* not a duplicate */
84 fingerprint_list_tmp[n_fingerprints_tmp].nickname = strdup(nickname);
85 fingerprint_list_tmp[n_fingerprints_tmp].fingerprint = strdup(fingerprint);
86 ++n_fingerprints_tmp;
89 fclose(file);
90 if(result == 0) { /* eof; replace the global fingerprints list. */
91 dirserv_free_fingerprint_list();
92 memcpy(fingerprint_list, fingerprint_list_tmp,
93 sizeof(fingerprint_entry_t)*n_fingerprints_tmp);
94 n_fingerprints = n_fingerprints_tmp;
95 return 0;
97 /* error */
98 log_fn(LOG_WARNING, "Error reading from fingerprint file");
99 for (i = 0; i < n_fingerprints_tmp; ++i) {
100 free(fingerprint_list_tmp[i].nickname);
101 free(fingerprint_list_tmp[i].fingerprint);
103 return -1;
106 /* return 1 if router's identity and nickname match. */
108 dirserv_router_fingerprint_is_known(const routerinfo_t *router)
110 int i;
111 fingerprint_entry_t *ent =NULL;
112 char fp[FINGERPRINT_LEN+1];
114 log_fn(LOG_DEBUG, "%d fingerprints known.", n_fingerprints);
115 for (i=0;i<n_fingerprints;++i) {
116 log_fn(LOG_DEBUG,"%s vs %s", router->nickname, fingerprint_list[i].nickname);
117 if (!strcasecmp(router->nickname,fingerprint_list[i].nickname)) {
118 ent = &fingerprint_list[i];
119 break;
123 if (!ent) { /* No such server known */
124 log_fn(LOG_WARNING,"no fingerprint found for %s",router->nickname);
125 return 0;
127 if (crypto_pk_get_fingerprint(router->identity_pkey, fp)) {
128 log_fn(LOG_WARNING,"error computing fingerprint");
129 return 0;
131 if (0==strcasecmp(ent->fingerprint, fp)) {
132 log_fn(LOG_DEBUG,"good fingerprint for %s",router->nickname);
133 return 1; /* Right fingerprint. */
134 } else {
135 log_fn(LOG_WARNING,"mismatched fingerprint for %s",router->nickname);
136 return 0; /* Wrong fingerprint. */
140 void
141 dirserv_free_fingerprint_list()
143 int i;
144 for (i = 0; i < n_fingerprints; ++i) {
145 free(fingerprint_list[i].nickname);
146 free(fingerprint_list[i].fingerprint);
148 n_fingerprints = 0;
152 * Descriptor list
154 typedef struct descriptor_entry_t {
155 char *nickname;
156 time_t published;
157 size_t desc_len;
158 char *descriptor;
159 } descriptor_entry_t;
161 static descriptor_entry_t *descriptor_list[MAX_ROUTERS_IN_DIR];
162 static int n_descriptors = 0;
164 static void free_descriptor_entry(descriptor_entry_t *desc)
166 if (desc->descriptor)
167 free(desc->descriptor);
168 if (desc->nickname)
169 free(desc->nickname);
170 free(desc);
173 void
174 dirserv_free_descriptors()
176 int i;
177 for (i = 0; i < n_descriptors; ++i) {
178 free_descriptor_entry(descriptor_list[i]);
180 n_descriptors = 0;
183 /* Return 0 if descriptor added; -1 if descriptor rejected. Updates *desc
184 * to point after the descriptor if the descriptor is OK.
187 dirserv_add_descriptor(const char **desc)
189 descriptor_entry_t **desc_ent_ptr;
190 routerinfo_t *ri = NULL;
191 int i;
192 char *start, *end;
193 char *desc_tmp = NULL, *cp;
194 size_t desc_len;
196 start = strstr(*desc, "router ");
197 if (!start) {
198 log(LOG_WARNING, "no descriptor found.");
199 goto err;
201 if ((end = strstr(start+6, "\nrouter "))) {
202 ++end; /* Include NL. */
203 } else if ((end = strstr(start+6, "\ndirectory-signature"))) {
204 ++end;
205 } else {
206 end = start+strlen(start);
208 desc_len = end-start;
209 cp = desc_tmp = tor_malloc(desc_len+1);
210 strncpy(desc_tmp, start, desc_len);
211 desc_tmp[desc_len]='\0';
213 /* Check: is the descriptor syntactically valid? */
214 ri = router_get_entry_from_string(&cp);
215 if (!ri) {
216 log(LOG_WARNING, "Couldn't parse descriptor");
217 goto err;
219 free(desc_tmp); desc_tmp = NULL;
220 /* Okay. Now check whether the fingerprint is recognized. */
221 if (!dirserv_router_fingerprint_is_known(ri)) {
222 log(LOG_WARNING, "Identity is unrecognized for descriptor");
223 goto err;
225 /* Do we already have an entry for this router? */
226 desc_ent_ptr = NULL;
227 for (i = 0; i < n_descriptors; ++i) {
228 if (!strcasecmp(ri->nickname, descriptor_list[i]->nickname)) {
229 desc_ent_ptr = &descriptor_list[i];
230 break;
233 if (desc_ent_ptr) {
234 /* if so, decide whether to update it. */
235 if ((*desc_ent_ptr)->published > ri->published_on) {
236 /* We already have a newer descriptor */
237 log_fn(LOG_INFO,"We already have a newer desc for nickname %s. Not adding.",ri->nickname);
238 /* This isn't really an error; return. */
239 if (desc_tmp) free(desc_tmp);
240 if (ri) routerinfo_free(ri);
241 *desc = end;
242 return 0;
244 /* We don't have a newer one; we'll update this one. */
245 free_descriptor_entry(*desc_ent_ptr);
246 } else {
247 /* Add this at the end. */
248 desc_ent_ptr = &descriptor_list[n_descriptors++];
251 (*desc_ent_ptr) = tor_malloc(sizeof(descriptor_entry_t));
252 (*desc_ent_ptr)->nickname = ri->nickname;
253 (*desc_ent_ptr)->published = ri->published_on;
254 (*desc_ent_ptr)->desc_len = desc_len;
255 (*desc_ent_ptr)->descriptor = tor_malloc(desc_len+1);
256 strncpy((*desc_ent_ptr)->descriptor, start, desc_len);
257 (*desc_ent_ptr)->descriptor[desc_len] = '\0';
258 *desc = end;
259 the_directory_is_dirty = 1;
261 routerinfo_free(ri);
262 return 0;
263 err:
264 if (desc_tmp)
265 free(desc_tmp);
266 if (ri)
267 routerinfo_free(ri);
269 return -1;
272 void
273 directory_set_dirty()
275 the_directory_is_dirty = 1;
278 int
279 dirserv_init_from_directory_string(const char *dir)
281 const char *cp = dir;
282 while(1) {
283 cp = strstr(cp, "\nrouter ");
284 if (!cp) break;
285 ++cp;
286 if (dirserv_add_descriptor(&cp)) {
287 return -1;
289 --cp; /*Back up to newline.*/
291 return 0;
295 dirserv_dump_directory_to_string(char *s, int maxlen,
296 crypto_pk_env_t *private_key)
298 char *cp, *eos;
299 char digest[20];
300 char signature[128];
301 char published[33];
302 time_t published_on;
303 int i;
304 eos = s+maxlen;
306 if (list_running_servers(&cp))
307 return -1;
308 published_on = time(NULL);
309 strftime(published, 32, "%Y-%m-%d %H:%M:%S", gmtime(&published_on));
310 snprintf(s, maxlen,
311 "signed-directory\n"
312 "published %s\n"
313 "recommended-software "RECOMMENDED_SOFTWARE_VERSIONS"\n"
314 "running-routers %s\n", published, cp);
315 free(cp);
316 i = strlen(s);
317 cp = s+i;
319 for (i = 0; i < n_descriptors; ++i) {
320 strncat(cp, descriptor_list[i]->descriptor, descriptor_list[i]->desc_len);
321 cp += descriptor_list[i]->desc_len;
322 assert(!*cp);
324 /* These multiple strlen calls are inefficient, but dwarfed by the RSA
325 signature.
327 i = strlen(s);
328 strncat(s, "directory-signature\n", maxlen-i);
329 i = strlen(s);
330 cp = s + i;
332 if (router_get_dir_hash(s,digest)) {
333 log_fn(LOG_WARNING,"couldn't compute digest");
334 return -1;
336 if (crypto_pk_private_sign(private_key, digest, 20, signature) < 0) {
337 log_fn(LOG_WARNING,"couldn't sign digest");
338 return -1;
340 log(LOG_DEBUG,"generated directory digest begins with %02x:%02x:%02x:%02x",
341 ((int)digest[0])&0xff,((int)digest[1])&0xff,
342 ((int)digest[2])&0xff,((int)digest[3])&0xff);
344 strncpy(cp,
345 "-----BEGIN SIGNATURE-----\n", maxlen-i);
347 i = strlen(s);
348 cp = s+i;
349 if (base64_encode(cp, maxlen-i, signature, 128) < 0) {
350 log_fn(LOG_WARNING,"couldn't base64-encode signature");
351 return -1;
354 i = strlen(s);
355 cp = s+i;
356 strncat(cp, "-----END SIGNATURE-----\n", maxlen-i);
357 i = strlen(s);
358 if (i == maxlen) {
359 log_fn(LOG_WARNING,"tried to exceed string length.");
360 return -1;
363 return 0;
366 size_t dirserv_get_directory(const char **directory)
368 char *new_directory;
369 char filename[512];
370 if (the_directory_is_dirty) {
371 new_directory = tor_malloc(MAX_DIR_SIZE);
372 if (dirserv_dump_directory_to_string(new_directory, MAX_DIR_SIZE,
373 get_identity_key())) {
374 log(LOG_WARNING, "Error creating directory.");
375 free(new_directory);
376 return 0;
378 if (the_directory)
379 free(the_directory);
380 the_directory = new_directory;
381 the_directory_len = strlen(the_directory);
382 log_fn(LOG_INFO,"New directory (size %d):\n%s",the_directory_len,
383 the_directory);
384 the_directory_is_dirty = 0;
385 /* Now read the directory we just made in order to update our own
386 * router lists. This does more signature checking than is strictly
387 * necessary, but safe is better than sorry. */
388 new_directory = strdup(the_directory);
389 /* use a new copy of the dir, since get_dir_from_string scribbles on it */
390 if (router_get_dir_from_string(new_directory, get_identity_key())) {
391 log_fn(LOG_ERR, "We just generated a directory we can't parse. Dying.");
392 exit(0);
394 free(new_directory);
395 sprintf(filename,"%s/cached-directory", options.DataDirectory);
396 if(write_str_to_file(filename,the_directory) < 0) {
397 log_fn(LOG_WARNING, "Couldn't write cached directory to disk. Ignoring.");
399 } else {
400 log(LOG_INFO,"Directory still clean, reusing.");
402 *directory = the_directory;
403 return the_directory_len;