Handle windows socket errors correctly; comment most of common.
[tor.git] / src / or / directory.c
blob9c6722390aed62f8d52d64bb79a868d99b0e8bc6
1 /* Copyright 2001,2002,2003 Roger Dingledine. */
2 /* See LICENSE for licensing information */
3 /* $Id$ */
5 #include "or.h"
7 static void directory_send_command(connection_t *conn, int purpose,
8 const char *payload, int payload_len);
9 static int directory_handle_command(connection_t *conn);
11 /********* START VARIABLES **********/
13 extern or_options_t options; /* command-line and config-file options */
15 char rend_publish_string[] = "/rendezvous/publish";
16 char rend_fetch_url[] = "/rendezvous/";
18 #define MAX_HEADERS_SIZE 2048
19 #define MAX_BODY_SIZE 500000
21 /********* END VARIABLES ************/
23 void directory_initiate_command(routerinfo_t *router, int purpose,
24 const char *payload, int payload_len) {
25 connection_t *conn;
27 if(purpose == DIR_PURPOSE_FETCH_DIR)
28 log_fn(LOG_DEBUG,"initiating directory fetch");
29 if(purpose == DIR_PURPOSE_FETCH_RENDDESC)
30 log_fn(LOG_DEBUG,"initiating hidden-service descriptor fetch");
31 if(purpose == DIR_PURPOSE_UPLOAD_DIR)
32 log_fn(LOG_DEBUG,"initiating server descriptor upload");
33 if(purpose == DIR_PURPOSE_UPLOAD_RENDDESC)
34 log_fn(LOG_DEBUG,"initiating hidden-service descriptor upload");
36 if (!router) { /* i guess they didn't have one in mind for me to use */
37 log_fn(LOG_WARN,"No running dirservers known. Not trying. (purpose %d)", purpose);
38 return;
41 conn = connection_new(CONN_TYPE_DIR);
43 /* set up conn so it's got all the data we need to remember */
44 conn->addr = router->addr;
45 conn->port = router->dir_port;
46 conn->address = tor_strdup(router->address);
47 conn->nickname = tor_strdup(router->nickname);
48 tor_assert(router->identity_pkey);
49 conn->identity_pkey = crypto_pk_dup_key(router->identity_pkey);
51 conn->purpose = purpose;
53 if(connection_add(conn) < 0) { /* no space, forget it */
54 connection_free(conn);
55 return;
58 /* queue the command on the outbuf */
59 directory_send_command(conn, purpose, payload, payload_len);
61 /* give it an initial state */
62 conn->state = DIR_CONN_STATE_CONNECTING;
64 if(purpose == DIR_PURPOSE_FETCH_DIR ||
65 purpose == DIR_PURPOSE_UPLOAD_DIR) {
66 /* then we want to connect directly */
67 switch(connection_connect(conn, conn->address, conn->addr, conn->port)) {
68 case -1:
69 router_mark_as_down(conn->nickname); /* don't try him again */
70 connection_mark_for_close(conn, 0);
71 return;
72 case 1:
73 conn->state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */
74 /* fall through */
75 case 0:
76 connection_set_poll_socket(conn);
77 connection_watch_events(conn, POLLIN | POLLOUT | POLLERR);
78 /* writable indicates finish, readable indicates broken link,
79 error indicates broken link in windowsland. */
81 } else { /* we want to connect via tor */
82 /* make an AP connection
83 * populate it and add it at the right state
84 * socketpair and hook up both sides
86 conn->s = connection_ap_make_bridge(conn->address, conn->port);
87 if(conn->s < 0) {
88 log_fn(LOG_WARN,"Making AP bridge to dirserver failed.");
89 connection_mark_for_close(conn, 0);
90 return;
93 conn->state = DIR_CONN_STATE_CLIENT_SENDING;
94 connection_set_poll_socket(conn);
95 connection_start_reading(conn);
99 static void directory_send_command(connection_t *conn, int purpose,
100 const char *payload, int payload_len) {
101 char fetchstring[] = "GET / HTTP/1.0\r\n\r\n";
102 char tmp[8192];
104 tor_assert(conn && conn->type == CONN_TYPE_DIR);
106 switch(purpose) {
107 case DIR_PURPOSE_FETCH_DIR:
108 tor_assert(payload == NULL);
109 connection_write_to_buf(fetchstring, strlen(fetchstring), conn);
110 break;
111 case DIR_PURPOSE_UPLOAD_DIR:
112 tor_assert(payload);
113 snprintf(tmp, sizeof(tmp), "POST / HTTP/1.0\r\nContent-Length: %d\r\n\r\n",
114 payload_len);
115 connection_write_to_buf(tmp, strlen(tmp), conn);
116 connection_write_to_buf(payload, payload_len, conn);
117 break;
118 case DIR_PURPOSE_FETCH_RENDDESC:
119 tor_assert(payload);
121 /* this must be true or we wouldn't be doing the lookup */
122 tor_assert(payload_len <= REND_SERVICE_ID_LEN);
123 memcpy(conn->rend_query, payload, payload_len);
124 conn->rend_query[payload_len] = 0;
126 snprintf(tmp, sizeof(tmp), "GET %s%s HTTP/1.0\r\n\r\n", rend_fetch_url, payload);
127 connection_write_to_buf(tmp, strlen(tmp), conn);
128 break;
129 case DIR_PURPOSE_UPLOAD_RENDDESC:
130 tor_assert(payload);
131 snprintf(tmp, sizeof(tmp),
132 "POST %s HTTP/1.0\r\nContent-Length: %d\r\n\r\n", rend_publish_string, payload_len);
133 connection_write_to_buf(tmp, strlen(tmp), conn);
134 /* could include nuls, need to write it separately */
135 connection_write_to_buf(payload, payload_len, conn);
136 break;
140 /* Parse "%s %s HTTP/1..."
141 * If it's well-formed, point *url to the second %s,
142 * null-terminate it (this modifies headers!) and return 0.
143 * Otherwise, return -1.
145 int parse_http_url(char *headers, char **url) {
146 char *s, *tmp;
148 s = (char *)eat_whitespace_no_nl(headers);
149 if (!*s) return -1;
150 s = (char *)find_whitespace(s); /* get past GET/POST */
151 if (!*s) return -1;
152 s = (char *)eat_whitespace_no_nl(s);
153 if (!*s) return -1;
154 tmp = s; /* this is it, assuming it's valid */
155 s = (char *)find_whitespace(s);
156 if (!*s) return -1;
157 *s = 0;
158 *url = tmp;
159 return 0;
162 /* Parse "HTTP/1.%d %d%s\r\n".
163 * If it's well-formed, assign *code, point *message to the first
164 * non-space character after code if there is one and message is non-NULL
165 * (else leave it alone), and return 0.
166 * Otherwise, return -1.
168 int parse_http_response(char *headers, int *code, char **message) {
169 int n1, n2;
170 tor_assert(headers && code);
172 while(isspace((int)*headers)) headers++; /* tolerate leading whitespace */
174 if(sscanf(headers, "HTTP/1.%d %d", &n1, &n2) < 2 ||
175 (n1 != 0 && n1 != 1) ||
176 (n2 < 100 || n2 >= 600)) {
177 log_fn(LOG_WARN,"Failed to parse header '%s'",headers);
178 return -1;
180 *code = n2;
181 if(message) {
182 /* XXX should set *message correctly */
184 return 0;
187 int connection_dir_process_inbuf(connection_t *conn) {
188 char *body;
189 char *headers;
190 int body_len=0;
191 int status_code;
193 tor_assert(conn && conn->type == CONN_TYPE_DIR);
195 if(conn->inbuf_reached_eof) {
196 if(conn->state != DIR_CONN_STATE_CLIENT_READING) {
197 log_fn(LOG_INFO,"conn reached eof, not reading. Closing.");
198 connection_close_immediate(conn); /* it was an error; give up on flushing */
199 connection_mark_for_close(conn,0);
200 return -1;
203 switch(fetch_from_buf_http(conn->inbuf,
204 &headers, MAX_HEADERS_SIZE,
205 &body, &body_len, MAX_DIR_SIZE)) {
206 case -1: /* overflow */
207 log_fn(LOG_WARN,"'fetch' response too large. Failing.");
208 connection_mark_for_close(conn,0);
209 return -1;
210 case 0:
211 log_fn(LOG_INFO,"'fetch' response not all here, but we're at eof. Closing.");
212 connection_mark_for_close(conn,0);
213 return -1;
214 /* case 1, fall through */
217 if(parse_http_response(headers, &status_code, NULL) < 0) {
218 log_fn(LOG_WARN,"Unparseable headers. Closing.");
219 free(body); free(headers);
220 connection_mark_for_close(conn,0);
221 return -1;
224 if(conn->purpose == DIR_PURPOSE_FETCH_DIR) {
225 /* fetch/process the directory to learn about new routers. */
226 log_fn(LOG_INFO,"Received directory (size %d):\n%s", body_len, body);
227 if(status_code == 503 || body_len == 0) {
228 log_fn(LOG_INFO,"Empty directory. Ignoring.");
229 free(body); free(headers);
230 connection_mark_for_close(conn,0);
231 return 0;
233 if(status_code != 200) {
234 log_fn(LOG_WARN,"Received http status code %d from dirserver. Failing.",
235 status_code);
236 free(body); free(headers);
237 connection_mark_for_close(conn,0);
238 return -1;
240 if(router_set_routerlist_from_directory(body, conn->identity_pkey) < 0){
241 log_fn(LOG_INFO,"...but parsing failed. Ignoring.");
242 } else {
243 log_fn(LOG_INFO,"updated routers.");
245 directory_has_arrived(); /* do things we've been waiting to do */
248 if(conn->purpose == DIR_PURPOSE_UPLOAD_DIR) {
249 switch(status_code) {
250 case 200:
251 log_fn(LOG_INFO,"eof (status 200) after uploading server descriptor: finished.");
252 break;
253 case 400:
254 log_fn(LOG_WARN,"http status 400 (bad request) response from dirserver. Malformed server descriptor?");
255 break;
256 case 403:
257 log_fn(LOG_WARN,"http status 403 (unapproved server) response from dirserver. Is your clock skewed? Have you mailed arma your identity fingerprint? Are you using the right key? See README.");
259 break;
260 default:
261 log_fn(LOG_WARN,"http status %d response unrecognized.", status_code);
262 break;
266 if(conn->purpose == DIR_PURPOSE_FETCH_RENDDESC) {
267 log_fn(LOG_INFO,"Received rendezvous descriptor (size %d, status code %d)",
268 body_len, status_code);
269 switch(status_code) {
270 case 200:
271 if(rend_cache_store(body, body_len) < 0) {
272 log_fn(LOG_WARN,"Failed to store rendezvous descriptor.");
273 /* alice's ap_stream will notice when connection_mark_for_close
274 * cleans it up */
275 } else {
276 /* success. notify pending connections about this. */
277 rend_client_desc_fetched(conn->rend_query, 1);
278 conn->purpose = DIR_PURPOSE_HAS_FETCHED_RENDDESC;
280 break;
281 case 404:
282 /* not there. pending connections will be notified when
283 * connection_mark_for_close cleans it up. */
284 break;
285 case 400:
286 log_fn(LOG_WARN,"http status 400 (bad request). Dirserver didn't like our rendezvous query?");
287 break;
291 if(conn->purpose == DIR_PURPOSE_UPLOAD_RENDDESC) {
292 switch(status_code) {
293 case 200:
294 log_fn(LOG_INFO,"eof (status 200) after uploading rendezvous descriptor: finished.");
295 break;
296 case 400:
297 log_fn(LOG_WARN,"http status 400 (bad request) response from dirserver. Malformed rendezvous descriptor?");
298 break;
299 default:
300 log_fn(LOG_WARN,"http status %d response unrecognized.", status_code);
301 break;
304 free(body); free(headers);
305 connection_mark_for_close(conn,0);
306 return 0;
309 if(conn->state == DIR_CONN_STATE_SERVER_COMMAND_WAIT) {
310 if (directory_handle_command(conn) < 0) {
311 connection_mark_for_close(conn,0);
312 return -1;
314 return 0;
317 /* XXX for READ states, might want to make sure inbuf isn't too big */
319 log_fn(LOG_DEBUG,"Got data, not eof. Leaving on inbuf.");
320 return 0;
323 static char answer200[] = "HTTP/1.0 200 OK\r\n\r\n";
324 static char answer400[] = "HTTP/1.0 400 Bad request\r\n\r\n";
325 static char answer403[] = "HTTP/1.0 403 Unapproved server\r\n\r\n";
326 static char answer404[] = "HTTP/1.0 404 Not found\r\n\r\n";
327 static char answer503[] = "HTTP/1.0 503 Directory unavailable\r\n\r\n";
329 /* always returns 0 */
330 static int directory_handle_command_get(connection_t *conn,
331 char *headers, char *body,
332 int body_len) {
333 size_t dlen;
334 const char *cp;
335 char *url;
336 char tmp[8192];
338 log_fn(LOG_DEBUG,"Received GET command.");
340 conn->state = DIR_CONN_STATE_SERVER_WRITING;
342 if (parse_http_url(headers, &url) < 0) {
343 connection_write_to_buf(answer400, strlen(answer400), conn);
344 return 0;
347 if(!strcmp(url,"/")) { /* directory fetch */
348 dlen = dirserv_get_directory(&cp);
350 if(dlen == 0) {
351 log_fn(LOG_WARN,"My directory is empty. Closing.");
352 connection_write_to_buf(answer503, strlen(answer503), conn);
353 return 0;
356 log_fn(LOG_DEBUG,"Dumping directory to client.");
357 snprintf(tmp, sizeof(tmp), "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nContent-Type: text/plain\r\n\r\n",
358 (int)dlen);
359 connection_write_to_buf(tmp, strlen(tmp), conn);
360 connection_write_to_buf(cp, strlen(cp), conn);
361 return 0;
364 if(!strncmp(url,rend_fetch_url,strlen(rend_fetch_url))) {
365 /* rendezvous descriptor fetch */
366 const char *descp;
367 int desc_len;
369 switch(rend_cache_lookup_desc(url+strlen(rend_fetch_url), &descp, &desc_len)) {
370 case 1: /* valid */
371 snprintf(tmp, sizeof(tmp), "HTTP/1.0 200 OK\r\nContent-Length: %d\r\nContent-Type: application/octet-stream\r\n\r\n",
372 desc_len); /* can't include descp here, because it's got nuls */
373 connection_write_to_buf(tmp, strlen(tmp), conn);
374 connection_write_to_buf(descp, desc_len, conn);
375 break;
376 case 0: /* well-formed but not present */
377 connection_write_to_buf(answer404, strlen(answer404), conn);
378 break;
379 case -1: /* not well-formed */
380 connection_write_to_buf(answer400, strlen(answer400), conn);
381 break;
383 return 0;
386 /* we didn't recognize the url */
387 connection_write_to_buf(answer404, strlen(answer404), conn);
388 return 0;
391 /* always returns 0 */
392 static int directory_handle_command_post(connection_t *conn,
393 char *headers, char *body,
394 int body_len) {
395 const char *cp;
396 char *url;
398 log_fn(LOG_DEBUG,"Received POST command.");
400 conn->state = DIR_CONN_STATE_SERVER_WRITING;
402 if (parse_http_url(headers, &url) < 0) {
403 connection_write_to_buf(answer400, strlen(answer400), conn);
404 return 0;
406 log_fn(LOG_INFO,"url '%s' posted to us.", url);
408 if(!strcmp(url,"/")) { /* server descriptor post */
409 cp = body;
410 switch(dirserv_add_descriptor(&cp)) {
411 case -1:
412 /* malformed descriptor, or something wrong */
413 connection_write_to_buf(answer400, strlen(answer400), conn);
414 break;
415 case 0:
416 /* descriptor was well-formed but server has not been approved */
417 connection_write_to_buf(answer403, strlen(answer403), conn);
418 break;
419 case 1:
420 dirserv_get_directory(&cp); /* rebuild and write to disk */
421 connection_write_to_buf(answer200, strlen(answer200), conn);
422 break;
424 return 0;
427 if(!strncmp(url,rend_publish_string,strlen(rend_publish_string))) {
428 /* rendezvous descriptor post */
429 if(rend_cache_store(body, body_len) < 0)
430 connection_write_to_buf(answer400, strlen(answer400), conn);
431 else
432 connection_write_to_buf(answer200, strlen(answer200), conn);
433 return 0;
436 /* we didn't recognize the url */
437 connection_write_to_buf(answer404, strlen(answer404), conn);
438 return 0;
441 static int directory_handle_command(connection_t *conn) {
442 char *headers=NULL, *body=NULL;
443 int body_len=0;
444 int r;
446 tor_assert(conn && conn->type == CONN_TYPE_DIR);
448 switch(fetch_from_buf_http(conn->inbuf,
449 &headers, MAX_HEADERS_SIZE,
450 &body, &body_len, MAX_BODY_SIZE)) {
451 case -1: /* overflow */
452 log_fn(LOG_WARN,"input too large. Failing.");
453 return -1;
454 case 0:
455 log_fn(LOG_DEBUG,"command not all here yet.");
456 return 0;
457 /* case 1, fall through */
460 log_fn(LOG_DEBUG,"headers '%s', body '%s'.", headers, body);
462 if(!strncasecmp(headers,"GET",3))
463 r = directory_handle_command_get(conn, headers, body, body_len);
464 else if (!strncasecmp(headers,"POST",4))
465 r = directory_handle_command_post(conn, headers, body, body_len);
466 else {
467 log_fn(LOG_WARN,"Got headers '%s' with unknown command. Closing.", headers);
468 r = -1;
471 tor_free(headers); tor_free(body);
472 return r;
475 int connection_dir_finished_flushing(connection_t *conn) {
476 int e, len=sizeof(e);
478 tor_assert(conn && conn->type == CONN_TYPE_DIR);
480 switch(conn->state) {
481 case DIR_CONN_STATE_CONNECTING:
482 if (getsockopt(conn->s, SOL_SOCKET, SO_ERROR, (void*)&e, &len) < 0) { /* not yet */
483 if(!ERRNO_IS_CONN_EINPROGRESS(tor_socket_errno(conn->s))) {
484 log_fn(LOG_DEBUG,"in-progress connect failed. Removing.");
485 router_mark_as_down(conn->nickname); /* don't try him again */
486 connection_mark_for_close(conn,0);
487 return -1;
488 } else {
489 return 0; /* no change, see if next time is better */
492 /* the connect has finished. */
494 log_fn(LOG_INFO,"Dir connection to router %s:%u established.",
495 conn->address,conn->port);
497 conn->state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */
498 return 0;
499 case DIR_CONN_STATE_CLIENT_SENDING:
500 log_fn(LOG_DEBUG,"client finished sending command.");
501 conn->state = DIR_CONN_STATE_CLIENT_READING;
502 connection_stop_writing(conn);
503 return 0;
504 case DIR_CONN_STATE_SERVER_WRITING:
505 log_fn(LOG_INFO,"Finished writing server response. Closing.");
506 connection_mark_for_close(conn,0);
507 return 0;
508 default:
509 log_fn(LOG_WARN,"BUG: called in unexpected state %d.", conn->state);
510 return -1;
512 return 0;
516 Local Variables:
517 mode:c
518 indent-tabs-mode:nil
519 c-basic-offset:2
520 End: