diff --git a/src/or/buffers.c b/src/or/buffers.c index 55bb2279f3..eba43e30ec 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -189,6 +189,74 @@ int fetch_from_buf(char *string, int string_len, return *buf_datalen; } +/* There is a (possibly incomplete) http statement on *buf, of the + * form "%s\r\n\r\n%s", headers, body. + * If a) the headers include a Content-Length field and all bytes in + * the body are present, or b) there's no Content-Length field and + * all headers are present, then: + * copy headers and body into the supplied args (and null terminate + * them), remove them from buf, and return 1. + * (If headers or body is NULL, discard that part of the buf.) + * If a headers or body doesn't fit in the arg, return -1. + * + * Else, change nothing and return 0. + */ +int fetch_from_buf_http(char *buf, int *buf_datalen, + char *headers_out, int max_headerlen, + char *body_out, int max_bodylen) { + char *headers, *body; + int i; + int headerlen, bodylen, contentlen; + + assert(buf && buf_datalen); + + headers = buf; + i = find_on_inbuf("\r\n\r\n", 4, buf, *buf_datalen); + if(i < 0) { + log_fn(LOG_DEBUG,"headers not all here yet."); + return 0; + } + body = buf+i; + headerlen = body-headers; /* includes the CRLFCRLF */ + bodylen = *buf_datalen - headerlen; + log_fn(LOG_DEBUG,"headerlen %d, bodylen %d.",headerlen,bodylen); + + if(headers_out && max_headerlen <= headerlen) { + log_fn(LOG_DEBUG,"headerlen %d larger than %d. Failing.", headerlen, max_headerlen-1); + return -1; + } + if(body_out && max_bodylen <= bodylen) { + log_fn(LOG_DEBUG,"bodylen %d larger than %d. Failing.", bodylen, max_bodylen-1); + return -1; + } + +#define CONTENT_LENGTH "Content-Length: " + i = find_on_inbuf(CONTENT_LENGTH, strlen(CONTENT_LENGTH), headers, headerlen); + /* This includes headers like Not-Content-Length. But close enough. */ + if(i > 0) { + contentlen = atoi(headers+i); + if(bodylen < contentlen) { + log_fn(LOG_DEBUG,"body not all here yet."); + return 0; /* not all there yet */ + } + bodylen = contentlen; + log_fn(LOG_DEBUG,"bodylen reduced to %d.",bodylen); + } + /* all happy. copy into the appropriate places, and return 1 */ + if(headers_out) { + memcpy(headers_out,buf,headerlen); + headers_out[headerlen] = 0; /* null terminate it */ + } + if(body_out) { + memcpy(body_out,buf+headerlen,bodylen); + body_out[bodylen] = 0; /* null terminate it */ + } + *buf_datalen -= (headerlen+bodylen); + memmove(buf, buf+headerlen+bodylen, *buf_datalen); + + return 1; +} + int find_on_inbuf(char *string, int string_len, char *buf, int buf_datalen) { /* find first instance of needle 'string' on haystack 'buf'. return how diff --git a/src/or/connection.c b/src/or/connection.c index 2f723ae2b0..5dead3f567 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -230,7 +230,7 @@ static int connection_init_accepted_conn(connection_t *conn) { conn->state = AP_CONN_STATE_SOCKS_WAIT; break; case CONN_TYPE_DIR: - conn->state = DIR_CONN_STATE_COMMAND_WAIT; + conn->state = DIR_CONN_STATE_SERVER_COMMAND_WAIT; break; } return 0; @@ -438,7 +438,8 @@ int connection_handle_read(connection_t *conn) { } if(connection_read_to_buf(conn) < 0) { - if(conn->type == CONN_TYPE_DIR && conn->state == DIR_CONN_STATE_CONNECTING) { + if(conn->type == CONN_TYPE_DIR && + (conn->state == DIR_CONN_STATE_CONNECTING_GET || DIR_CONN_STATE_CONNECTING_POST)) { /* it's a directory server and connecting failed: forget about this router */ /* XXX I suspect pollerr may make Windows not get to this point. :( */ router_forget_router(conn->addr,conn->port); @@ -526,6 +527,13 @@ int connection_fetch_from_buf(char *string, int len, connection_t *conn) { return fetch_from_buf(string, len, &conn->inbuf, &conn->inbuflen, &conn->inbuf_datalen); } +int connection_fetch_from_buf_http(connection_t *conn, + char *headers_out, int max_headerlen, + char *body_out, int max_bodylen) { + return fetch_from_buf_http(conn->inbuf,&conn->inbuf_datalen, + headers_out, max_headerlen, body_out, max_bodylen); +} + int connection_find_on_inbuf(char *string, int len, connection_t *conn) { return find_on_inbuf(string, len, conn->inbuf, conn->inbuf_datalen); } diff --git a/src/or/directory.c b/src/or/directory.c index 7d1d7d0f55..ac70ba08dc 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -6,10 +6,9 @@ #define MAX_DIR_SIZE 50000 /* XXX, big enough? */ -static int directory_send_command(connection_t *conn); +static int directory_send_command(connection_t *conn, int command); static void directory_rebuild(void); static int directory_handle_command(connection_t *conn); -static int directory_handle_reading(connection_t *conn); /********* START VARIABLES **********/ @@ -17,7 +16,6 @@ extern or_options_t options; /* command-line and config-file options */ static char the_directory[MAX_DIR_SIZE+1]; static int directorylen=0; -static int reading_headers=0; static int directory_dirty=1; static char getstring[] = "GET / HTTP/1.0\r\n\r\n"; @@ -26,18 +24,21 @@ static char answerstring[] = "HTTP/1.0 200 OK\r\n\r\n"; /********* END VARIABLES ************/ -void directory_initiate_fetch(routerinfo_t *router) { +void directory_initiate_command(routerinfo_t *router, int command) { connection_t *conn; if(!router) /* i guess they didn't have one in mind for me to use */ return; - if(connection_get_by_type(CONN_TYPE_DIR)) { /* there's already a fetch running */ - log_fn(LOG_DEBUG,"Canceling fetch, dir conn already active."); + if(connection_get_by_type(CONN_TYPE_DIR)) { /* there's already a dir conn running */ + log_fn(LOG_DEBUG,"Canceling connect, dir conn already active."); return; } - log_fn(LOG_DEBUG,"initiating directory fetch"); + if(command == DIR_CONN_STATE_CONNECTING_GET) + log_fn(LOG_DEBUG,"initiating directory get"); + else + log_fn(LOG_DEBUG,"initiating directory post"); conn = connection_new(CONN_TYPE_DIR); if(!conn) @@ -52,7 +53,7 @@ void directory_initiate_fetch(routerinfo_t *router) { if (router->signing_pkey) conn->pkey = crypto_pk_dup_key(router->signing_pkey); else { - log_fn(LOG_ERR, "No signing key known for directory %s; signature won't be checked", conn->address); + log_fn(LOG_ERR, "No signing key known for dirserver %s; signature won't be checked", conn->address); conn->pkey = NULL; } @@ -63,7 +64,7 @@ void directory_initiate_fetch(routerinfo_t *router) { switch(connection_connect(conn, router->address, router->addr, router->dir_port)) { case -1: - router_forget_router(conn->addr, conn->port); /* don't try him again */ + router_forget_router(conn->addr, conn->port); /* XXX don't try him again */ connection_free(conn); return; case 0: @@ -71,28 +72,45 @@ void directory_initiate_fetch(routerinfo_t *router) { connection_watch_events(conn, POLLIN | POLLOUT | POLLERR); /* writable indicates finish, readable indicates broken link, error indicates broken link in windowsland. */ - conn->state = DIR_CONN_STATE_CONNECTING; + conn->state = command; return; /* case 1: fall through */ } connection_set_poll_socket(conn); - if(directory_send_command(conn) < 0) { + if(directory_send_command(conn, command) < 0) { connection_remove(conn); connection_free(conn); } } -static int directory_send_command(connection_t *conn) { +static int directory_send_command(connection_t *conn, int command) { + char *s; assert(conn && conn->type == CONN_TYPE_DIR); - if(connection_write_to_buf(getstring, strlen(getstring), conn) < 0) { - log_fn(LOG_DEBUG,"Couldn't write command to buffer."); - return -1; + switch(command) { + case DIR_CONN_STATE_CONNECTING_GET: + if(connection_write_to_buf(getstring, strlen(getstring), conn) < 0) { + log_fn(LOG_DEBUG,"Couldn't write get to buffer."); + return -1; + } + conn->state = DIR_CONN_STATE_CLIENT_SENDING_GET; + break; + case DIR_CONN_STATE_CONNECTING_POST: + s = router_get_my_descriptor(); + if(!s) { + log_fn(LOG_DEBUG,"Failed to get my descriptor."); + return -1; + } + if(connection_write_to_buf(poststring, strlen(poststring), conn) < 0 || + connection_write_to_buf(s, strlen(s), conn) < 0) { + log_fn(LOG_DEBUG,"Couldn't write post/descriptor to buffer."); + return -1; + } + conn->state = DIR_CONN_STATE_CLIENT_SENDING_POST; + break; } - - conn->state = DIR_CONN_STATE_SENDING_COMMAND; return 0; } @@ -107,8 +125,8 @@ static void directory_rebuild(void) { log(LOG_ERR, "Error writing directory"); return; } - log(LOG_INFO,"New directory:\n%s",the_directory); directorylen = strlen(the_directory); + log(LOG_INFO,"New directory (size %d):\n%s",directorylen,the_directory); directory_dirty = 0; } else { log(LOG_INFO,"Directory still clean, reusing."); @@ -120,111 +138,101 @@ int connection_dir_process_inbuf(connection_t *conn) { assert(conn && conn->type == CONN_TYPE_DIR); if(conn->inbuf_reached_eof) { - if(conn->state != DIR_CONN_STATE_READING) { - log_fn(LOG_DEBUG,"conn reached eof, not reading. Closing."); - return -1; + switch(conn->state) { + case DIR_CONN_STATE_CLIENT_READING_GET: + /* kill it, but first process the_directory and learn about new routers. */ + switch(connection_fetch_from_buf_http(conn, NULL, 0, the_directory, MAX_DIR_SIZE)) { + case -1: /* overflow */ + log_fn(LOG_DEBUG,"'get' response too large. Failing."); + return -1; + case 0: + log_fn(LOG_DEBUG,"'get' response not all here, but we're at eof. Closing."); + return -1; + /* case 1, fall through */ + } + directorylen = strlen(the_directory); + log_fn(LOG_DEBUG,"Received directory (size %d):\n%s", directorylen, the_directory); + if(directorylen == 0) { + log_fn(LOG_DEBUG,"Empty directory. Ignoring."); + return -1; + } + if(router_get_dir_from_string(the_directory, conn->pkey) < 0) { + log_fn(LOG_DEBUG,"...but parsing failed. Ignoring."); + } else { + log_fn(LOG_DEBUG,"and got an %s directory; updated routers.", + conn->pkey ? "authenticated" : "unauthenticated"); + } + if(options.OnionRouter) { /* connect to them all */ + router_retry_connections(); + } + return -1; + case DIR_CONN_STATE_CLIENT_READING_POST: + /* XXX make sure there's a 200 OK on the buffer */ + log_fn(LOG_DEBUG,"eof while reading post response. Finished."); + return -1; + default: + log_fn(LOG_DEBUG,"conn reached eof, not reading. Closing."); + return -1; } - /* eof reached, kill it, but first process the_directory and learn about new routers. */ - log_fn(LOG_DEBUG,"Received directory (size %d)\n%s", directorylen, the_directory); - if(directorylen == 0) { - log_fn(LOG_DEBUG,"Empty directory. Ignoring."); - return -1; - } - if(router_get_dir_from_string(the_directory, conn->pkey) < 0) { - log_fn(LOG_DEBUG,"...but parsing failed. Ignoring."); - } else { - log_fn(LOG_DEBUG,"and got an %s directory; updated routers.", - conn->pkey ? "authenticated" : "unauthenticated"); - } - - if(options.OnionRouter) { /* connect to them all */ - router_retry_connections(); - } - return -1; } - switch(conn->state) { - case DIR_CONN_STATE_COMMAND_WAIT: - return directory_handle_command(conn); - case DIR_CONN_STATE_READING: - return directory_handle_reading(conn); - default: - log_fn(LOG_DEBUG,"Got data while writing; Ignoring."); - break; - } + if(conn->state == DIR_CONN_STATE_SERVER_COMMAND_WAIT) + return directory_handle_command(conn); + /* XXX for READ states, might want to make sure inbuf isn't too big */ + + log_fn(LOG_DEBUG,"Got data, not eof. Leaving on inbuf."); return 0; } static int directory_handle_command(connection_t *conn) { - char buf[15]; + char headers[1024]; + char body[1024]; assert(conn && conn->type == CONN_TYPE_DIR); - if(conn->inbuf_datalen < (int)strlen(getstring)) { /* entire response available? */ - log_fn(LOG_DEBUG,"Entire command not here yet. Waiting."); - return 0; /* not yet */ - } - - connection_fetch_from_buf(buf,strlen(getstring),conn); - - if(strncasecmp(buf,getstring,strlen("GET / HTTP/"))) { - log_fn(LOG_DEBUG,"Command doesn't seem to be a get. Closing,"); - return -1; - } - - directory_rebuild(); /* rebuild it now, iff it's dirty */ - - if(directorylen == 0) { - log_fn(LOG_DEBUG,"My directory is empty. Closing."); - return -1; - } - - log_fn(LOG_DEBUG,"Dumping directory to client."); - if((connection_write_to_buf(answerstring, strlen(answerstring), conn) < 0) || - (connection_write_to_buf(the_directory, directorylen, conn) < 0)) { - log_fn(LOG_DEBUG,"my outbuf is full. Oops."); - return -1; - } - - conn->state = DIR_CONN_STATE_WRITING; - return 0; -} - -static int directory_handle_reading(connection_t *conn) { - int amt; - char *headers; - - assert(conn && conn->type == CONN_TYPE_DIR); - - if(reading_headers) { - amt = connection_find_on_inbuf("\r\n\r\n", 4, conn); - if(amt < 0) /* not there yet */ + switch(connection_fetch_from_buf_http(conn, headers, sizeof(headers), body, sizeof(body))) { + case -1: /* overflow */ + log_fn(LOG_DEBUG,"input too large. Failing."); + return -1; + case 0: + log_fn(LOG_DEBUG,"command not all here yet."); return 0; - headers = tor_malloc(amt+1); - connection_fetch_from_buf(headers,amt,conn); - headers[amt] = 0; /* null terminate it, */ - free(headers); /* and then throw it away */ - reading_headers = 0; + /* case 1, fall through */ } - amt = conn->inbuf_datalen; + log_fn(LOG_DEBUG,"headers '%s', body '%s'.",headers,body); + if(!strncasecmp(headers,"GET",3)) { - if(amt + directorylen >= MAX_DIR_SIZE) { - log_fn(LOG_DEBUG,"Directory too large. Failing messily."); - return -1; + directory_rebuild(); /* rebuild it now, iff it's dirty */ + + if(directorylen == 0) { + log_fn(LOG_DEBUG,"My directory is empty. Closing."); + return -1; + } + + log_fn(LOG_DEBUG,"Dumping directory to client."); + if((connection_write_to_buf(answerstring, strlen(answerstring), conn) < 0) || + (connection_write_to_buf(the_directory, directorylen, conn) < 0)) { + log_fn(LOG_DEBUG,"Failed to write answerstring+directory to outbuf."); + return -1; + } + conn->state = DIR_CONN_STATE_SERVER_WRITING; + return 0; } - log_fn(LOG_DEBUG,"Pulling %d bytes in at offset %d.", - amt, directorylen); + if(!strncasecmp(headers,"POST",4)) { + log_fn(LOG_DEBUG,"Received POST command, body '%s'", body); + if(connection_write_to_buf(answerstring, strlen(answerstring), conn) < 0) { + log_fn(LOG_DEBUG,"Failed to write answerstring to outbuf."); + return -1; + } + conn->state = DIR_CONN_STATE_SERVER_WRITING; + return 0; + } - connection_fetch_from_buf(the_directory+directorylen,amt,conn); - - directorylen += amt; - - the_directory[directorylen] = 0; - - return 0; + log_fn(LOG_DEBUG,"Got headers with unknown command. Closing."); + return -1; } int connection_dir_finished_flushing(connection_t *conn) { @@ -233,7 +241,8 @@ int connection_dir_finished_flushing(connection_t *conn) { assert(conn && conn->type == CONN_TYPE_DIR); switch(conn->state) { - case DIR_CONN_STATE_CONNECTING: + case DIR_CONN_STATE_CONNECTING_GET: + case DIR_CONN_STATE_CONNECTING_POST: if (getsockopt(conn->s, SOL_SOCKET, SO_ERROR, (void*)&e, &len) < 0) { /* not yet */ if(!ERRNO_CONN_EINPROGRESS(errno)) { log_fn(LOG_DEBUG,"in-progress connect failed. Removing."); @@ -248,16 +257,19 @@ int connection_dir_finished_flushing(connection_t *conn) { log_fn(LOG_DEBUG,"Dir connection to router %s:%u established.", conn->address,conn->port); - return directory_send_command(conn); - case DIR_CONN_STATE_SENDING_COMMAND: - log_fn(LOG_DEBUG,"client finished sending command."); - directorylen = 0; - reading_headers = 1; - conn->state = DIR_CONN_STATE_READING; + return directory_send_command(conn, conn->state); + case DIR_CONN_STATE_CLIENT_SENDING_GET: + log_fn(LOG_DEBUG,"client finished sending 'get' command."); + conn->state = DIR_CONN_STATE_CLIENT_READING_GET; connection_watch_events(conn, POLLIN); return 0; - case DIR_CONN_STATE_WRITING: - log_fn(LOG_DEBUG,"Finished writing directory. Closing."); + case DIR_CONN_STATE_CLIENT_SENDING_POST: + log_fn(LOG_DEBUG,"client finished sending 'post' command."); + conn->state = DIR_CONN_STATE_CLIENT_READING_POST; + connection_watch_events(conn, POLLIN); + return 0; + case DIR_CONN_STATE_SERVER_WRITING: + log_fn(LOG_DEBUG,"Finished writing server response. Closing."); return -1; /* kill it */ default: log_fn(LOG_DEBUG,"BUG: called in unexpected state."); diff --git a/src/or/main.c b/src/or/main.c index 5d94bbd7fd..7d9be0f061 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -335,7 +335,7 @@ static int prepare_for_poll(void) { /* NOTE directory servers do not currently fetch directories. * Hope this doesn't bite us later. */ - directory_initiate_fetch(router_pick_directory_server()); + directory_initiate_command(router_pick_directory_server(), DIR_CONN_STATE_CONNECTING_GET); time_to_fetch_directory = now.tv_sec + options.DirFetchPeriod; } } @@ -492,7 +492,7 @@ static int do_main_loop(void) { log(LOG_ERR,"Error reloading router list. Continuing with old list."); } } else { - directory_initiate_fetch(router_pick_directory_server()); + directory_initiate_command(router_pick_directory_server(), DIR_CONN_STATE_CONNECTING_GET); } please_fetch_directory = 0; } @@ -772,6 +772,10 @@ dump_signed_directory_to_string_impl(char *s, int maxlen, directory_t *dir, return 0; } +char *router_get_my_descriptor(void) { + return "this is bob's descriptor"; +} + void daemonize(void) { #ifndef MS_WINDOWS /* Fork; parent exits. */ diff --git a/src/or/or.h b/src/or/or.h index b791f786f0..dbd27d0fa6 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -156,12 +156,15 @@ #define _AP_CONN_STATE_MAX 5 #define _DIR_CONN_STATE_MIN 0 -#define DIR_CONN_STATE_CONNECTING 0 /* client */ -#define DIR_CONN_STATE_SENDING_COMMAND 1 /* client */ -#define DIR_CONN_STATE_READING 2 /* client */ -#define DIR_CONN_STATE_COMMAND_WAIT 3 /* dirserver */ -#define DIR_CONN_STATE_WRITING 4 /* dirserver */ -#define _DIR_CONN_STATE_MAX 4 +#define DIR_CONN_STATE_CONNECTING_GET 0 +#define DIR_CONN_STATE_CONNECTING_POST 1 +#define DIR_CONN_STATE_CLIENT_SENDING_GET 2 +#define DIR_CONN_STATE_CLIENT_SENDING_POST 3 +#define DIR_CONN_STATE_CLIENT_READING_GET 4 +#define DIR_CONN_STATE_CLIENT_READING_POST 5 +#define DIR_CONN_STATE_SERVER_COMMAND_WAIT 6 +#define DIR_CONN_STATE_SERVER_WRITING 7 +#define _DIR_CONN_STATE_MAX 7 #define CIRCUIT_STATE_BUILDING 0 /* I'm the OP, still haven't done all my handshakes */ #define CIRCUIT_STATE_ONIONSKIN_PENDING 1 /* waiting to process the onionskin */ @@ -437,6 +440,9 @@ int flush_buf_tls(tor_tls *tls, char **buf, int *buflen, int *buf_flushlen, int int write_to_buf(char *string, int string_len, char **buf, int *buflen, int *buf_datalen); int fetch_from_buf(char *string, int string_len, char **buf, int *buflen, int *buf_datalen); +int fetch_from_buf_http(char *buf, int *buf_datalen, + char *headers_out, int max_headerlen, + char *body_out, int max_bodylen); int find_on_inbuf(char *string, int string_len, char *buf, int buf_datalen); /********************************* circuit.c ***************************/ @@ -504,6 +510,9 @@ int connection_handle_read(connection_t *conn); int connection_read_to_buf(connection_t *conn); int connection_fetch_from_buf(char *string, int len, connection_t *conn); +int connection_fetch_from_buf_http(connection_t *conn, + char *headers_out, int max_headerlen, + char *body_out, int max_bodylen); int connection_find_on_inbuf(char *string, int len, connection_t *conn); int connection_wants_to_flush(connection_t *conn); @@ -559,7 +568,7 @@ int assign_to_cpuworker(connection_t *cpuworker, unsigned char question_type, /********************************* directory.c ***************************/ -void directory_initiate_fetch(routerinfo_t *router); +void directory_initiate_command(routerinfo_t *router, int command); void directory_set_dirty(void); int connection_dir_process_inbuf(connection_t *conn); int connection_dir_finished_flushing(connection_t *conn); @@ -602,6 +611,7 @@ int dump_signed_directory_to_string(char *s, int maxlen, int dump_signed_directory_to_string_impl(char *s, int maxlen, directory_t *dir, crypto_pk_env_t *private_key); +char *router_get_my_descriptor(void); int main(int argc, char *argv[]); diff --git a/src/or/routers.c b/src/or/routers.c index 180713fa85..ca8f567e3d 100644 --- a/src/or/routers.c +++ b/src/or/routers.c @@ -42,7 +42,16 @@ router_resolve_directory(directory_t *dir); int learn_my_address(struct sockaddr_in *me) { /* local host information */ char localhostname[512]; - struct hostent *localhost; + static struct hostent *localhost; + static int already_learned=0; + + if(already_learned) { + memset(me,0,sizeof(struct sockaddr_in)); + me->sin_family = AF_INET; + memcpy((void *)&me->sin_addr,(void *)localhost->h_addr,sizeof(struct in_addr)); + me->sin_port = htons((uint16_t) options.ORPort); + return 0; + } /* obtain local host information */ if(gethostname(localhostname,512) < 0) { @@ -65,6 +74,7 @@ int learn_my_address(struct sockaddr_in *me) { /* We're a loopback IP but we're not called localhost. Uh oh! */ log_fn(LOG_WARNING, "Got a loopback address: /etc/hosts may be wrong"); } + already_learned=1; return 0; } @@ -977,7 +987,6 @@ int router_compare_to_exit_policy(connection_t *conn) { } return 0; /* accept all by default. */ - } /*