From 6af8d0606f1be27d572f46e8ef03112eedc73928 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Fri, 12 Mar 2004 12:43:13 +0000 Subject: [PATCH] inform unapproved servers when we reject their descriptors svn:r1263 --- src/or/directory.c | 184 +++++++++++++++++++++++++++++++-------------- src/or/dirserv.c | 34 ++++----- src/or/router.c | 2 +- 3 files changed, 141 insertions(+), 79 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index 6816d5b405..d6be10eff7 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -12,8 +12,8 @@ static int directory_handle_command(connection_t *conn); extern or_options_t options; /* command-line and config-file options */ extern int has_fetched_directory; -static char fetchstring[] = "GET / HTTP/1.0\r\n\r\n"; -static char answerstring[] = "HTTP/1.0 200 OK\r\n\r\n"; +#define MAX_HEADERS_SIZE 2048 +#define MAX_BODY_SIZE 500000 /********* END VARIABLES ************/ @@ -69,6 +69,7 @@ void directory_initiate_command(routerinfo_t *router, int command) { } static int directory_send_command(connection_t *conn, int command) { + char fetchstring[] = "GET / HTTP/1.0\r\n\r\n"; const char *s; char tmp[8192]; @@ -94,59 +95,116 @@ static int directory_send_command(connection_t *conn, int command) { return 0; } +/* Parse "HTTP/1.%d %d%s\r\n". + * If it's well-formed, assign *code, point *message to the first + * non-space character after code if there is one and message is non-NULL + * (else leave it alone), and return 0. + * Otherwise, return -1. + */ +int parse_http_response(char *headers, int *code, char **message) { + int n1, n2; + assert(headers && code); + + while(isspace(*headers)) headers++; /* tolerate leading whitespace */ + + if(sscanf(headers, "HTTP/1.%d %d", &n1, &n2) < 2 || + (n1 != 0 && n1 != 1) || + (n2 < 100 || n2 >= 600)) { + log_fn(LOG_WARN,"Failed to parse header '%s'",headers); + return -1; + } + *code = n2; + if(message) { + /* XXX should set *message correctly */ + } + return 0; +} + int connection_dir_process_inbuf(connection_t *conn) { char *directory; - int directorylen=0; + char *headers; + int status_code; assert(conn && conn->type == CONN_TYPE_DIR); if(conn->inbuf_reached_eof) { - switch(conn->state) { - case DIR_CONN_STATE_CLIENT_READING_FETCH: - /* kill it, but first fetch/process the directory to learn about new routers. */ - switch(fetch_from_buf_http(conn->inbuf, - NULL, 0, &directory, MAX_DIR_SIZE)) { - case -1: /* overflow */ - log_fn(LOG_WARN,"'fetch' response too large. Failing."); - connection_mark_for_close(conn,0); - return -1; - case 0: - log_fn(LOG_INFO,"'fetch' response not all here, but we're at eof. Closing."); - connection_mark_for_close(conn,0); - return -1; - /* case 1, fall through */ - } - /* XXX check headers, at least make sure returned 2xx */ - directorylen = strlen(directory); - log_fn(LOG_INFO,"Received directory (size %d):\n%s", directorylen, directory); - if(directorylen == 0) { - log_fn(LOG_INFO,"Empty directory. Ignoring."); - free(directory); - connection_mark_for_close(conn,0); - return 0; - } - if(router_set_routerlist_from_directory(directory, conn->identity_pkey) < 0){ - log_fn(LOG_INFO,"...but parsing failed. Ignoring."); - } else { - log_fn(LOG_INFO,"updated routers."); - } - has_fetched_directory=1; - if(options.ORPort) { /* connect to them all */ - router_retry_connections(); - } - free(directory); - connection_mark_for_close(conn,0); - return 0; - case DIR_CONN_STATE_CLIENT_READING_UPLOAD: - /* XXX make sure there's a 200 OK on the buffer */ - log_fn(LOG_INFO,"eof while reading upload response. Finished."); - connection_mark_for_close(conn,0); - return 0; - default: - log_fn(LOG_INFO,"conn reached eof, not reading. Closing."); - connection_close_immediate(conn); /* it was an error; give up on flushing */ + if(conn->state != DIR_CONN_STATE_CLIENT_READING_FETCH && + conn->state != DIR_CONN_STATE_CLIENT_READING_UPLOAD) { + log_fn(LOG_INFO,"conn reached eof, not reading. Closing."); + connection_close_immediate(conn); /* it was an error; give up on flushing */ + connection_mark_for_close(conn,0); + return -1; + } + + switch(fetch_from_buf_http(conn->inbuf, + &headers, MAX_HEADERS_SIZE, + &directory, MAX_DIR_SIZE)) { + case -1: /* overflow */ + log_fn(LOG_WARN,"'fetch' response too large. Failing."); connection_mark_for_close(conn,0); return -1; + case 0: + log_fn(LOG_INFO,"'fetch' response not all here, but we're at eof. Closing."); + connection_mark_for_close(conn,0); + return -1; + /* case 1, fall through */ + } + + if(parse_http_response(headers, &status_code, NULL) < 0) { + log_fn(LOG_WARN,"Unparseable headers. Closing."); + free(directory); free(headers); + connection_mark_for_close(conn,0); + return -1; + } + + if(conn->state == DIR_CONN_STATE_CLIENT_READING_FETCH) { + /* fetch/process the directory to learn about new routers. */ + int directorylen; + directorylen = strlen(directory); + log_fn(LOG_INFO,"Received directory (size %d):\n%s", directorylen, directory); + if(status_code == 503 || directorylen == 0) { + log_fn(LOG_INFO,"Empty directory. Ignoring."); + free(directory); free(headers); + connection_mark_for_close(conn,0); + return 0; + } + if(status_code != 200) { + log_fn(LOG_WARN,"Received http status code %d from dirserver. Failing.", + status_code); + free(directory); free(headers); + connection_mark_for_close(conn,0); + return -1; + } + if(router_set_routerlist_from_directory(directory, conn->identity_pkey) < 0){ + log_fn(LOG_INFO,"...but parsing failed. Ignoring."); + } else { + log_fn(LOG_INFO,"updated routers."); + } + has_fetched_directory=1; + if(options.ORPort) { /* connect to them all */ + router_retry_connections(); + } + free(directory); free(headers); + connection_mark_for_close(conn,0); + return 0; + } + + if(conn->state == DIR_CONN_STATE_CLIENT_READING_UPLOAD) { + switch(status_code) { + case 200: + log_fn(LOG_INFO,"eof (status 200) while reading upload response: finished."); + break; + case 400: + log_fn(LOG_WARN,"http status 400 (bad request) from dirserver. Is your clock skewed?"); + break; + case 403: + log_fn(LOG_WARN,"http status 403 (unapproved server) from dirserver. Have you mailed arma your identity fingerprint? Are you using the right key?"); + + break; + } + free(directory); free(headers); + connection_mark_for_close(conn,0); + return 0; } } @@ -159,6 +217,11 @@ int connection_dir_process_inbuf(connection_t *conn) { return 0; } +static char answer200[] = "HTTP/1.0 200 OK\r\n\r\n"; +static char answer400[] = "HTTP/1.0 400 Bad request\r\n\r\n"; +static char answer403[] = "HTTP/1.0 403 Unapproved server\r\n\r\n"; +static char answer503[] = "HTTP/1.0 503 Directory unavailable\r\n\r\n"; + static int directory_handle_command_get(connection_t *conn, char *headers, char *body) { size_t dlen; @@ -171,11 +234,13 @@ static int directory_handle_command_get(connection_t *conn, if(dlen == 0) { log_fn(LOG_WARN,"My directory is empty. Closing."); - return -1; /* XXX send some helpful http error code */ + connection_write_to_buf(answer503, strlen(answer503), conn); + conn->state = DIR_CONN_STATE_SERVER_WRITING; + return 0; } log_fn(LOG_DEBUG,"Dumping directory to client."); - connection_write_to_buf(answerstring, strlen(answerstring), conn); + connection_write_to_buf(answer200, strlen(answer200), conn); connection_write_to_buf(cp, dlen, conn); conn->state = DIR_CONN_STATE_SERVER_WRITING; return 0; @@ -188,12 +253,20 @@ static int directory_handle_command_post(connection_t *conn, /* XXX should check url and http version */ log_fn(LOG_DEBUG,"Received POST command."); cp = body; - if(dirserv_add_descriptor(&cp) < 0) { - log_fn(LOG_WARN,"dirserv_add_descriptor() failed. Dropping."); - return -1; /* XXX should write an http failed code */ + switch(dirserv_add_descriptor(&cp)) { + case -1: + /* malformed descriptor, or clock is skewed, or something wrong */ + connection_write_to_buf(answer400, strlen(answer400), conn); + break; + case 0: + /* descriptor was well-formed but server has not been approved */ + connection_write_to_buf(answer403, strlen(answer403), conn); + break; + case 1: + dirserv_get_directory(&cp); /* rebuild and write to disk */ + connection_write_to_buf(answer200, strlen(answer200), conn); + break; } - dirserv_get_directory(&cp); /* rebuild and write to disk */ - connection_write_to_buf(answerstring, strlen(answerstring), conn); conn->state = DIR_CONN_STATE_SERVER_WRITING; return 0; } @@ -202,9 +275,6 @@ static int directory_handle_command(connection_t *conn) { char *headers=NULL, *body=NULL; int r; -#define MAX_HEADERS_SIZE 2048 -#define MAX_BODY_SIZE 500000 - assert(conn && conn->type == CONN_TYPE_DIR); switch(fetch_from_buf_http(conn->inbuf, diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 3540048faf..0da23a7162 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -182,15 +182,13 @@ dirserv_free_descriptors() n_descriptors = 0; } -/* Return 0 if descriptor is well-formed; -1 if descriptor is not - * well-formed. Update *desc to point after the descriptor if the +/* Return 1 if descriptor is well-formed and accepted; + * 0 if well-formed and server is unapproved; + * -1 if not well-formed or if clock is skewed or other error. + * + * Update *desc to point after the descriptor if the * descriptor is well-formed. */ -/* XXX down the road perhaps we should return 1 for accepted, 0 for - * well-formed but rejected, -1 for not-well-formed. So remote servers - * can know if their submission was accepted and not just whether it - * was well-formed. ...Or maybe we shouldn't give them that info? - */ int dirserv_add_descriptor(const char **desc) { @@ -205,7 +203,7 @@ dirserv_add_descriptor(const char **desc) start = strstr(*desc, "router "); if (!start) { log(LOG_WARN, "no descriptor found."); - goto err; + return -1; } if ((end = strstr(start+6, "\nrouter "))) { ++end; /* Include NL. */ @@ -219,11 +217,11 @@ dirserv_add_descriptor(const char **desc) /* Check: is the descriptor syntactically valid? */ ri = router_get_entry_from_string(cp, NULL); + tor_free(desc_tmp); if (!ri) { log(LOG_WARN, "Couldn't parse descriptor"); - goto err; + return -1; } - tor_free(desc_tmp); /* Okay. Now check whether the fingerprint is recognized. */ r = dirserv_router_fingerprint_is_known(ri); if(r<1) { @@ -248,7 +246,7 @@ dirserv_add_descriptor(const char **desc) log_fn(LOG_WARN, "Publication time for nickname %s is too far in the future; possible clock skew. Not adding", ri->nickname); routerinfo_free(ri); *desc = end; - return 0; + return -1; } /* Do we already have an entry for this router? */ desc_ent_ptr = NULL; @@ -263,10 +261,10 @@ dirserv_add_descriptor(const char **desc) if ((*desc_ent_ptr)->published > ri->published_on) { /* We already have a newer descriptor */ log_fn(LOG_INFO,"We already have a newer desc for nickname %s. Not adding.",ri->nickname); - /* This isn't really an error; return. */ + /* This isn't really an error; return success. */ routerinfo_free(ri); *desc = end; - return 0; + return 1; } /* We don't have a newer one; we'll update this one. */ free_descriptor_entry(*desc_ent_ptr); @@ -287,13 +285,7 @@ dirserv_add_descriptor(const char **desc) directory_set_dirty(); routerinfo_free(ri); - return 0; - err: - tor_free(desc_tmp); - if (ri) - routerinfo_free(ri); - - return -1; + return 1; } void @@ -310,7 +302,7 @@ dirserv_init_from_directory_string(const char *dir) cp = strstr(cp, "\nrouter "); if (!cp) break; ++cp; - if (dirserv_add_descriptor(&cp)) { + if (dirserv_add_descriptor(&cp) < 0) { return -1; } --cp; /*Back up to newline.*/ diff --git a/src/or/router.c b/src/or/router.c index 387169d956..db2a7f3b1f 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -161,7 +161,7 @@ int init_keys(void) { return -1; } tmp = mydesc = router_get_my_descriptor(); - if (dirserv_add_descriptor(&tmp)) { + if (dirserv_add_descriptor(&tmp) != 1) { log(LOG_ERR, "Unable to add own descriptor to directory."); return -1; }