clean up directory.c API
[tor.git] / src / or / directory.c
blobaa97c7c41954ba7dae90fe7ccfbb4d0d6a761d47
1 /* Copyright 2001,2002,2003 Roger Dingledine. */
2 /* See LICENSE for licensing information */
3 /* $Id$ */
5 #include "or.h"
7 /**
8 * \file directory.c
9 * \brief Implement directory HTTP protocol.
10 **/
12 /* In-points to directory.c:
14 * - directory_post_to_dirservers(), called from
15 * router_upload_dir_desc_to_dirservers() in router.c
16 * upload_service_descriptor() in rendservice.c
17 * - directory_get_from_dirserver(), called from
18 * rend_client_refetch_renddesc() in rendclient.c
19 * run_scheduled_events() in main.c
20 * do_hup() in main.c
21 * - connection_dir_process_inbuf(), called from
22 * connection_process_inbuf() in connection.c
23 * - connection_dir_finished_flushing(), called from
24 * connection_finished_flushing() in connection.c
25 * - connection_dir_finished_connecting(), called from
26 * connection_finished_connecting() in connection.c
29 static void
30 directory_initiate_command(routerinfo_t *router, uint8_t purpose,
31 const char *payload, int payload_len);
32 static void directory_send_command(connection_t *conn, int purpose,
33 const char *payload, int payload_len);
34 static int directory_handle_command(connection_t *conn);
36 /********* START VARIABLES **********/
38 extern or_options_t options; /* command-line and config-file options */
40 /** URL for publishing rendezvous descriptors. */
41 char rend_publish_string[] = "/rendezvous/publish";
42 /** Prefix for downloading rendezvous descriptors. */
43 char rend_fetch_url[] = "/rendezvous/";
45 #define MAX_HEADERS_SIZE 10000
46 #define MAX_BODY_SIZE 500000
48 /********* END VARIABLES ************/
50 /** Start a connection to every known directory server, using
51 * connection purpose 'purpose' and uploading the payload 'payload'
52 * (length 'payload_len'). The purpose should be one of
53 * 'DIR_PURPOSE_UPLOAD_DIR' or 'DIR_PURPOSE_UPLOAD_RENDDESC'.
55 void
56 directory_post_to_dirservers(uint8_t purpose, const char *payload,
57 int payload_len)
59 int i;
60 routerinfo_t *router;
61 routerlist_t *rl;
63 router_get_routerlist(&rl);
64 if(!rl)
65 return;
67 for(i=0; i < smartlist_len(rl->routers); i++) {
68 router = smartlist_get(rl->routers, i);
69 if(router->dir_port > 0)
70 directory_initiate_command(router, purpose, payload, payload_len);
74 /** Start a connection to a random running directory server, using
75 * connection purpose 'purpose' requesting 'payload' (length
76 * 'payload_len'). The purpose should be one of
77 * 'DIR_PURPOSE_FETCH_DIR' or 'DIR_PURPOSE_FETCH_RENDDESC'.
79 void
80 directory_get_from_dirserver(uint8_t purpose, const char *payload,
81 int payload_len)
83 directory_initiate_command(router_pick_directory_server(),
84 purpose, payload, payload_len);
87 /** Launch a new connection to the directory server <b>router</b> to upload or
88 * download a service or rendezvous descriptor. <b>purpose</b> determines what
89 * kind of directory connection we're launching, and must be one of
90 * DIR_PURPOSE_{FETCH|UPLOAD}_{DIR|RENDDESC}.
92 * When uploading, <b>payload</b> and <b>payload_len</b> determine the content
93 * of the HTTP post. When fetching a rendezvous descriptor, <b>payload</b>
94 * and <b>payload_len</b> are the service ID we want to fetch.
96 static void
97 directory_initiate_command(routerinfo_t *router, uint8_t purpose,
98 const char *payload, int payload_len)
100 connection_t *conn;
102 switch (purpose)
104 case DIR_PURPOSE_FETCH_DIR:
105 log_fn(LOG_DEBUG,"initiating directory fetch");
106 break;
107 case DIR_PURPOSE_FETCH_RENDDESC:
108 log_fn(LOG_DEBUG,"initiating hidden-service descriptor fetch");
109 break;
110 case DIR_PURPOSE_UPLOAD_DIR:
111 log_fn(LOG_DEBUG,"initiating server descriptor upload");
112 break;
113 case DIR_PURPOSE_UPLOAD_RENDDESC:
114 log_fn(LOG_DEBUG,"initiating hidden-service descriptor upload");
115 break;
116 default:
117 log_fn(LOG_ERR, "Unrecognized directory connection purpose.");
118 tor_assert(0);
121 if (!router) { /* i guess they didn't have one in mind for me to use */
122 log_fn(LOG_WARN,"No running dirservers known. Not trying. (purpose %d)", purpose);
123 return;
126 conn = connection_new(CONN_TYPE_DIR);
128 /* set up conn so it's got all the data we need to remember */
129 conn->addr = router->addr;
130 conn->port = router->dir_port;
131 conn->address = tor_strdup(router->address);
132 conn->nickname = tor_strdup(router->nickname);
133 tor_assert(router->identity_pkey);
134 conn->identity_pkey = crypto_pk_dup_key(router->identity_pkey);
136 conn->purpose = purpose;
138 /* give it an initial state */
139 conn->state = DIR_CONN_STATE_CONNECTING;
141 if(purpose == DIR_PURPOSE_FETCH_DIR ||
142 purpose == DIR_PURPOSE_UPLOAD_DIR) {
143 /* then we want to connect directly */
144 switch(connection_connect(conn, conn->address, conn->addr, conn->port)) {
145 case -1:
146 router_mark_as_down(conn->nickname); /* don't try him again */
147 connection_free(conn);
148 return;
149 case 1:
150 conn->state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */
151 /* fall through */
152 case 0:
153 /* queue the command on the outbuf */
154 directory_send_command(conn, purpose, payload, payload_len);
156 connection_watch_events(conn, POLLIN | POLLOUT | POLLERR);
157 /* writable indicates finish, readable indicates broken link,
158 error indicates broken link in windowsland. */
160 } else { /* we want to connect via tor */
161 /* make an AP connection
162 * populate it and add it at the right state
163 * socketpair and hook up both sides
165 conn->s = connection_ap_make_bridge(conn->address, conn->port);
166 if(conn->s < 0) {
167 log_fn(LOG_WARN,"Making AP bridge to dirserver failed.");
168 connection_mark_for_close(conn);
169 return;
172 conn->state = DIR_CONN_STATE_CLIENT_SENDING;
173 connection_add(conn);
174 /* queue the command on the outbuf */
175 directory_send_command(conn, purpose, payload, payload_len);
176 connection_watch_events(conn, POLLIN | POLLOUT | POLLERR);
180 /** Queue an appropriate HTTP command on conn-\>outbuf. The args
181 * <b>purpose</b>, <b>payload</b>, and <b>payload_len</b> are as in
182 * directory_initiate_command.
184 static void directory_send_command(connection_t *conn, int purpose,
185 const char *payload, int payload_len) {
186 char fetchstring[] = "GET / HTTP/1.0\r\n\r\n";
187 char tmp[8192];
189 tor_assert(conn && conn->type == CONN_TYPE_DIR);
191 switch(purpose) {
192 case DIR_PURPOSE_FETCH_DIR:
193 tor_assert(payload == NULL);
194 connection_write_to_buf(fetchstring, strlen(fetchstring), conn);
195 break;
196 case DIR_PURPOSE_UPLOAD_DIR:
197 tor_assert(payload);
198 snprintf(tmp, sizeof(tmp), "POST / HTTP/1.0\r\nContent-Length: %d\r\n\r\n",
199 payload_len);
200 connection_write_to_buf(tmp, strlen(tmp), conn);
201 connection_write_to_buf(payload, payload_len, conn);
202 break;
203 case DIR_PURPOSE_FETCH_RENDDESC:
204 tor_assert(payload);
206 /* this must be true or we wouldn't be doing the lookup */
207 tor_assert(payload_len <= REND_SERVICE_ID_LEN);
208 /* This breaks the function abstraction. */
209 memcpy(conn->rend_query, payload, payload_len);
210 conn->rend_query[payload_len] = 0;
212 snprintf(tmp, sizeof(tmp), "GET %s%s HTTP/1.0\r\n\r\n", rend_fetch_url, payload);
213 connection_write_to_buf(tmp, strlen(tmp), conn);
214 break;
215 case DIR_PURPOSE_UPLOAD_RENDDESC:
216 tor_assert(payload);
217 snprintf(tmp, sizeof(tmp),
218 "POST %s HTTP/1.0\r\nContent-Length: %d\r\n\r\n", rend_publish_string, payload_len);
219 connection_write_to_buf(tmp, strlen(tmp), conn);
220 /* could include nuls, need to write it separately */
221 connection_write_to_buf(payload, payload_len, conn);
222 break;
226 /** Parse an HTTP request string <b>headers</b> of the form "\%s \%s HTTP/1..."
227 * If it's well-formed, point *<b>url</b> to the second \%s,
228 * null-terminate it (this modifies headers!) and return 0.
229 * Otherwise, return -1.
231 static int
232 parse_http_url(char *headers, char **url)
234 char *s, *tmp;
236 s = (char *)eat_whitespace_no_nl(headers);
237 if (!*s) return -1;
238 s = (char *)find_whitespace(s); /* get past GET/POST */
239 if (!*s) return -1;
240 s = (char *)eat_whitespace_no_nl(s);
241 if (!*s) return -1;
242 tmp = s; /* this is it, assuming it's valid */
243 s = (char *)find_whitespace(s);
244 if (!*s) return -1;
245 *s = 0;
246 *url = tmp;
247 return 0;
250 /** Parse an HTTP response string <b>headers</b> of the form
251 * "HTTP/1.\%d \%d\%s\r\n...".
252 * If it's well-formed, assign *<b>code</b>, point *<b>message</b> to the first
253 * non-space character after code if there is one and message is non-NULL
254 * (else leave it alone), and return 0.
255 * Otherwise, return -1.
257 static int
258 parse_http_response(char *headers, int *code, char **message)
260 int n1, n2;
261 tor_assert(headers && code);
263 while(isspace((int)*headers)) headers++; /* tolerate leading whitespace */
265 if(sscanf(headers, "HTTP/1.%d %d", &n1, &n2) < 2 ||
266 (n1 != 0 && n1 != 1) ||
267 (n2 < 100 || n2 >= 600)) {
268 log_fn(LOG_WARN,"Failed to parse header '%s'",headers);
269 return -1;
271 *code = n2;
272 if(message) {
273 /* XXX should set *message correctly */
275 return 0;
278 /** Read handler for directory connections. (That's connections <em>to</em>
279 * directory servers and connections <em>at</em> directory servers.)
281 int connection_dir_process_inbuf(connection_t *conn) {
282 char *body;
283 char *headers;
284 int body_len=0;
285 int status_code;
287 tor_assert(conn && conn->type == CONN_TYPE_DIR);
289 /* Directory clients write, then read data until they receive EOF;
290 * directory servers read data until they get an HTTP command, then
291 * write their response (when it's finished flushing, they mark for
292 * close).
294 if(conn->inbuf_reached_eof) {
295 if(conn->state != DIR_CONN_STATE_CLIENT_READING) {
296 log_fn(LOG_INFO,"conn reached eof, not reading. Closing.");
297 connection_close_immediate(conn); /* it was an error; give up on flushing */
298 connection_mark_for_close(conn);
299 return -1;
302 switch(fetch_from_buf_http(conn->inbuf,
303 &headers, MAX_HEADERS_SIZE,
304 &body, &body_len, MAX_DIR_SIZE)) {
305 case -1: /* overflow */
306 log_fn(LOG_WARN,"'fetch' response too large. Failing.");
307 connection_mark_for_close(conn);
308 return -1;
309 case 0:
310 log_fn(LOG_INFO,"'fetch' response not all here, but we're at eof. Closing.");
311 connection_mark_for_close(conn);
312 return -1;
313 /* case 1, fall through */
316 if(parse_http_response(headers, &status_code, NULL) < 0) {
317 log_fn(LOG_WARN,"Unparseable headers. Closing.");
318 free(body); free(headers);
319 connection_mark_for_close(conn);
320 return -1;
323 if(conn->purpose == DIR_PURPOSE_FETCH_DIR) {
324 /* fetch/process the directory to learn about new routers. */
325 log_fn(LOG_INFO,"Received directory (size %d):\n%s", body_len, body);
326 if(status_code == 503 || body_len == 0) {
327 log_fn(LOG_INFO,"Empty directory. Ignoring.");
328 free(body); free(headers);
329 connection_mark_for_close(conn);
330 return 0;
332 if(status_code != 200) {
333 log_fn(LOG_WARN,"Received http status code %d from dirserver. Failing.",
334 status_code);
335 free(body); free(headers);
336 connection_mark_for_close(conn);
337 return -1;
339 if(router_set_routerlist_from_directory(body, conn->identity_pkey) < 0){
340 log_fn(LOG_INFO,"...but parsing failed. Ignoring.");
341 } else {
342 log_fn(LOG_INFO,"updated routers.");
344 directory_has_arrived(); /* do things we've been waiting to do */
347 if(conn->purpose == DIR_PURPOSE_UPLOAD_DIR) {
348 switch(status_code) {
349 case 200:
350 log_fn(LOG_INFO,"eof (status 200) after uploading server descriptor: finished.");
351 break;
352 case 400:
353 log_fn(LOG_WARN,"http status 400 (bad request) response from dirserver. Malformed server descriptor?");
354 break;
355 case 403:
356 log_fn(LOG_WARN,"http status 403 (unapproved server) response from dirserver. Is your clock skewed? Have you mailed us your identity fingerprint? Are you using the right key? See README.");
358 break;
359 default:
360 log_fn(LOG_WARN,"http status %d response unrecognized.", status_code);
361 break;
365 if(conn->purpose == DIR_PURPOSE_FETCH_RENDDESC) {
366 log_fn(LOG_INFO,"Received rendezvous descriptor (size %d, status code %d)",
367 body_len, status_code);
368 switch(status_code) {
369 case 200:
370 if(rend_cache_store(body, body_len) < 0) {
371 log_fn(LOG_WARN,"Failed to store rendezvous descriptor.");
372 /* alice's ap_stream will notice when connection_mark_for_close
373 * cleans it up */
374 } else {
375 /* success. notify pending connections about this. */
376 rend_client_desc_fetched(conn->rend_query, 1);
377 conn->purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
379 break;
380 case 404:
381 /* not there. pending connections will be notified when
382 * connection_mark_for_close cleans it up. */
383 break;
384 case 400:
385 log_fn(LOG_WARN,"http status 400 (bad request). Dirserver didn't like our rendezvous query?");
386 break;
390 if(conn->purpose == DIR_PURPOSE_UPLOAD_RENDDESC) {
391 switch(status_code) {
392 case 200:
393 log_fn(LOG_INFO,"eof (status 200) after uploading rendezvous descriptor: finished.");
394 break;
395 case 400:
396 log_fn(LOG_WARN,"http status 400 (bad request) response from dirserver. Malformed rendezvous descriptor?");
397 break;
398 default:
399 log_fn(LOG_WARN,"http status %d response unrecognized.", status_code);
400 break;
403 free(body); free(headers);
404 connection_mark_for_close(conn);
405 return 0;
406 } /* endif 'reached eof' */
408 /* If we're on the dirserver side, look for a command. */
409 if(conn->state == DIR_CONN_STATE_SERVER_COMMAND_WAIT) {
410 if (directory_handle_command(conn) < 0) {
411 connection_mark_for_close(conn);
412 return -1;
414 return 0;
417 /* XXX for READ states, might want to make sure inbuf isn't too big */
419 log_fn(LOG_DEBUG,"Got data, not eof. Leaving on inbuf.");
420 return 0;
423 static char answer200[] = "HTTP/1.0 200 OK\r\n\r\n";
424 static char answer400[] = "HTTP/1.0 400 Bad request\r\n\r\n";
425 static char answer403[] = "HTTP/1.0 403 Unapproved server\r\n\r\n";
426 static char answer404[] = "HTTP/1.0 404 Not found\r\n\r\n";
427 static char answer503[] = "HTTP/1.0 503 Directory unavailable\r\n\r\n";
429 /** Helper function: called when a dirserver gets a complete HTTP GET
430 * request. Look for a request for a directory or for a rendezvous
431 * service descriptor. On finding one, write a response into
432 * conn-\>outbuf. If the request is unrecognized, send a 404.
433 * Always return 0. */
434 static int
435 directory_handle_command_get(connection_t *conn, char *headers,
436 char *body, int body_len)
438 size_t dlen;
439 const char *cp;
440 char *url;
441 char tmp[8192];
443 log_fn(LOG_DEBUG,"Received GET command.");
445 conn->state = DIR_CONN_STATE_SERVER_WRITING;
447 if (parse_http_url(headers, &url) < 0) {
448 connection_write_to_buf(answer400, strlen(answer400), conn);
449 return 0;
452 if(!strcmp(url,"/")) { /* directory fetch */
453 dlen = dirserv_get_directory(&cp);
455 if(dlen == 0) {
456 log_fn(LOG_WARN,"My directory is empty. Closing.");
457 connection_write_to_buf(answer503, strlen(answer503), conn);
458 return 0;
461 log_fn(LOG_DEBUG,"Dumping directory to client.");
462 snprintf(tmp, sizeof(tmp), "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nContent-Type: text/plain\r\n\r\n",
463 (int)dlen);
464 connection_write_to_buf(tmp, strlen(tmp), conn);
465 connection_write_to_buf(cp, strlen(cp), conn);
466 return 0;
469 if(!strncmp(url,rend_fetch_url,strlen(rend_fetch_url))) {
470 /* rendezvous descriptor fetch */
471 const char *descp;
472 int desc_len;
474 switch(rend_cache_lookup_desc(url+strlen(rend_fetch_url), &descp, &desc_len)) {
475 case 1: /* valid */
476 snprintf(tmp, sizeof(tmp), "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nContent-Type: application/octet-stream\r\n\r\n",
477 desc_len); /* can't include descp here, because it's got nuls */
478 connection_write_to_buf(tmp, strlen(tmp), conn);
479 connection_write_to_buf(descp, desc_len, conn);
480 break;
481 case 0: /* well-formed but not present */
482 connection_write_to_buf(answer404, strlen(answer404), conn);
483 break;
484 case -1: /* not well-formed */
485 connection_write_to_buf(answer400, strlen(answer400), conn);
486 break;
488 return 0;
491 /* we didn't recognize the url */
492 connection_write_to_buf(answer404, strlen(answer404), conn);
493 return 0;
496 /** Helper function: called when a dirserver gets a complete HTTP POST
497 * request. Look for an uploaded server descriptor or rendezvous
498 * service descriptor. On finding one, process it and write a
499 * response into conn-\>outbuf. If the request is unrecognized, send a
500 * 404. Always return 0. */
501 static int
502 directory_handle_command_post(connection_t *conn, char *headers,
503 char *body, int body_len)
505 const char *cp;
506 char *url;
508 log_fn(LOG_DEBUG,"Received POST command.");
510 conn->state = DIR_CONN_STATE_SERVER_WRITING;
512 if (parse_http_url(headers, &url) < 0) {
513 connection_write_to_buf(answer400, strlen(answer400), conn);
514 return 0;
516 log_fn(LOG_INFO,"url '%s' posted to us.", url);
518 if(!strcmp(url,"/")) { /* server descriptor post */
519 cp = body;
520 switch(dirserv_add_descriptor(&cp)) {
521 case -1:
522 /* malformed descriptor, or something wrong */
523 connection_write_to_buf(answer400, strlen(answer400), conn);
524 break;
525 case 0:
526 /* descriptor was well-formed but server has not been approved */
527 connection_write_to_buf(answer403, strlen(answer403), conn);
528 break;
529 case 1:
530 dirserv_get_directory(&cp); /* rebuild and write to disk */
531 connection_write_to_buf(answer200, strlen(answer200), conn);
532 break;
534 return 0;
537 if(!strncmp(url,rend_publish_string,strlen(rend_publish_string))) {
538 /* rendezvous descriptor post */
539 if(rend_cache_store(body, body_len) < 0)
540 connection_write_to_buf(answer400, strlen(answer400), conn);
541 else
542 connection_write_to_buf(answer200, strlen(answer200), conn);
543 return 0;
546 /* we didn't recognize the url */
547 connection_write_to_buf(answer404, strlen(answer404), conn);
548 return 0;
551 /** Called when a dirserver receives data on a directory connection;
552 * looks for an HTTP request. If the request is complete, remove it
553 * from the inbuf, try to process it; otherwise, leave it on the
554 * buffer. Return a 0 on success, or -1 on error.
556 static int directory_handle_command(connection_t *conn) {
557 char *headers=NULL, *body=NULL;
558 int body_len=0;
559 int r;
561 tor_assert(conn && conn->type == CONN_TYPE_DIR);
563 switch(fetch_from_buf_http(conn->inbuf,
564 &headers, MAX_HEADERS_SIZE,
565 &body, &body_len, MAX_BODY_SIZE)) {
566 case -1: /* overflow */
567 log_fn(LOG_WARN,"input too large. Failing.");
568 return -1;
569 case 0:
570 log_fn(LOG_DEBUG,"command not all here yet.");
571 return 0;
572 /* case 1, fall through */
575 log_fn(LOG_DEBUG,"headers '%s', body '%s'.", headers, body);
577 if(!strncasecmp(headers,"GET",3))
578 r = directory_handle_command_get(conn, headers, body, body_len);
579 else if (!strncasecmp(headers,"POST",4))
580 r = directory_handle_command_post(conn, headers, body, body_len);
581 else {
582 log_fn(LOG_WARN,"Got headers '%s' with unknown command. Closing.", headers);
583 r = -1;
586 tor_free(headers); tor_free(body);
587 return r;
590 /** Write handler for directory connections; called when all data has
591 * been flushed. Close the connection or wait for a response as
592 * appropriate.
594 int connection_dir_finished_flushing(connection_t *conn) {
596 tor_assert(conn && conn->type == CONN_TYPE_DIR);
598 switch(conn->state) {
599 case DIR_CONN_STATE_CLIENT_SENDING:
600 log_fn(LOG_DEBUG,"client finished sending command.");
601 conn->state = DIR_CONN_STATE_CLIENT_READING;
602 connection_stop_writing(conn);
603 return 0;
604 case DIR_CONN_STATE_SERVER_WRITING:
605 log_fn(LOG_INFO,"Finished writing server response. Closing.");
606 connection_mark_for_close(conn);
607 return 0;
608 default:
609 log_fn(LOG_WARN,"BUG: called in unexpected state %d.", conn->state);
610 return -1;
612 return 0;
615 /** Connected handler for directory connections: begin sending data to the
616 * server */
617 int connection_dir_finished_connecting(connection_t *conn)
619 tor_assert(conn && conn->type == CONN_TYPE_DIR);
620 tor_assert(conn->state == DIR_CONN_STATE_CONNECTING);
622 log_fn(LOG_INFO,"Dir connection to router %s:%u established.",
623 conn->address,conn->port);
625 conn->state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */
626 return 0;
630 Local Variables:
631 mode:c
632 indent-tabs-mode:nil
633 c-basic-offset:2
634 End: