diff options
Diffstat (limited to 'src/or')
-rw-r--r-- | src/or/Makefile | 18 | ||||
-rw-r--r-- | src/or/args.c | 99 | ||||
-rw-r--r-- | src/or/buffers.c | 174 | ||||
-rw-r--r-- | src/or/cell.c | 23 | ||||
-rw-r--r-- | src/or/circuit.c | 319 | ||||
-rw-r--r-- | src/or/command.c | 212 | ||||
-rw-r--r-- | src/or/config.c | 49 | ||||
-rw-r--r-- | src/or/connection.c | 362 | ||||
-rw-r--r-- | src/or/connection_app.c | 212 | ||||
-rw-r--r-- | src/or/connection_op.c | 130 | ||||
-rw-r--r-- | src/or/connection_or.c | 580 | ||||
-rw-r--r-- | src/or/main.c | 406 | ||||
-rw-r--r-- | src/or/onion.c | 62 | ||||
-rw-r--r-- | src/or/or.h | 411 | ||||
-rw-r--r-- | src/or/routers.c | 365 |
15 files changed, 3422 insertions, 0 deletions
diff --git a/src/or/Makefile b/src/or/Makefile new file mode 100644 index 000000000..edf2398b4 --- /dev/null +++ b/src/or/Makefile @@ -0,0 +1,18 @@ +SRC=args.c buffers.c cell.c circuit.c command.c connection.c connection_app.c connection_op.c connection_or.c config.c main.c onion.c routers.c +OBJ=${SRC:.c=.o} +PROGS=or +LIB= +LIBS= +INCLUDE = -I/usr/local/ssl/include + +CFLAGS= $(INCLUDE) -Wall -Wpointer-arith -ggdb +LDFLAGS = $(LIB) $(LIBS) + +all: ${OBJ} ${PROGS} + +or: ${OBJ} + gcc -o or $(CFLAGS) *.o ../common/*.o -lcrypto + +clean: + rm -f *.o ${PROGS} + diff --git a/src/or/args.c b/src/or/args.c new file mode 100644 index 000000000..fc3837ca8 --- /dev/null +++ b/src/or/args.c @@ -0,0 +1,99 @@ +/** + * args.c + * Routines for processing command-line arguments. + * + * Matej Pfajfar <mp292@cam.ac.uk> + */ + +/* + * Changes : + * $Log$ + * Revision 1.1 2002/06/26 22:45:50 arma + * Initial revision + * + * Revision 1.3 2002/01/27 00:42:50 mp292 + * Reviewed according to Secure-Programs-HOWTO. + * + * Revision 1.2 2002/01/04 10:05:28 badbytes + * Completed. + * + * Revision 1.1 2002/01/03 10:23:43 badbytes + * Code based on that in op. Needs to be modified. + */ + +#include "or.h" + +/* prints help on using or */ +void print_usage() +{ + char *program = "or"; + printf("\n%s - Onion Router.\nUsage : %s -f config [-l loglevel -h]\n-h : display this help\n-f config : config file\n-l loglevel : logging threshold; one of alert|crit|err|warning|notice|info|debug\n\n", program,program); +} + + +/* get command-line arguments */ +int getargs(int argc,char *argv[], char *args, char **conf_filename, int *loglevel) +{ + char c; /* next option character */ + int gotf=0; + + if ((!args) || (!conf_filename) || (!loglevel)) + return -1; + + while ((c = getopt(argc,argv,args)) != -1) + { + switch(c) + { + case 'f': /* config file */ + *conf_filename = optarg; + gotf=1; + break; + case 'h': + print_usage(argv[0]); + exit(0); + case 'l': + if (!strcmp(optarg,"emerg")) + *loglevel = LOG_EMERG; + else if (!strcmp(optarg,"alert")) + *loglevel = LOG_ALERT; + else if (!strcmp(optarg,"crit")) + *loglevel = LOG_CRIT; + else if (!strcmp(optarg,"err")) + *loglevel = LOG_ERR; + else if (!strcmp(optarg,"warning")) + *loglevel = LOG_WARNING; + else if (!strcmp(optarg,"notice")) + *loglevel = LOG_NOTICE; + else if (!strcmp(optarg,"info")) + *loglevel = LOG_INFO; + else if (!strcmp(optarg,"debug")) + *loglevel = LOG_DEBUG; + else + { + log(LOG_ERR,"Error : argument to -l must be one of alert|crit|err|warning|notice|info|debug."); + print_usage(argv[0]); + return -1; + } + break; + case '?': + if (isprint(c)) + log(LOG_ERR,"Missing argument or unknown option '-%c'. See help (-h).",optopt); + else + log(LOG_ERR,"Unknown option character 'x%x'. See help (-h).",optopt); + print_usage(argv[0]); + return -1; + break; + default: + return -1; + } + } + + /* the -f option is mandatory */ + if (!gotf) + { + log(LOG_ERR,"You must specify a config file with the -f option. See help (-h)."); + return -1; + } + + return 0; +} diff --git a/src/or/buffers.c b/src/or/buffers.c new file mode 100644 index 000000000..6900ca300 --- /dev/null +++ b/src/or/buffers.c @@ -0,0 +1,174 @@ + +/* buffers.c */ + +#include "or.h" + +int buf_new(char **pbuf, size_t *pbuflen, size_t *pbuf_datalen) { + + if (!pbuf || !pbuflen || !pbuf_datalen) /* invalid parameters */ + return -1; + + *pbuf = (char *)malloc(MAX_BUF_SIZE); + if(!*pbuf) + return -1; + memset(*pbuf,0,MAX_BUF_SIZE); + *pbuflen = MAX_BUF_SIZE; + *pbuf_datalen = 0; + + return 0; +} + +int buf_free(char *buf) { + + free(buf); + + return 0; +} + +int read_to_buf(int s, char **pbuf, size_t *pbuflen, size_t *pbuf_datalen, int *preached_eof) { + + /* grab from s, put onto buf, return how many bytes read */ + + int read_result; + char *buf; + size_t buflen; + size_t buf_datalen; + + if (!pbuf || !pbuflen || !pbuf_datalen || !preached_eof) /* invalid parameters */ + return -1 ; + + if(s<0) { + log(LOG_DEBUG,"read_to_buf() received negative socket %d.",s); + return -1; + } + + /* this is the point where you would grow the buffer, if you want to */ + buf = *pbuf, buflen = *pbuflen, buf_datalen = *pbuf_datalen; + + if (!buf) /* invalid parameter */ + return -1; + + read_result = read(s, buf+buf_datalen, buflen - buf_datalen); + if (read_result < 0) { + if(errno!=EAGAIN) { /* it's a real error */ + return -1; + } + return 0; + } else if (read_result == 0) { + log(LOG_DEBUG,"read_to_buf(): Encountered eof"); + *preached_eof = 1; + return 0; + } else { /* we read some bytes */ + *pbuf_datalen = buf_datalen + read_result; + log(LOG_DEBUG,"read_to_buf(): Read %d bytes. %d on inbuf.",read_result, *pbuf_datalen); + return read_result; + } + +} + +int flush_buf(int s, char **pbuf, size_t *pbuflen, size_t *pbuf_datalen) { + + /* push from buf onto s + * then memmove to front of buf + * return -1 or how many bytes remain on the buf */ + + int write_result; + char *buf; + size_t buflen; + size_t buf_datalen; + + if (!pbuf || !pbuflen || !pbuf_datalen) /* invalid parameters */ + return -1; + + if(s<0) { + log(LOG_DEBUG,"flush_buf() received negative socket %d.",s); + return -1; + } + + + if(*pbuf_datalen == 0) /* nothing to flush */ + return 0; + + /* this is the point where you would grow the buffer, if you want to */ + buf = *pbuf, buflen = *pbuflen, buf_datalen = *pbuf_datalen; + + if (!buf) /* invalid parameter */ + return -1; + + write_result = write(s, buf, buf_datalen); + if (write_result < 0) { + if(errno!=EAGAIN) { /* it's a real error */ + return -1; + } + log(LOG_DEBUG,"flush_buf(): write() would block, returning."); + return 0; + } else { + *pbuf_datalen -= write_result; + memmove(buf, buf+write_result, *pbuf_datalen); + log(LOG_DEBUG,"flush_buf(): flushed %d bytes, %d remain.",write_result,*pbuf_datalen); + return *pbuf_datalen; + } + +} + +int write_to_buf(char *string, size_t string_len, + char **pbuf, size_t *pbuflen, size_t *pbuf_datalen) { + + /* append string to buf (growing as needed, return -1 if "too big") + * return total number of bytes on the buf + */ + + char *buf; + size_t buflen; + size_t buf_datalen; + + if (!string || !pbuf || !pbuflen || !pbuf_datalen) /* invalid parameters */ + return -1; + + /* this is the point where you would grow the buffer, if you want to */ + buf = *pbuf, buflen = *pbuflen, buf_datalen = *pbuf_datalen; + + if (!buf) /* invalid parameter */ + return -1; + + if (string_len + buf_datalen > buflen) { /* we're out of luck */ + log(LOG_DEBUG, "write_to_buf(): buflen too small. Time to implement growing dynamic bufs."); + return -1; + } + + memcpy(buf+buf_datalen, string, string_len); + *pbuf_datalen += string_len; + log(LOG_DEBUG,"write_to_buf(): added %d bytes to buf (now %d total).",string_len, *pbuf_datalen); + return *pbuf_datalen; + +} + +int fetch_from_buf(char *string, size_t string_len, + char **pbuf, size_t *pbuflen, size_t *pbuf_datalen) { + + /* if there is string_len bytes in buf, write them onto string, + * then memmove buf back (that is, remove them from buf) */ + + char *buf; + size_t buflen; + size_t buf_datalen; + + if (!string || !pbuf || !pbuflen || !pbuf_datalen) /* invalid parameters */ + return -1; + + /* this is the point where you would grow the buffer, if you want to */ + buf = *pbuf, buflen = *pbuflen, buf_datalen = *pbuf_datalen; + + if (!buf) /* invalid parameter */ + return -1; + + if(string_len > buf_datalen) /* we want too much. sorry. */ + return -1; + + memcpy(string,buf,string_len); + *pbuf_datalen -= string_len; + memmove(buf, buf+string_len, *pbuf_datalen); + return *pbuf_datalen; + +} + diff --git a/src/or/cell.c b/src/or/cell.c new file mode 100644 index 000000000..ef53a9eae --- /dev/null +++ b/src/or/cell.c @@ -0,0 +1,23 @@ + +#include "or.h" + +int check_sane_cell(cell_t *cell) { + + if(!cell) + return -1; + + if(cell->aci == 0) { + log(LOG_DEBUG,"check_sane_cell(): Cell has aci=0. Dropping."); + return -1; + } + +#if 0 /* actually, the length is sometimes encrypted. so it's ok. */ + if(cell->length > 120) { + log(LOG_DEBUG,"check_sane_cell(): Cell claims to have payload length %d. Dropping.",cell->length); + return -1; + } +#endif + + return 0; /* looks good */ +} + diff --git a/src/or/circuit.c b/src/or/circuit.c new file mode 100644 index 000000000..b910fd351 --- /dev/null +++ b/src/or/circuit.c @@ -0,0 +1,319 @@ + +#include "or.h" + +/********* START VARIABLES **********/ + +circuit_t *global_circuitlist=NULL; + +/********* END VARIABLES ************/ + +void circuit_add(circuit_t *circ) { + + if(!global_circuitlist) { /* first one */ + global_circuitlist = circ; + circ->next = NULL; + } else { + circ->next = global_circuitlist; + global_circuitlist = circ; + } + +} + +void circuit_remove(circuit_t *circ) { + circuit_t *tmpcirc; + + if(!circ || !global_circuitlist) + return; + + if(global_circuitlist == circ) { + global_circuitlist = global_circuitlist->next; + return; + } + + for(tmpcirc = global_circuitlist;tmpcirc->next;tmpcirc = tmpcirc->next) { + if(tmpcirc->next == circ) { + tmpcirc->next = circ->next; + return; + } + } + +} + +circuit_t *circuit_new(aci_t p_aci, connection_t *p_conn) { + + circuit_t *circ; + + circ = (circuit_t *)malloc(sizeof(circuit_t)); + if(!circ) + return NULL; + memset(circ,0,sizeof(circuit_t)); /* zero it out */ + + circ->p_aci = p_aci; + circ->p_conn = p_conn; + + circ->state = CIRCUIT_STATE_OPEN_WAIT; + circ->onion = NULL; + circ->onionlen=0; + circ->recvlen=0; + + /* ACIs */ + circ->p_aci = p_aci; + circ->n_aci = 0; /* we need to have identified the next hop to choose a correct ACI */ + + circuit_add(circ); + + return circ; +} + +void circuit_free(circuit_t *circ) { + + EVP_CIPHER_CTX_cleanup(&circ->n_ctx); + EVP_CIPHER_CTX_cleanup(&circ->p_ctx); + + if(circ->onion) + free(circ->onion); + + free(circ); + +} + +aci_t get_unique_aci_by_addr_port(uint32_t addr, uint16_t port, int aci_type) { + aci_t test_aci; + connection_t *conn; + + log(LOG_DEBUG,"get_unique_aci_by_addr_port() trying to get a unique aci"); + + RAND_pseudo_bytes((unsigned char *)&test_aci, 2); + + if(aci_type == ACI_TYPE_LOWER) + test_aci &= htons(0x00FF); + if(aci_type == ACI_TYPE_HIGHER) + test_aci &= htons(0xFF00); + + if(test_aci == 0) + return get_unique_aci_by_addr_port(addr, port, aci_type); /* try again */ + + conn = connection_get_by_addr_port(addr,port); + if(!conn) /* there can't be a conflict -- no connection of that sort yet */ + return test_aci; + + if(circuit_get_by_aci_conn(test_aci, conn)) + return get_unique_aci_by_addr_port(addr, port, aci_type); /* try again */ + + return test_aci; + +} + +int circuit_init(circuit_t *circ, int aci_type) { + onion_layer_t *ol; + int retval = 0; + unsigned char digest1[20]; + unsigned char digest2[20]; + + if (!circ) + return -1; + + ol = (onion_layer_t *)circ->onion; + if (!ol) + return -1; + + log(LOG_DEBUG,"circuit_init(): starting"); + circ->n_addr = ol->addr; + circ->n_port = ol->port; + log(LOG_DEBUG,"circuit_init(): Set port to %u.",ntohs(ol->port)); + circ->p_f = ol->backf; + log(LOG_DEBUG,"circuit_init(): Set BACKF to %u.",ol->backf); + circ->n_f = ol->forwf; + log(LOG_DEBUG,"circuit_init(): Set FORWF to %u.",ol->forwf); + circ->state = CIRCUIT_STATE_OPEN; + + log(LOG_DEBUG,"circuit_init(): aci_type = %u.",aci_type); + + circ->n_aci = get_unique_aci_by_addr_port(circ->n_addr, circ->n_port, aci_type); + + log(LOG_DEBUG,"circuit_init(): Chosen ACI %u.",circ->n_aci); + + /* keys */ + SHA1(ol->keyseed,16,digest1); + SHA1(digest1,20,digest2); + SHA1(digest2,20,digest1); + memcpy(circ->p_key,digest2,16); + memcpy(circ->n_key,digest1,16); + log(LOG_DEBUG,"circuit_init(): Computed keys."); + + /* set IVs to zero */ + memset(circ->n_iv,0,16); + memset(circ->p_iv,0,16); + + /* initialize cipher context */ + EVP_CIPHER_CTX_init(&circ->n_ctx); + EVP_CIPHER_CTX_init(&circ->p_ctx); + + /* initialize crypto engines */ + switch(circ->p_f) + { + case ONION_CIPHER_DES : + retval = EVP_EncryptInit(&circ->p_ctx, EVP_des_ofb(), circ->p_key, circ->p_iv); + break; + case ONION_CIPHER_RC4 : + retval = EVP_EncryptInit(&circ->p_ctx, EVP_rc4(), circ->p_key,circ->p_iv); + break; + case ONION_CIPHER_IDENTITY : + retval = EVP_EncryptInit(&circ->p_ctx, EVP_enc_null(), circ->p_key, circ->p_iv); + break; + default : + log(LOG_ERR,"Onion contains unrecognized cipher(%u) for ACI : %u.",circ->p_f,circ->n_aci); + return -1; + break; + } + + if (!retval) /* EVP_EncryptInit() error */ + { + log(LOG_ERR,"Cipher initialization failed (ACI %u).",circ->n_aci); + EVP_CIPHER_CTX_cleanup(&circ->n_ctx); + EVP_CIPHER_CTX_cleanup(&circ->p_ctx); + return -1; + } + switch(circ->n_f) + { + case ONION_CIPHER_DES : + retval = EVP_DecryptInit(&circ->n_ctx, EVP_des_ofb(), circ->n_key, circ->n_iv); + break; + case ONION_CIPHER_RC4 : + retval = EVP_DecryptInit(&circ->n_ctx, EVP_rc4(), circ->n_key,circ->n_iv); + break; + case ONION_CIPHER_IDENTITY : + retval = EVP_DecryptInit(&circ->n_ctx, EVP_enc_null(), circ->n_key, circ->n_iv); + break; + default : + log(LOG_ERR,"Onion contains unrecognized cipher for ACI : %u.",circ->n_aci); + return -1; + break; + } + if (!retval) /* EVP_EncryptInit() error */ + { + log(LOG_ERR,"Cipher initialization failed (ACI %u).",circ->n_aci); + EVP_CIPHER_CTX_cleanup(&circ->n_ctx); + EVP_CIPHER_CTX_cleanup(&circ->p_ctx); + return -1; + } + log(LOG_DEBUG,"circuit_init(): Cipher initialization complete."); + + circ->expire = ol->expire; + + return 0; +} + +circuit_t *circuit_get_by_aci_conn(aci_t aci, connection_t *conn) { + circuit_t *circ; + + for(circ=global_circuitlist;circ;circ = circ->next) { + if(circ->p_conn == conn && circ->p_aci == aci) + return circ; + if(circ->n_conn == conn && circ->n_aci == aci) + return circ; + } + return NULL; +} + +circuit_t *circuit_get_by_conn(connection_t *conn) { + circuit_t *circ; + + for(circ=global_circuitlist;circ;circ = circ->next) { + if(circ->p_conn == conn) + return circ; + if(circ->n_conn == conn) + return circ; + } + return NULL; +} + +int circuit_deliver_data_cell(cell_t *cell, circuit_t *circ, connection_t *conn, int crypt_type) { + + /* first decrypt cell->length */ + if(circuit_crypt(circ, &(cell->length), 1, crypt_type) < 0) { + log(LOG_DEBUG,"circuit_deliver_data_cell(): length decryption failed. Dropping connection."); + return -1; + } + + /* then decrypt the payload */ + if(circuit_crypt(circ, (char *)&(cell->payload), CELL_PAYLOAD_SIZE, crypt_type) < 0) { + log(LOG_DEBUG,"circuit_deliver_data_cell(): payload decryption failed. Dropping connection."); + return -1; + } + + if(conn->type == CONN_TYPE_APP) { /* send payload directly */ + log(LOG_DEBUG,"circuit_deliver_data_cell(): Sending to application."); + if(connection_app_process_data_cell(cell, conn) < 0) { + return -1; + } + } else { /* send it as a cell */ + log(LOG_DEBUG,"circuit_deliver_data_cell(): Sending to connection."); + if(connection_write_cell_to_buf(cell, conn) < 0) { + return -1; + } + } + return 0; /* success */ +} + +int circuit_crypt(circuit_t *circ, char *in, size_t inlen, char crypt_type) { + char *out; + int outlen; + + if(!circ || !in) + return -1; + + out = malloc(inlen); + if(!out) + return -1; + + if(crypt_type == 'e') { + log(LOG_DEBUG,"circuit_crypt(): Encrypting %d bytes.",inlen); + if(!EVP_EncryptUpdate(&circ->p_ctx,out,&outlen,in,inlen)) { + log(LOG_ERR,"circuit_encrypt(): Encryption failed for ACI : %u (%s).",circ->p_aci, ERR_reason_error_string(ERR_get_error())); + return -1; + } + } else if(crypt_type == 'd') { + log(LOG_DEBUG,"circuit_crypt(): Decrypting %d bytes.",inlen); + if(!EVP_DecryptUpdate(&circ->n_ctx,out,&outlen,in,inlen)) { + log(LOG_ERR,"circuit_crypt(): Decryption failed for ACI : %u (%s).",circ->n_aci, ERR_reason_error_string(ERR_get_error())); + return -1; + } + } + + if(outlen != inlen) { + log(LOG_DEBUG,"circuit_crypt(): %d bytes crypted to %d bytes. Weird.",inlen,outlen); + return -1; + } + + memcpy(in,out,inlen); + free(out); + + return 0; +} + +void circuit_close(circuit_t *circ) { + circuit_remove(circ); + connection_send_destroy(circ->n_aci, circ->n_conn); + connection_send_destroy(circ->p_aci, circ->p_conn); + circuit_free(circ); +} + +void circuit_about_to_close_connection(connection_t *conn) { + /* send destroys for all circuits using conn */ + /* currently, we assume it's too late to flush conn's buf here. + * down the road, maybe we'll consider that eof doesn't mean can't-write + */ + + circuit_t *circ; + + while((circ = circuit_get_by_conn(conn))) { + circuit_remove(circ); + if(circ->n_conn == conn) /* it's closing in front of us */ + connection_send_destroy(circ->p_aci, circ->p_conn); + if(circ->p_conn == conn) /* it's closing behind us */ + connection_send_destroy(circ->n_aci, circ->n_conn); + circuit_free(circ); + } +} + diff --git a/src/or/command.c b/src/or/command.c new file mode 100644 index 000000000..2392a9bf2 --- /dev/null +++ b/src/or/command.c @@ -0,0 +1,212 @@ + +#include "or.h" + +void command_process_cell(cell_t *cell, connection_t *conn) { + + if(check_sane_cell(cell) < 0) + return; + + switch(cell->command) { + case CELL_PADDING: + /* do nothing */ + break; + case CELL_CREATE: + command_process_create_cell(cell, conn); + break; + case CELL_DATA: + command_process_data_cell(cell, conn); + break; + case CELL_DESTROY: + command_process_destroy_cell(cell, conn); + break; + } +} + +void command_process_create_cell(cell_t *cell, connection_t *conn) { + circuit_t *circ; + connection_t *n_conn; + unsigned char *cellbuf; /* array of cells */ + int cellbuflen; /* size of cellbuf in bytes */ + cell_t *tmpcell; /* pointer to an arbitrary cell */ + int retval, i; + + circ = circuit_get_by_aci_conn(cell->aci, conn); + + if(circ && circ->state != CIRCUIT_STATE_OPEN_WAIT) { + log(LOG_DEBUG,"command_process_create_cell(): received CREATE cell, not in open_wait. Dropping."); + return; + } + + if(!circ) { /* if it's not there, create it */ + circ = circuit_new(cell->aci, conn); + circ->state = CIRCUIT_STATE_OPEN_WAIT; + memcpy((void *)&circ->onionlen,(void *)cell->payload, 4); + circ->onionlen = ntohl(circ->onionlen); + log(LOG_DEBUG,"command_process_create_cell(): Onion length is %u.",circ->onionlen); + if(circ->onionlen > 50000) { /* too big */ + log(LOG_DEBUG,"That's ludicrous. Closing."); + circuit_close(circ); + return; + } + circ->onion = (unsigned char *)malloc(circ->onionlen); + if(!circ->onion) { + log(LOG_DEBUG,"command_process_create_cell(): Out of memory. Closing."); + circuit_close(circ); + return; + } + if(circ->onionlen < cell->length-4) { /* protect from buffer overflow */ + log(LOG_DEBUG,"command_process_create_cell(): Onion too small. Closing."); + circuit_close(circ); + return; + } + memcpy((void *)circ->onion,(void *)(cell->payload+4),cell->length-4); + circ->recvlen = cell->length-4; + log(LOG_DEBUG,"command_process_create_cell(): Primary create cell handled, have received %d of %d onion bytes.", + circ->recvlen,circ->onionlen); + + } else { /* pull over as much of the onion as we can */ + if(cell->length + circ->recvlen > circ->onionlen) { /* protect from buffer overflow */ + log(LOG_DEBUG,"command_process_create_cell(): payload too big for onion. Closing."); + circuit_close(circ); + return; + } + memcpy((void *)(circ->onion+circ->recvlen),(void *)cell->payload,cell->length); + circ->recvlen += cell->length; + log(LOG_DEBUG,"command_process_create_cell(): Secondary create cell handled, have received %d of %d onion bytes.", + circ->recvlen,circ->onionlen); + } + + if(circ->recvlen != circ->onionlen) { + log(LOG_DEBUG,"command_process_create_cell(): Onion not all here yet. Ok."); + return; + } + + /* we're all ready to go now. */ + circ->state = CIRCUIT_STATE_OPEN; + + if(process_onion(circ, conn) < 0) { + log(LOG_DEBUG,"command_process_create_cell(): Onion processing failed. Closing."); + circuit_close(circ); + return; + } + + if(circ->n_addr && circ->n_port) { /* must send create cells to the next router */ + n_conn = connection_get_by_addr_port(circ->n_addr,circ->n_port); + if(!n_conn || n_conn->type != CONN_TYPE_OR) { + /* i've disabled making connections through OPs, but it's definitely + * possible here. I'm not sure if it would be a bug or a feature. -RD + */ + log(LOG_DEBUG,"command_process_create_cell(): Next router not connected. Closing."); + circuit_close(circ); + } + circ->n_conn = n_conn; + log(LOG_DEBUG,"command_process_create_cell(): n_conn is %s:%u",n_conn->address,ntohs(n_conn->port)); + + /* send the CREATE cells on to the next hop */ + pad_onion(circ->onion,circ->onionlen, sizeof(onion_layer_t)); + log(LOG_DEBUG,"command_process_create_cell(): Padded the onion with random data."); + + retval = pack_create(circ->n_aci, circ->onion, circ->onionlen, &cellbuf, &cellbuflen); + free((void *)circ->onion); + circ->onion = NULL; + if (retval == -1) /* pack_create() error */ + { + log(LOG_DEBUG,"command_process_create_cell(): Could not pack the onion into CREATE cells. Closing the connection."); + circuit_close(circ); + return; + } + log(LOG_DEBUG,"command_process_create_cell(): Onion packed into CREATE cells. Buffering the cells."); + /* queue the create cells for transmission to the next hop */ + tmpcell = (cell_t *)cellbuf; + for (i=0;i<cellbuflen/sizeof(cell_t);i++) + { + retval = connection_write_cell_to_buf(tmpcell, n_conn); + if (retval == -1) /* buffering failed, drop the connection */ + { + log(LOG_DEBUG,"command_process_create_cell(): Could not buffer new create cells. Closing."); + circuit_close(circ); + return; + } + tmpcell++; + } + free((void *)cellbuf); + return; + + } else { /* this is destined for an app */ + log(LOG_DEBUG,"command_process_create_cell(): Creating new application connection."); + n_conn = connection_new(CONN_TYPE_APP); + if(!n_conn) { + log(LOG_DEBUG,"command_process_create_cell(): connection_new failed. Closing."); + circuit_close(circ); + return; + } + n_conn->state = APP_CONN_STATE_CONNECTING_WAIT; + n_conn->s = -1; /* not yet valid */ + if(connection_add(n_conn) < 0) { /* no space, forget it */ + log(LOG_DEBUG,"command_process_create_cell(): connection_add failed. Closing."); + connection_free(n_conn); + circuit_close(circ); + return; + } + circ->n_conn = n_conn; + return; + } + +} + +void command_process_data_cell(cell_t *cell, connection_t *conn) { + circuit_t *circ; + + /* FIXME do something with 'close' state, here */ + + circ = circuit_get_by_aci_conn(cell->aci, conn); + + if(!circ) { + log(LOG_DEBUG,"command_process_data_cell(): received DATA cell for unknown circuit. Dropping."); + return; + } + + if(circ->state == CIRCUIT_STATE_OPEN_WAIT) { + log(LOG_DEBUG,"command_process_data_cell(): circuit in open_wait. Dropping data cell."); + return; + } + + /* at this point both circ->n_conn and circ->p_conn are guaranteed to be set */ + + if(cell->aci == circ->p_aci) { /* it's an outgoing cell */ + cell->aci = circ->n_aci; /* switch it */ + if(circuit_deliver_data_cell(cell, circ, circ->n_conn, 'd') < 0) { + log(LOG_DEBUG,"command_process_data_cell(): circuit_deliver_data_cell (forward) failed. Closing."); + circuit_close(circ); + return; + } + } else { /* it's an ingoing cell */ + cell->aci = circ->p_aci; /* switch it */ + if(circuit_deliver_data_cell(cell, circ, circ->p_conn, 'e') < 0) { + log(LOG_DEBUG,"command_process_data_cell(): circuit_deliver_data_cell (backward) failed. Closing."); + circuit_close(circ); + return; + } + } +} + +void command_process_destroy_cell(cell_t *cell, connection_t *conn) { + circuit_t *circ; + + circ = circuit_get_by_aci_conn(cell->aci, conn); + + if(!circ) { + log(LOG_DEBUG,"command_process_destroy_cell(): received DESTROY cell for unknown circuit. Dropping."); + return; + } + + log(LOG_DEBUG,"command_process_destroy_cell(): Received for aci %d.",cell->aci); + circuit_remove(circ); + if(cell->aci == circ->p_aci) /* the destroy came from behind */ + connection_send_destroy(circ->n_aci, circ->n_conn); + if(cell->aci == circ->n_aci) /* the destroy came from ahead */ + connection_send_destroy(circ->p_aci, circ->p_conn); + circuit_free(circ); + +} + diff --git a/src/or/config.c b/src/or/config.c new file mode 100644 index 000000000..535f7638d --- /dev/null +++ b/src/or/config.c @@ -0,0 +1,49 @@ +/** + * config.c + * Routines for loading the configuration file. + * + * Matej Pfajfar <mp292@cam.ac.uk> + */ + +/* + * Changes : + * $Log$ + * Revision 1.1 2002/06/26 22:45:50 arma + * Initial revision + * + * Revision 1.3 2002/04/02 14:28:24 badbytes + * Final finishes. + * + * Revision 1.2 2002/01/27 00:42:50 mp292 + * Reviewed according to Secure-Programs-HOWTO. + * + * Revision 1.1 2002/01/03 10:24:05 badbytes + * COde based on that in op. Needs to be modified. + * + */ + +#include "or.h" + +/* loads the configuration file */ +int getconfig(char *conf_filename, config_opt_t *options) +{ + FILE *cf = NULL; + int retval = 0; + + if ((!conf_filename) || (!options)) + return -1; + + /* load config file */ + cf = open_config(conf_filename); + if (!cf) + { + log(LOG_ERR,"Could not open configuration file %s.",conf_filename); + return -1; + } + retval = parse_config(cf,options); + if (retval) + return -1; + + return 0; +} + diff --git a/src/or/connection.c b/src/or/connection.c new file mode 100644 index 000000000..7411852ac --- /dev/null +++ b/src/or/connection.c @@ -0,0 +1,362 @@ + +#include "or.h" + +/********* START VARIABLES **********/ + +char *conn_type_to_string[] = { + "OP listener", /* 0 */ + "OP", /* 1 */ + "OR listener", /* 2 */ + "OR", /* 3 */ + "App" /* 4 */ +}; + +char *conn_state_to_string[][10] = { + { "ready" }, /* op listener, 0 */ + { "awaiting keys", /* op, 0 */ + "open", /* 1 */ + "close", /* 2 */ + "close_wait" }, /* 3 */ + { "ready" }, /* or listener, 0 */ + { "connecting (as client)", /* or, 0 */ + "sending auth (as client)", /* 1 */ + "waiting for auth (as client)", /* 2 */ + "sending nonce (as client)", /* 3 */ + "waiting for auth (as server)", /* 4 */ + "sending auth (as server)", /* 5 */ + "waiting for nonce (as server)",/* 6 */ + "open" }, /* 7 */ + { "connecting", /* app, 0 */ + "open", /* 1 */ + "waiting for dest info", /* 2 */ + "flushing buffer, then will close",/* 3 */ + "close_wait" } /* 4 */ +}; + +/********* END VARIABLES ************/ + +connection_t *connection_new(int type) { + connection_t *conn; + + conn = (connection_t *)malloc(sizeof(connection_t)); + if(!conn) + return NULL; + memset(conn,0,sizeof(connection_t)); /* zero it out to start */ + + conn->type = type; + buf_new(&conn->inbuf, &conn->inbuflen, &conn->inbuf_datalen); + buf_new(&conn->outbuf, &conn->outbuflen, &conn->outbuf_datalen); + + return conn; +} + +void connection_free(connection_t *conn) { + assert(conn); + + buf_free(conn->inbuf); + buf_free(conn->outbuf); + if(conn->address) + free(conn->address); + + /* FIXME should we do these for all connections, or just ORs, or what */ + if(conn->type == CONN_TYPE_OR || + conn->type == CONN_TYPE_OP) { +// EVP_CIPHER_CTX_cleanup(&conn->f_ctx); +// EVP_CIPHER_CTX_cleanup(&conn->b_ctx); + } + + if(conn->s > 0) + close(conn->s); + free(conn); +} + +int connection_create_listener(RSA *prkey, struct sockaddr_in *local, int type) { + connection_t *conn; + int s; + int one=1; + + s = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); + if (s < 0) + { + log(LOG_ERR,"connection_create_listener(): Socket creation failed."); + return -1; + } + + setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + + if(bind(s,(struct sockaddr *)local,sizeof(*local)) < 0) { + perror("bind "); + log(LOG_ERR,"Could not bind to local port %u.",ntohs(local->sin_port)); + return -1; + } + + /* start local server */ + if(listen(s,SOMAXCONN) < 0) { + log(LOG_ERR,"Could not listen on local port %u.",ntohs(local->sin_port)); + return -1; + } + + fcntl(s, F_SETFL, O_NONBLOCK); /* set s to non-blocking */ + + conn = connection_new(type); + conn->s = s; + + if(connection_add(conn) < 0) { /* no space, forget it */ + connection_free(conn); + return -1; + } + + /* remember things so you can tell the baby sockets */ + memcpy(&conn->local,local,sizeof(struct sockaddr_in)); + conn->prkey = prkey; + + log(LOG_DEBUG,"connection_create_listener(): Listening on local port %u.",ntohs(local->sin_port)); + + conn->state = LISTENER_STATE_READY; + connection_watch_events(conn, POLLIN); + + return 0; +} + +int connection_handle_listener_read(connection_t *conn, int new_type, int new_state) { + + int news; /* the new socket */ + connection_t *newconn; + struct sockaddr_in remote; /* information about the remote peer when connecting to other routers */ + int remotelen = sizeof(struct sockaddr_in); /* length of the remote address */ + + news = accept(conn->s,(struct sockaddr *)&remote,&remotelen); + if (news == -1) { /* accept() error */ + if(errno==EAGAIN) + return 0; /* he hung up before we could accept(). that's fine. */ + /* else there was a real error. */ + log(LOG_ERR,"connection_handle_listener_read(): accept() failed. Closing."); + return -1; + } + log(LOG_DEBUG,"Connection accepted on socket %d.",news); + + newconn = connection_new(new_type); + newconn->s = news; + + /* learn things from parent, so we can perform auth */ + memcpy(&newconn->local,&conn->local,sizeof(struct sockaddr_in)); + newconn->prkey = conn->prkey; +// newconn->address = strdup(get_string_from_remote()) FIXME ; + + if(connection_add(newconn) < 0) { /* no space, forget it */ + connection_free(newconn); + return -1; + } + + log(LOG_DEBUG,"connection_handle_listener_read(): socket %d entered state %d.",newconn->s, new_state); + newconn->state = new_state; + connection_watch_events(newconn, POLLIN); + + return 0; +} + +int retry_all_connections(routerinfo_t **router_array, int rarray_len, + RSA *prkey, uint16_t or_port, uint16_t op_port) { + + /* start all connections that should be up but aren't */ + + routerinfo_t *router; + int i; + + /* local host information */ + char localhostname[512]; + struct hostent *localhost; + struct sockaddr_in local; /* local address */ + + /* obtain local host information */ + if(gethostname(localhostname,512) < 0) { + log(LOG_ERR,"Error obtaining local hostname."); + return -1; + } + localhost = gethostbyname(localhostname); + if (!localhost) { + log(LOG_ERR,"Error obtaining local host info."); + return -1; + } + memset((void *)&local,0,sizeof(local)); + local.sin_family = AF_INET; + local.sin_addr.s_addr = INADDR_ANY; + local.sin_port = htons(or_port); + memcpy((void *)&local.sin_addr,(void *)localhost->h_addr,sizeof(struct in_addr)); + + for (i=0;i<rarray_len;i++) { + router = router_array[i]; + if(!connection_get_by_addr_port(router->addr,router->port)) { /* not in the list */ + connect_to_router(router, prkey, &local); + } + } + + if(!connection_get_by_type(CONN_TYPE_OR_LISTENER)) { + connection_or_create_listener(prkey, &local); + } + + local.sin_port = htons(op_port); + if(!connection_get_by_type(CONN_TYPE_OP_LISTENER)) { + connection_op_create_listener(prkey, &local); + } + + return 0; + +} + +int connection_read_to_buf(connection_t *conn) { + return read_to_buf(conn->s, &conn->inbuf, &conn->inbuflen, &conn->inbuf_datalen, &conn->inbuf_reached_eof); +} + +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_flush_buf(connection_t *conn) { + return flush_buf(conn->s, &conn->outbuf, &conn->outbuflen, &conn->outbuf_datalen); +} + +int connection_write_to_buf(char *string, int len, connection_t *conn) { + if(!len) + return 0; + connection_watch_events(conn, POLLOUT | POLLIN); + return write_to_buf(string, len, &conn->outbuf, &conn->outbuflen, &conn->outbuf_datalen); +} + +int connection_send_destroy(aci_t aci, connection_t *conn) { + cell_t cell; + + if(!conn) + return -1; + + if(conn->type == CONN_TYPE_OP || + conn->type == CONN_TYPE_APP) { + log(LOG_DEBUG,"connection_send_destroy(): At an edge. Marking connection for close."); + conn->marked_for_close = 1; + return 0; + } + + cell.aci = aci; + cell.command = CELL_DESTROY; + log(LOG_DEBUG,"connection_send_destroy(): Sending destroy (aci %d).",aci); + return connection_write_cell_to_buf(&cell, conn); + +} + +int connection_write_cell_to_buf(cell_t *cellp, connection_t *conn) { + /* FIXME in the future, we should modify windows, etc, here */ + + if(connection_encrypt_cell_header(cellp,conn)<0) { + return -1; + } + + return connection_write_to_buf((char *)cellp, sizeof(cell_t), conn); + +} + +int connection_encrypt_cell_header(cell_t *cellp, connection_t *conn) { + char newheader[8]; + int newsize; + int x; + char *px; + + printf("Sending: Cell header plaintext: "); + px = (char *)cellp; + for(x=0;x<8;x++) { + printf("%u ",px[x]); + } + printf("\n"); + + if(!EVP_EncryptUpdate(&conn->f_ctx, newheader, &newsize, (char *)cellp, 8)) { + log(LOG_ERR,"Could not encrypt data for connection %s:%u.",conn->address,ntohs(conn->port)); + return -1; + } + printf("Sending: Cell header crypttext: "); + for(x=0;x<8;x++) { + printf("%u ",newheader[x]); + } + printf("\n"); + + memcpy(cellp,newheader,8); + return 0; +} + +int connection_process_inbuf(connection_t *conn) { + + assert(conn); + + switch(conn->type) { + case CONN_TYPE_OP: + return connection_op_process_inbuf(conn); + case CONN_TYPE_OR: + return connection_or_process_inbuf(conn); + case CONN_TYPE_APP: + return connection_app_process_inbuf(conn); + default: + log(LOG_DEBUG,"connection_process_inbuf() got unexpected conn->type."); + return -1; + } +} + +int connection_finished_flushing(connection_t *conn) { + + assert(conn); + + log(LOG_DEBUG,"connection_finished_flushing() entered. Socket %u.", conn->s); + + switch(conn->type) { + case CONN_TYPE_OP: + return connection_op_finished_flushing(conn); + case CONN_TYPE_OR: + return connection_or_finished_flushing(conn); + case CONN_TYPE_APP: + return connection_app_finished_flushing(conn); + default: + log(LOG_DEBUG,"connection_finished_flushing() got unexpected conn->type."); + return -1; + } +} + +int connection_process_cell_from_inbuf(connection_t *conn) { + /* check if there's a whole cell there. + * if yes, pull it off, decrypt it, and process it. + */ + char crypted[128]; + char outbuf[1024]; + int outlen; + int x; + cell_t *cellp; + + if(conn->inbuf_datalen < 128) /* entire response available? */ + return 0; /* not yet */ + + if(connection_fetch_from_buf(crypted,128,conn) < 0) { + return -1; + } + + printf("Cell header crypttext: "); + for(x=0;x<8;x++) { + printf("%u ",crypted[x]); + } + printf("\n"); + /* decrypt */ + if(!EVP_DecryptUpdate(&conn->b_ctx,(unsigned char *)outbuf,&outlen,crypted,8)) { + log(LOG_ERR,"connection_process_cell_from_inbuf(): Decryption failed, dropping."); + return connection_process_inbuf(conn); /* process the remainder of the buffer */ + } + log(LOG_DEBUG,"connection_process_cell_from_inbuf(): Cell decrypted (%d bytes).",outlen); + printf("Cell header plaintext: "); + for(x=0;x<8;x++) { + printf("%u ",outbuf[x]); + } + printf("\n"); + + /* copy the rest of the cell */ + memcpy((char *)outbuf+8, (char *)crypted+8, sizeof(cell_t)-8); + cellp = (cell_t *)outbuf; + log(LOG_DEBUG,"connection_process_cell_from_inbuf(): Decrypted cell is of type %u (ACI %u).",cellp->command,cellp->aci); + command_process_cell(cellp, conn); + + return connection_process_inbuf(conn); /* process the remainder of the buffer */ +} + diff --git a/src/or/connection_app.c b/src/or/connection_app.c new file mode 100644 index 000000000..94276fd59 --- /dev/null +++ b/src/or/connection_app.c @@ -0,0 +1,212 @@ + +#include "or.h" + +connection_t *connection_app_new(void) { + return connection_new(CONN_TYPE_APP); +} + +int connection_app_process_inbuf(connection_t *conn) { + + assert(conn && conn->type == CONN_TYPE_APP); + + if(conn->inbuf_reached_eof) { + /* eof reached, kill it. */ + log(LOG_DEBUG,"connection_app_process_inbuf(): conn reached eof. Closing."); + return -1; + } + + log(LOG_DEBUG,"connection_app_process_inbuf(): state %d.",conn->state); + + switch(conn->state) { + case APP_CONN_STATE_CONNECTING: + log(LOG_DEBUG,"connection_app_process_inbuf(): text from app server while in 'connecting' state. Leaving it on buffer."); + return 0; + case APP_CONN_STATE_OPEN: + return connection_app_package_inbuf(conn); + } + return 0; + +} + +int connection_app_package_inbuf(connection_t *conn) { + int amount_to_process; + cell_t cell; + circuit_t *circ; + + assert(conn && conn->type == CONN_TYPE_APP); + + amount_to_process = conn->inbuf_datalen; + + if(!amount_to_process) + return 0; + + if(amount_to_process > CELL_PAYLOAD_SIZE) { + cell.length = CELL_PAYLOAD_SIZE; + } else { + cell.length = amount_to_process; + } + + if(connection_fetch_from_buf(cell.payload,cell.length,conn) < 0) { + return -1; + } + + circ = circuit_get_by_conn(conn); + if(!circ) { + log(LOG_DEBUG,"connection_app_package_inbuf(): conn has no circuits!"); + return -1; + } + + log(LOG_DEBUG,"connection_app_package_inbuf(): Packaging %d bytes.",cell.length); + cell.aci = circ->p_aci; + cell.command = CELL_DATA; + if(circuit_deliver_data_cell(&cell, circ, circ->p_conn, 'e') < 0) { + log(LOG_DEBUG,"connection_app_package_inbuf(): circuit_deliver_data_cell (backward) failed. Closing."); + circuit_close(circ); + return 0; + } + if(amount_to_process > CELL_PAYLOAD_SIZE) + return(connection_app_package_inbuf(conn)); + return 0; +} + +int connection_app_finished_flushing(connection_t *conn) { + int e, len=sizeof(e); + + assert(conn && conn->type == CONN_TYPE_APP); + + switch(conn->state) { + case APP_CONN_STATE_CONNECTING: + if (getsockopt(conn->s, SOL_SOCKET, SO_ERROR, &e, &len) < 0) { /* not yet */ + if(errno != EINPROGRESS){ + /* yuck. kill it. */ + log(LOG_DEBUG,"connection_app_finished_flushing(): in-progress connect failed. Removing."); + return -1; + } else { + return 0; /* no change, see if next time is better */ + } + } + /* the connect has finished. */ + + log(LOG_DEBUG,"connection_app_finished_flushing() : Connection to %s:%u established.", + conn->address,ntohs(conn->port)); + + conn->state = APP_CONN_STATE_OPEN; + connection_watch_events(conn, POLLIN); + return 0; + case APP_CONN_STATE_OPEN: + /* FIXME down the road, we'll clear out circuits that are pending to close */ + connection_watch_events(conn, POLLIN); + return 0; + default: + log(LOG_DEBUG,"Bug: connection_app_finished_flushing() called in unexpected state."); + return 0; + } + + return 0; +} + +int connection_app_process_data_cell(cell_t *cell, connection_t *conn) { + struct hostent *rent; + struct sockaddr_in dest_addr; + int s; + + /* an outgoing data cell has arrived */ + + assert(conn && conn->type == CONN_TYPE_APP); + + switch(conn->state) { + case APP_CONN_STATE_CONNECTING_WAIT: + log(LOG_DEBUG,"connection_app_process_cell(): state is connecting_wait. cell length %d.", cell->length); + if(!conn->ss_received) { /* this cell contains the ss */ + if(cell->length != sizeof(ss_t)) { + log(LOG_DEBUG,"connection_app_process_cell(): Supposed to contain SS but wrong size. Closing."); + return -1; + } + memcpy(&conn->ss, cell->payload, cell->length); + if(conn->ss.addr_fmt != SS_ADDR_FMT_ASCII_HOST_PORT) { /* unrecognized address format */ + log(LOG_DEBUG,"connection_app_process_cell(): SS has unrecognized address format. Closing."); + return -1; + } + conn->ss_received = 1; + log(LOG_DEBUG,"connection_app_process_cell(): SS received."); + } else if (!conn->addr) { /* this cell contains the dest addr */ + if(!memchr(cell->payload,0,cell->length)) { + log(LOG_DEBUG,"connection_app_process_cell(): dest_addr cell has no \\0. Closing."); + return -1; + } + conn->address = strdup(cell->payload); + rent = gethostbyname(cell->payload); + if (!rent) { + log(LOG_ERR,"connection_app_process_cell(): Could not resolve dest addr %s.",cell->payload); + return -1; + } + memcpy(&conn->addr, rent->h_addr,rent->h_length); + log(LOG_DEBUG,"connection_app_process_cell(): addr %s resolves to %d.",cell->payload,conn->addr); + } else if (!conn->port) { /* this cell contains the dest port */ + if(!memchr(cell->payload,'\0',cell->length)) { + log(LOG_DEBUG,"connection_app_process_cell(): dest_port cell has no \\0. Closing."); + return -1; + } + conn->port = atoi(cell->payload); + if(!conn->port) { /* bad port */ + log(LOG_DEBUG,"connection_app_process_cell(): dest_port cell isn't a valid number. Closing."); + return -1; + } + /* all the necessary info is here. Start the connect() */ + s=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); + if (s < 0) + { + log(LOG_ERR,"connection_app_process_cell(): Error creating network socket."); + return -1; + } + fcntl(s, F_SETFL, O_NONBLOCK); /* set s to non-blocking */ + + memset((void *)&dest_addr,0,sizeof(dest_addr)); + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = conn->port; + memcpy((void *)&dest_addr.sin_addr, &conn->addr, sizeof(uint32_t)); + + log(LOG_DEBUG,"connection_app_process_cell(): Connecting to %s:%u.",conn->address,ntohs(conn->port)); + + if(connect(s,(struct sockaddr *)&dest_addr,sizeof(dest_addr)) < 0){ + if(errno != EINPROGRESS){ + /* yuck. kill it. */ + log(LOG_DEBUG,"connection_app_process_cell(): Connect failed."); + return -1; + } else { + /* it's in progress. set state appropriately and return. */ + conn->s = s; + connection_set_poll_socket(conn); + conn->state = APP_CONN_STATE_CONNECTING; + + /* i think only pollout is needed, but i'm curious if pollin ever gets caught -RD */ + log(LOG_DEBUG,"connection_app_process_cell(): connect in progress, socket %d.",s); + connection_watch_events(conn, POLLOUT | POLLIN); + return 0; + } + } + + /* it succeeded. we're connected. */ + log(LOG_DEBUG,"connection_app_process_cell(): Connection to %s:%u established.",conn->address,ntohs(conn->port)); + + conn->s = s; + connection_set_poll_socket(conn); + conn->state = APP_CONN_STATE_OPEN; + connection_watch_events(conn, POLLIN); + } else { /* i'm not sure what this would be */ + log(LOG_DEBUG,"connection_app_process_cell(): in connecting_wait, not sure why."); + } + return 0; + case APP_CONN_STATE_CONNECTING: + log(LOG_DEBUG,"connection_app_process_cell(): Data receiving while connecting. Queueing."); + /* FIXME kludge. shouldn't call write_to_buf directly. */ + return write_to_buf(cell->payload, cell->length, &conn->outbuf, &conn->outbuflen, &conn->outbuf_datalen); + case APP_CONN_STATE_OPEN: + return connection_write_to_buf(cell->payload, cell->length, conn); + } + + return 0; +} + + + diff --git a/src/or/connection_op.c b/src/or/connection_op.c new file mode 100644 index 000000000..03d36dbe6 --- /dev/null +++ b/src/or/connection_op.c @@ -0,0 +1,130 @@ + +#include "or.h" + +connection_t *connection_op_new(void) { + return connection_new(CONN_TYPE_OP); +} + +connection_t *connection_op_listener_new(void) { + return connection_new(CONN_TYPE_OP_LISTENER); +} + +int connection_op_process_inbuf(connection_t *conn) { + + assert(conn && conn->type == CONN_TYPE_OP); + + if(conn->inbuf_reached_eof) { + /* eof reached, kill it. */ + log(LOG_DEBUG,"connection_op_process_inbuf(): conn reached eof. Closing."); + return -1; + } + + log(LOG_DEBUG,"connection_op_process_inbuf(): state %d.",conn->state); + + switch(conn->state) { + case OP_CONN_STATE_AWAITING_KEYS: + return op_handshake_process_keys(conn); + case OP_CONN_STATE_OPEN: + return connection_process_cell_from_inbuf(conn); + default: + log(LOG_DEBUG,"connection_op_process_inbuf() called in state where I'm writing. Ignoring buf for now.") +; + } + + return 0; + +} + +int op_handshake_process_keys(connection_t *conn) { + int retval; + int x; + + /* key exchange message */ + unsigned char auth_cipher[128]; + unsigned char auth_plain[128]; + + assert(conn); + + log(LOG_DEBUG,"op_handshake_process_keys() entered."); + + if(conn->inbuf_datalen < 128) /* entire response available? */ + return 0; /* not yet */ + + if(connection_fetch_from_buf(auth_cipher,128,conn) < 0) { + return -1; + } + log(LOG_DEBUG,"op_handshake_process_keys() : Received auth."); + + /* decrypt response */ + retval = RSA_private_decrypt(128,auth_cipher,auth_plain,conn->prkey,RSA_PKCS1_PADDING); + if (retval == -1) + { + log(LOG_ERR,"Decrypting keys from new OP failed."); + log(LOG_DEBUG,"op_handshake_process_keys() : Reason : %s.", + ERR_reason_error_string(ERR_get_error())); + return -1; + } + + log(LOG_DEBUG,"Successfully decrypted keys from new OP."); + + conn->bandwidth = ntohl(*((uint32_t *)auth_plain)); + + memcpy(conn->b_session_key, auth_plain+4, 8); + memcpy(conn->f_session_key, auth_plain+12, 8); + printf("f_session_key: "); + for(x=0;x<8;x++) { + printf("%d ",conn->f_session_key[x]); + } + printf("\nb_session_key: "); + for(x=0;x<8;x++) { + printf("%d ",conn->b_session_key[x]); + } + printf("\n"); + + memset((void *)conn->f_session_iv, 0, 8); + memset((void *)conn->b_session_iv, 0, 8); + + EVP_CIPHER_CTX_init(&conn->f_ctx); + EVP_CIPHER_CTX_init(&conn->b_ctx); + EVP_EncryptInit(&conn->b_ctx, EVP_des_ofb(), conn->b_session_key, conn->b_session_iv); + EVP_DecryptInit(&conn->f_ctx, EVP_des_ofb(), conn->f_session_key, conn->f_session_iv); + +#if 0 + /* FIXME must choose conn->aci here? What does it mean for a connection to have an aci? */ + log(LOG_DEBUG,"new_entry_connection : Chosen ACI %u.",conn->aci); +#endif + + conn->state = OP_CONN_STATE_OPEN; + connection_watch_events(conn, POLLIN); + + return 0; +} + +int connection_op_finished_flushing(connection_t *conn) { + + assert(conn && conn->type == CONN_TYPE_OP); + + switch(conn->state) { + case OP_CONN_STATE_OPEN: + /* FIXME down the road, we'll clear out circuits that are pending to close */ + connection_watch_events(conn, POLLIN); + return 0; + default: + log(LOG_DEBUG,"Bug: connection_op_finished_flushing() called in unexpected state."); + return 0; + } + + return 0; + +} + +int connection_op_create_listener(RSA *prkey, struct sockaddr_in *local) { + log(LOG_DEBUG,"connection_create_op_listener starting"); + return connection_create_listener(prkey, local, CONN_TYPE_OP_LISTENER); +} + +int connection_op_handle_listener_read(connection_t *conn) { + log(LOG_NOTICE,"OP: Received a connection request. Waiting for keys."); + return connection_handle_listener_read(conn, CONN_TYPE_OP, OP_CONN_STATE_AWAITING_KEYS); +} + diff --git a/src/or/connection_or.c b/src/or/connection_or.c new file mode 100644 index 000000000..813cc8975 --- /dev/null +++ b/src/or/connection_or.c @@ -0,0 +1,580 @@ + +#include "or.h" + +/* + * + * these two functions are the main ways 'in' to connection_or + * + */ + +int connection_or_process_inbuf(connection_t *conn) { + + assert(conn && conn->type == CONN_TYPE_OR); + + if(conn->inbuf_reached_eof) { + /* eof reached, kill it. */ + log(LOG_DEBUG,"connection_or_process_inbuf(): conn reached eof. Closing."); + return -1; + } + + log(LOG_DEBUG,"connection_or_process_inbuf(): state %d.",conn->state); + + switch(conn->state) { + case OR_CONN_STATE_CLIENT_AUTH_WAIT: + return or_handshake_client_process_auth(conn); + case OR_CONN_STATE_SERVER_AUTH_WAIT: + return or_handshake_server_process_auth(conn); + case OR_CONN_STATE_SERVER_NONCE_WAIT: + return or_handshake_server_process_nonce(conn); + case OR_CONN_STATE_OPEN: + return connection_process_cell_from_inbuf(conn); + default: + log(LOG_DEBUG,"connection_or_process_inbuf() called in state where I'm writing. Ignoring buf for now."); + } + + return 0; + +} + +int connection_or_finished_flushing(connection_t *conn) { + int e, len=sizeof(e); + + assert(conn && conn->type == CONN_TYPE_OR); + + switch(conn->state) { + case OR_CONN_STATE_CLIENT_CONNECTING: + if (getsockopt(conn->s, SOL_SOCKET, SO_ERROR, &e, &len) < 0) { /* not yet */ + if(errno != EINPROGRESS){ + /* yuck. kill it. */ + log(LOG_DEBUG,"connection_or_finished_flushing(): in-progress connect failed. Removing."); + return -1; + } else { + return 0; /* no change, see if next time is better */ + } + } + /* the connect has finished. */ + + log(LOG_DEBUG,"connection_or_finished_flushing() : Connection to router %s:%u established.", + conn->address,ntohs(conn->port)); + + return or_handshake_client_send_auth(conn); + case OR_CONN_STATE_CLIENT_SENDING_AUTH: + log(LOG_DEBUG,"connection_or_finished_flushing(): client finished sending auth."); + conn->state = OR_CONN_STATE_CLIENT_AUTH_WAIT; + connection_watch_events(conn, POLLIN); + return 0; + case OR_CONN_STATE_CLIENT_SENDING_NONCE: + log(LOG_DEBUG,"connection_or_finished_flushing(): client finished sending nonce."); + conn_or_init_crypto(conn); + conn->state = OR_CONN_STATE_OPEN; + connection_watch_events(conn, POLLIN); + return 0; + case OR_CONN_STATE_SERVER_SENDING_AUTH: + log(LOG_DEBUG,"connection_or_finished_flushing(): server finished sending auth."); + conn->state = OR_CONN_STATE_SERVER_NONCE_WAIT; + connection_watch_events(conn, POLLIN); + return 0; + case OR_CONN_STATE_OPEN: + /* FIXME down the road, we'll clear out circuits that are pending to close */ + connection_watch_events(conn, POLLIN); + return 0; + default: + log(LOG_DEBUG,"Bug: connection_or_finished_flushing() called in unexpected state."); + return 0; + } + + return 0; + +} + +/*********************/ + +connection_t *connection_or_new(void) { + return connection_new(CONN_TYPE_OR); +} + +connection_t *connection_or_new_listener(void) { + return connection_new(CONN_TYPE_OR_LISTENER); +} + +void conn_or_init_crypto(connection_t *conn) { + int x; + + assert(conn); + printf("f_session_key: "); + for(x=0;x<8;x++) { + printf("%d ",conn->f_session_key[x]); + } + printf("\nb_session_key: "); + for(x=0;x<8;x++) { + printf("%d ",conn->b_session_key[x]); + } + printf("\n"); + + memset(conn->f_session_iv,0,8); + memset(conn->b_session_iv,0,8); + EVP_CIPHER_CTX_init(&conn->f_ctx); + EVP_CIPHER_CTX_init(&conn->b_ctx); + EVP_EncryptInit(&conn->f_ctx, EVP_des_ofb(), conn->f_session_key, conn->f_session_iv); + EVP_DecryptInit(&conn->b_ctx, EVP_des_ofb(), conn->b_session_key, conn->b_session_iv); + /* always encrypt with f, always decrypt with b */ + +} + +/* + * + * auth handshake, as performed by OR *initiating* the connection + * + */ + +int connect_to_router(routerinfo_t *router, RSA *prkey, struct sockaddr_in *local) { + connection_t *conn; + struct sockaddr_in router_addr; + int s; + + if ((!router) || (!prkey) || (!local)) + return -1; + + if(router->addr == local->sin_addr.s_addr && router->port == local->sin_port) { + /* this is me! don't connect to me. */ + return 0; + } + + conn = connection_or_new(); + + /* set up conn so it's got all the data we need to remember */ + conn->addr = router->addr, conn->port = router->port; + conn->prkey = prkey; + conn->min = router->min, conn->max = router->max; + conn->pkey = router->pkey; + conn->address = strdup(router->address); + memcpy(&conn->local,local,sizeof(struct sockaddr_in)); + + s=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP); + if (s < 0) + { + log(LOG_ERR,"Error creating network socket."); + connection_free(conn); + return -1; + } + fcntl(s, F_SETFL, O_NONBLOCK); /* set s to non-blocking */ + + memset((void *)&router_addr,0,sizeof(router_addr)); + router_addr.sin_family = AF_INET; + router_addr.sin_port = router->port; + memcpy((void *)&router_addr.sin_addr, &router->addr, sizeof(uint32_t)); + + log(LOG_DEBUG,"connect_to_router() : Trying to connect to %s:%u.",inet_ntoa(*(struct in_addr *)&router->addr),ntohs(router->port)); + + if(connect(s,(struct sockaddr *)&router_addr,sizeof(router_addr)) < 0){ + if(errno != EINPROGRESS){ + /* yuck. kill it. */ + connection_free(conn); + return -1; + } else { + /* it's in progress. set state appropriately and return. */ + conn->s = s; + + conn->state = OR_CONN_STATE_CLIENT_CONNECTING; + + if(connection_add(conn) < 0) { /* no space, forget it */ + connection_free(conn); + return -1; + } + /* i think only pollout is needed, but i'm curious if pollin ever gets caught -RD */ + log(LOG_DEBUG,"connect_to_router() : connect in progress."); + connection_watch_events(conn, POLLOUT | POLLIN); + return 0; + } + } + + /* it succeeded. we're connected. */ + conn->s = s; + + if(connection_add(conn) < 0) { /* no space, forget it */ + connection_free(conn); + return -1; + } + + log(LOG_DEBUG,"connect_to_router() : Connection to router %s:%u established.",router->address,ntohs(router->port)); + + /* move to the next step in the handshake */ + if(or_handshake_client_send_auth(conn) < 0) { + connection_remove(conn); + connection_free(conn); + return -1; + } + return 0; +} + +int or_handshake_client_send_auth(connection_t *conn) { + int retval; + char keys[16]; + char buf[44]; + char cipher[128]; + + if (!conn) + return -1; + + /* generate random keys */ + retval = RAND_bytes(keys,16); + if (retval != 1) /* error */ + { + log(LOG_ERR,"Cannot generate a secure DES key."); + return -1; + } + log(LOG_DEBUG,"or_handshake_client_send_auth() : Generated DES keys."); + + /* save keys */ + memcpy(conn->f_session_key,keys,8); + memcpy(conn->b_session_key,keys+8,8); + + /* generate first message */ + memcpy(buf,&conn->local.sin_addr,4); /* local address */ + memcpy(buf+4,(void *)&conn->local.sin_port,2); /* local port */ + memcpy(buf+6, (void *)&conn->addr, 4); /* remote address */ + memcpy(buf+10, (void *)&conn->port, 2); /* remote port */ + memcpy(buf+12,keys,16); /* keys */ + *((uint32_t *)(buf+28)) = htonl(conn->min); /* min link utilisation */ + *((uint32_t *)(buf+32)) = htonl(conn->max); /* maximum link utilisation */ + log(LOG_DEBUG,"or_handshake_client_send_auth() : Generated first authentication message."); + + /* encrypt message */ + retval = RSA_public_encrypt(36,buf,cipher,conn->pkey,RSA_PKCS1_PADDING); + if (retval == -1) /* error */ + { + log(LOG_ERR,"Public-key encryption failed during authentication to %s:%u.",conn->address,ntohs(conn->port)); + log(LOG_DEBUG,"or_handshake_client_send_auth() : Reason : %s.",ERR_reason_error_string(ERR_get_error())); + return -1; + } + log(LOG_DEBUG,"or_handshake_client_send_auth() : Encrypted authentication message."); + + /* send message */ + + if(connection_write_to_buf(cipher, 128, conn) < 0) { + log(LOG_DEBUG,"or_handshake_client_send_auth(): my outbuf is full. Oops."); + return -1; + } + retval = connection_flush_buf(conn); + if(retval < 0) { + log(LOG_DEBUG,"or_handshake_client_send_auth(): bad socket while flushing."); + return -1; + } + if(retval > 0) { + /* still stuff on the buffer. */ + conn->state = OR_CONN_STATE_CLIENT_SENDING_AUTH; + connection_watch_events(conn, POLLOUT | POLLIN); + return 0; + } + + /* it finished sending */ + log(LOG_DEBUG,"or_handshake_client_send_auth(): Finished sending authentication message."); + conn->state = OR_CONN_STATE_CLIENT_AUTH_WAIT; + connection_watch_events(conn, POLLIN); + return 0; + +} + +int or_handshake_client_process_auth(connection_t *conn) { + char buf[128]; /* only 44 of this is expected to be used */ + char cipher[128]; + uint32_t min,max; + int retval; + + assert(conn); + + if(conn->inbuf_datalen < 128) /* entire response available? */ + return 0; /* not yet */ + + if(connection_fetch_from_buf(cipher,128,conn) < 0) { + return -1; + } + log(LOG_DEBUG,"or_handshake_client_process_auth() : Received auth."); + + /* decrypt response */ + retval = RSA_private_decrypt(128,cipher,buf,conn->prkey,RSA_PKCS1_PADDING); + if (retval == -1) + { + log(LOG_ERR,"Public-key decryption failed during authentication to %s:%u.", + conn->address,ntohs(conn->port)); + log(LOG_DEBUG,"or_handshake_client_process_auth() : Reason : %s.", + ERR_reason_error_string(ERR_get_error())); + return -1; + } + else if (retval != 44) + { + log(LOG_ERR,"Received an incorrect response from router %s:%u during authentication.", + conn->address,ntohs(conn->port)); + return -1; + } + log(LOG_DEBUG,"or_handshake_client_process_auth() : Decrypted response."); + /* check validity */ + if ( (memcmp(&conn->local.sin_addr, buf, 4)) || /* local address */ + (memcmp(&conn->local.sin_port, buf+4, 2)) || /* local port */ + (memcmp(&conn->addr, buf+6, 4)) || /* remote address */ + (memcmp(&conn->port, buf+10, 2)) || /* remote port */ + (memcmp(&conn->f_session_key, buf+12, 8)) || /* keys */ + (memcmp(&conn->b_session_key, buf+20, 8))) + { /* incorrect response */ + log(LOG_ERR,"Router %s:%u failed to authenticate. Either the key I have is obsolete or they're doing something they're not supposed to.",conn->address,ntohs(conn->port)); + return -1; + } + + log(LOG_DEBUG,"or_handshake_client_process_auth() : Response valid."); + + /* update link info */ + min = *(uint32_t *)(buf+28); + max = *(uint32_t *)(buf+32); + min = ntohl(min); + max = ntohl(max); + + if (conn->min > min) + conn->min = min; + if (conn->max > max) + conn->max = max; + + /* reply is just local addr/port, remote addr/port, nonce */ + memcpy(buf+12, buf+36, 8); + + /* encrypt reply */ + retval = RSA_public_encrypt(20,buf,cipher,conn->pkey,RSA_PKCS1_PADDING); + if (retval == -1) /* error */ + { + log(LOG_ERR,"Public-key encryption failed during authentication to %s:%u.",conn->address,ntohs(conn->port)); + log(LOG_DEBUG,"or_handshake_client_process_auth() : Reason : %s.",ERR_reason_error_string(ERR_get_error())); + return -1; + } + + /* send the message */ + + if(connection_write_to_buf(cipher, 128, conn) < 0) { + log(LOG_DEBUG,"or_handshake_client_process_auth(): my outbuf is full. Oops."); + return -1; + } + retval = connection_flush_buf(conn); + if(retval < 0) { + log(LOG_DEBUG,"or_handshake_client_process_auth(): bad socket while flushing."); + return -1; + } + if(retval > 0) { + /* still stuff on the buffer. */ + conn->state = OR_CONN_STATE_CLIENT_SENDING_NONCE; + connection_watch_events(conn, POLLOUT | POLLIN); +/* return(connection_process_inbuf(conn)); process the rest of the inbuf */ + return 0; + } + + /* it finished sending */ + log(LOG_DEBUG,"or_handshake_client_process_auth(): Finished sending nonce."); + conn_or_init_crypto(conn); + conn->state = OR_CONN_STATE_OPEN; + connection_watch_events(conn, POLLIN); + return connection_process_inbuf(conn); /* process the rest of the inbuf */ + +} + +/* + * + * auth handshake, as performed by OR *receiving* the connection + * + */ + +int or_handshake_server_process_auth(connection_t *conn) { + int retval; + + char buf[128]; /* only 42 of this is expected to be used */ + char cipher[128]; + + uint32_t addr; + uint16_t port; + + uint32_t min,max; + routerinfo_t *router; + + assert(conn); + + log(LOG_DEBUG,"or_handshake_server_process_auth() entered."); + + if(conn->inbuf_datalen < 128) /* entire response available? */ + return 0; /* not yet */ + + if(connection_fetch_from_buf(cipher,128,conn) < 0) { + return -1; + } + log(LOG_DEBUG,"or_handshake_server_process_auth() : Received auth."); + + /* decrypt response */ + retval = RSA_private_decrypt(128,cipher,buf,conn->prkey,RSA_PKCS1_PADDING); + if (retval == -1) + { + log(LOG_ERR,"Public-key decryption failed during authentication to %s:%u.", + conn->address,ntohs(conn->port)); + log(LOG_DEBUG,"or_handshake_server_process_auth() : Reason : %s.", + ERR_reason_error_string(ERR_get_error())); + return -1; + } + else if (retval != 36) + { + log(LOG_ERR,"Received an incorrect authentication request."); + return -1; + } + log(LOG_DEBUG,"or_handshake_server_process_auth() : Decrypted authentication message."); + + /* identify the router */ + memcpy(&addr,buf,4); /* save the IP address */ + memcpy(&port,buf+4,2); /* save the port */ + + router = router_get_by_addr_port(addr,port); + if (!router) + { + log(LOG_DEBUG,"or_handshake_server_process_auth() : Received a connection from an unknown router. Will drop."); + return -1; + } + log(LOG_DEBUG,"or_handshake_server_process_auth() : Router identified as %s:%u.", + router->address,ntohs(router->port)); + + if(connection_get_by_addr_port(addr,port)) { + log(LOG_DEBUG,"or_handshake_server_process_auth(): That router is already connected. Dropping."); + return -1; + } + + /* save keys */ + memcpy(conn->b_session_key,buf+12,8); + memcpy(conn->f_session_key,buf+20,8); + + /* update link info */ + min = *(uint32_t *)(buf+28); + max = *(uint32_t *)(buf+32); + min = ntohl(min); + max = ntohl(max); + + conn->min = router->min; + conn->max = router->max; + + if (conn->min > min) + conn->min = min; + if (conn->max > max) + conn->max = max; + + /* copy all relevant info to conn */ + conn->addr = router->addr, conn->port = router->port; + conn->pkey = router->pkey; + conn->address = strdup(router->address); + + /* generate a nonce */ + retval = RAND_pseudo_bytes(conn->nonce,8); + if (retval == -1) /* error */ + { + log(LOG_ERR,"Cannot generate a nonce."); + return -1; + } + log(LOG_DEBUG,"or_handshake_server_process_auth() : Nonce generated."); + + /* generate message */ + memcpy(buf+36,conn->nonce,8); /* append the nonce to the end of the message */ + *(uint32_t *)(buf+28) = htonl(conn->min); /* send min link utilisation */ + *(uint32_t *)(buf+32) = htonl(conn->max); /* send max link utilisation */ + + /* encrypt message */ + retval = RSA_public_encrypt(44,buf,cipher,conn->pkey,RSA_PKCS1_PADDING); + if (retval == -1) /* error */ + { + log(LOG_ERR,"Public-key encryption failed during authentication to %s:%u.",conn->address,ntohs(conn->port)); + log(LOG_DEBUG,"or_handshake_server_process_auth() : Reason : %s.",ERR_reason_error_string(ERR_get_error())); + return -1; + } + log(LOG_DEBUG,"or_handshake_server_process_auth() : Reply encrypted."); + + /* send message */ + + if(connection_write_to_buf(cipher, 128, conn) < 0) { + log(LOG_DEBUG,"or_handshake_server_process_auth(): my outbuf is full. Oops."); + return -1; + } + retval = connection_flush_buf(conn); + if(retval < 0) { + log(LOG_DEBUG,"or_handshake_server_process_auth(): bad socket while flushing."); + return -1; + } + if(retval > 0) { + /* still stuff on the buffer. */ + conn->state = OR_CONN_STATE_SERVER_SENDING_AUTH; + connection_watch_events(conn, POLLOUT | POLLIN); + return 0; + } + + /* it finished sending */ + log(LOG_DEBUG,"or_handshake_server_process_auth(): Finished sending auth."); + conn->state = OR_CONN_STATE_SERVER_NONCE_WAIT; + connection_watch_events(conn, POLLIN); + return 0; + +} + +int or_handshake_server_process_nonce(connection_t *conn) { + + char buf[128]; + char cipher[128]; + int retval; + + assert(conn); + + if(conn->inbuf_datalen < 128) /* entire response available? */ + return 0; /* not yet */ + + if(connection_fetch_from_buf(cipher,128,conn) < 0) { + return -1; + } + log(LOG_DEBUG,"or_handshake_server_process_nonce() : Received auth."); + + /* decrypt response */ + retval = RSA_private_decrypt(128,cipher,buf,conn->prkey,RSA_PKCS1_PADDING); + if (retval == -1) + { + log(LOG_ERR,"Public-key decryption failed during authentication to %s:%u.", + conn->address,ntohs(conn->port)); + log(LOG_DEBUG,"or_handshake_server_process_nonce() : Reason : %s.", + ERR_reason_error_string(ERR_get_error())); + return -1; + } + else if (retval != 20) + { + log(LOG_ERR,"Received an incorrect response from router %s:%u during authentication.", + conn->address,ntohs(conn->port)); + return -1; + } + log(LOG_DEBUG,"or_handshake_server_process_nonce() : Response decrypted."); + + /* check validity */ + if ((memcmp(&conn->addr,buf, 4)) || /* remote address */ + (memcmp(&conn->port,buf+4,2)) || /* remote port */ + (memcmp(&conn->local.sin_addr,buf+6,4)) || /* local address */ + (memcmp(&conn->local.sin_port,buf+10,2)) || /* local port */ + (memcmp(conn->nonce,buf+12,8))) /* nonce */ + { + log(LOG_ERR,"Router %s:%u failed to authenticate. Either the key I have is obsolete or they're doing something they're not supposed to.",conn->address,ntohs(conn->port)); + return -1; + } + log(LOG_DEBUG,"or_handshake_server_process_nonce() : Response valid. Authentication complete."); + + conn_or_init_crypto(conn); + conn->state = OR_CONN_STATE_OPEN; + connection_watch_events(conn, POLLIN); + return connection_process_inbuf(conn); /* process the rest of the inbuf */ + +} + + +/* ********************************** */ + + +int connection_or_create_listener(RSA *prkey, struct sockaddr_in *local) { + log(LOG_DEBUG,"connection_create_or_listener starting"); + return connection_create_listener(prkey, local, CONN_TYPE_OR_LISTENER); +} + +int connection_or_handle_listener_read(connection_t *conn) { + log(LOG_NOTICE,"OR: Received a connection request from a router. Attempting to authenticate."); + return connection_handle_listener_read(conn, CONN_TYPE_OR, OR_CONN_STATE_SERVER_AUTH_WAIT); +} + diff --git a/src/or/main.c b/src/or/main.c new file mode 100644 index 000000000..85f91ab30 --- /dev/null +++ b/src/or/main.c @@ -0,0 +1,406 @@ + +#include "or.h" + +/********* START VARIABLES **********/ + +/* valid command-line options */ +static char *args = "hf:e:n:l:"; + +int loglevel = LOG_DEBUG; + +/* valid config file options */ +config_opt_t options[] = +{ + {"RouterFile", CONFIG_TYPE_STRING, {0}, 0}, + {"PrivateKeyFile", CONFIG_TYPE_STRING, {0}, 0}, + {"EntryPort", CONFIG_TYPE_INT, {0}, 0}, + {"NetworkPort", CONFIG_TYPE_INT, {0}, 0}, + {"MaxConn", CONFIG_TYPE_INT, {0}, 0}, + {"MaxConnTimeout", CONFIG_TYPE_INT, {0}, 0}, + {"TrafficShaping", CONFIG_TYPE_INT, {0}, 0}, + {0} +}; +enum opts { + RouterFile=0, PrivateKeyFile, EntryPort, NetworkPort, MaxConn, MaxConnTimeout, TrafficShaping +}; + +connection_t *connection_array[MAXCONNECTIONS] = + { NULL }; + +struct pollfd poll_array[MAXCONNECTIONS] = + { [0 ... MAXCONNECTIONS-1] = { -1, 0, 0 } }; + +int nfds=0; /* number of connections currently active */ + +/* default logging threshold */ +extern int loglevel; + +/* private key */ +RSA *prkey = NULL; + +/* router array */ +routerinfo_t **router_array = NULL; +int rarray_len = 0; + +/********* END VARIABLES ************/ + +int connection_add(connection_t *conn) { + + if(nfds >= MAXCONNECTIONS-2) { /* 2, for some breathing room. should count the fenceposts. */ + /* FIXME should use the 'max connections' option */ + log(LOG_DEBUG,"connection_add(): failing because nfds is too high."); + return -1; + } + + conn->poll_index = nfds; + connection_set_poll_socket(conn); + connection_array[nfds] = conn; + + /* zero these out here, because otherwise we'll inherit values from the previously freed one */ + poll_array[nfds].events = 0; + poll_array[nfds].revents = 0; + + nfds++; + + log(LOG_DEBUG,"connection_add(): new conn type %d, socket %d, nfds %d.",conn->type, conn->s, nfds); + + return 0; + +} + +void connection_set_poll_socket(connection_t *conn) { + poll_array[conn->poll_index].fd = conn->s; +} + +int connection_remove(connection_t *conn) { + + int current_index; + + assert(conn); + assert(nfds>0); + + circuit_about_to_close_connection(conn); /* flush and send destroys for all circuits on this conn */ + + current_index = conn->poll_index; + if(current_index == nfds-1) { /* this is the end */ +// connection_free(conn); + nfds--; + log(LOG_DEBUG,"connection_remove(): nfds now %d.",nfds); + return 0; + } + + /* we replace this one with the one at the end, then free it */ + nfds--; + poll_array[current_index].fd = poll_array[nfds].fd; + poll_array[current_index].events = poll_array[nfds].events; + poll_array[current_index].revents = poll_array[nfds].revents; + connection_array[current_index] = connection_array[nfds]; + connection_array[current_index]->poll_index = current_index; + + log(LOG_DEBUG,"connection_remove(): nfds now %d.",nfds); + + return 0; +} + +connection_t *connection_get_by_addr_port(uint32_t addr, uint16_t port) { + + int i; + connection_t *conn; + + for(i=0;i<nfds;i++) { + conn = connection_array[i]; + assert(conn); + if(conn->addr == addr && conn->port == port) + return conn; + } + + return NULL; + +} + +connection_t *connection_get_by_type(int type) { + + int i; + connection_t *conn; + + for(i=0;i<nfds;i++) { + conn = connection_array[i]; + if(conn->type == type) + return conn; + } + + return NULL; + +} + +routerinfo_t *router_get_by_addr_port(uint32_t addr, uint16_t port) { + int i; + routerinfo_t *router; + + if (!router_array) + return NULL; + + for(i=0;i<rarray_len;i++) + { + router = router_array[i]; + if ((router->addr == addr) && (router->port == port)) + return router; + } + + return NULL; + +} + +void connection_watch_events(connection_t *conn, short events) { + + assert(conn && conn->poll_index < nfds); + + poll_array[conn->poll_index].events = events; +} + +void check_conn_read(int i) { + + int retval; + connection_t *conn; + + if(poll_array[i].revents & POLLIN) { /* something to read */ + + conn = connection_array[i]; + assert(conn); + log(LOG_DEBUG,"check_conn_read(): socket %d has something to read.",conn->s); + + if (conn->type == CONN_TYPE_OP_LISTENER) { + retval = connection_op_handle_listener_read(conn); + } else if (conn->type == CONN_TYPE_OR_LISTENER) { + retval = connection_or_handle_listener_read(conn); + } else { + /* else it's an OP, OR, or app */ + retval = connection_read_to_buf(conn); + if (retval >= 0) { /* all still well */ + retval = connection_process_inbuf(conn); + log(LOG_DEBUG,"check_conn_read(): connection_process_inbuf returned %d.",retval); + } + } + + if(retval < 0) { /* this connection is broken. remove it */ + log(LOG_DEBUG,"check_conn_read(): Connection broken, removing."); + connection_remove(conn); + connection_free(conn); + if(i<nfds) { /* we just replaced the one at i with a new one. + process it too. */ + check_conn_read(i); + } + } + } +} + +void check_conn_write(int i) { + + int retval; + connection_t *conn; + + if(poll_array[i].revents & POLLOUT) { /* something to write */ + + conn = connection_array[i]; + log(LOG_DEBUG,"check_conn_write(): socket %d wants to write.",conn->s); + + if(conn->type == CONN_TYPE_OP_LISTENER || + conn->type == CONN_TYPE_OR_LISTENER) { + log(LOG_DEBUG,"check_conn_write(): Got a listener socket. Can't happen!"); + retval = -1; + } else { + /* else it's an OP, OR, or app */ + retval = connection_flush_buf(conn); /* conns in CONNECTING state will fall through... */ + if(retval == 0) { /* it's done flushing */ + retval = connection_finished_flushing(conn); /* ...and get handled here. */ + } + } + + if(retval < 0) { /* this connection is broken. remove it. */ + log(LOG_DEBUG,"check_conn_write(): Connection broken, removing."); + connection_remove(conn); + connection_free(conn); + if(i<nfds) { /* we just replaced the one at i with a new one. + process it too. */ + check_conn_read(i); + } + } + } +} + +void check_conn_marked(int i) { + connection_t *conn; + + conn = connection_array[i]; + assert(conn); + if(conn->marked_for_close) { + log(LOG_DEBUG,"check_conn_marked(): Cleaning up connection."); + connection_flush_buf(conn); /* flush it first */ + connection_remove(conn); + connection_free(conn); + if(i<nfds) { /* we just replaced the one at i with a new one. + process it too. */ + check_conn_read(i); + } + } +} + +#if 0 +void check_conn_hup(int i) { + connection_t *conn; + + if(poll_array[i].revents & POLLHUP) { /* they've hung up */ + conn = connection_array[i]; + log(LOG_DEBUG,"check_conn_hup(): socket %d has hung up.",conn->s); + connection_remove(conn); + connection_free(conn); + + if(i<nfds) { /* we just replaced the one at i with a new one. + process it too. */ + check_conn_hup(i); + } + } +} +#endif + +int do_main_loop(void) { + + int i; + + /* load the routers file */ + router_array = getrouters(options[RouterFile].r.str,&rarray_len); + if (!router_array) + { + log(LOG_ERR,"Error loading router list."); + exit(1); + } + + /* load the private key */ + ERR_load_crypto_strings(); + prkey = load_prkey(options[PrivateKeyFile].r.str); + if (!prkey) + { + log(LOG_ERR,"Error loading private key."); + exit(1); + } + log(LOG_DEBUG,"core : Loaded private key of size %u bytes.",RSA_size(prkey)); + ERR_free_strings(); + + /* try to connect to all the other ORs, and start the listeners */ + retry_all_connections(router_array, rarray_len, prkey, + options[NetworkPort].r.i,options[EntryPort].r.i); + + for(;;) { + poll(poll_array, nfds, -1); /* poll until we have an event */ + + /* do all the reads first, so we can detect closed sockets */ + for(i=0;i<nfds;i++) + check_conn_read(i); + + /* then do the writes */ + for(i=0;i<nfds;i++) + check_conn_write(i); + + /* any of the conns need to be closed now? */ + for(i=0;i<nfds;i++) + check_conn_marked(i); + +#if 0 /* no, check_conn_read() takes care of hups. */ + /* remove the ones that have disconnected */ + for(i=0;i<nfds;i++) + check_conn_hup(i); +#endif + } + +} + +void catch () +{ + errno = 0; /* netcat does this. it looks fun. */ + + log(LOG_DEBUG,"Catching ^c, exiting cleanly."); + + exit(0); + +} + +int main(int argc, char *argv[]) +{ + int retval = 0; + + char *conf_filename = NULL; /* configuration file */ + signal (SIGINT, catch); /* to catch ^c so we can exit cleanly */ + + /* get command-line arguments */ + retval = getargs(argc,argv,args,&conf_filename,&loglevel); + if (retval == -1) + { + log(LOG_ERR,"Error processing command-line arguments."); + exit(1); + } + + /* load config file */ + retval = getconfig(conf_filename,options); + if (retval == -1) + { + log(LOG_ERR,"Error loading configuration file."); + exit(1); + } + else if (options[RouterFile].err != 1) + { + log(LOG_ERR,"RouterFile option required, but not found."); + exit(1); + } + else if (options[PrivateKeyFile].err != 1) + { + log(LOG_ERR,"PrivateKeyFile option required but not found."); + exit(1); + } + else if (options[EntryPort].err != 1) + { + log(LOG_ERR,"EntryPort option required but not found."); + exit(1); + } + else if (options[NetworkPort].err != 1) + { + log(LOG_ERR,"NetworkPort option required but not found."); + exit(1); + } + else if (options[MaxConn].err != 1) + { + log(LOG_ERR,"MaxConn option required but not found."); + exit(1); + } +#if 0 + else if (options[MaxConnTimeout].err != 1) + { + conn_tout.tv_sec = OR_DEFAULT_CONN_TIMEOUT; + } + else + { + if (!options[MaxConnTimeout].r.i) + conn_toutp = NULL; + else + conn_tout.tv_sec = options[MaxConnTimeout].r.i; + } + conn_tout.tv_usec = 0; + + if (!options[TrafficShaping].err) + { + options[TrafficShaping].r.i = DEFAULT_POLICY; + } + else if ((options[TrafficShaping].r.i < 0) || (options[TrafficShaping].r.i > 1)) + { + log(LOG_ERR,"Invalid value for the TrafficShaping option."); + exit(1); + } +#endif + + ERR_load_crypto_strings(); + retval = do_main_loop(); + ERR_free_strings(); + + return retval; + +} + diff --git a/src/or/onion.c b/src/or/onion.c new file mode 100644 index 000000000..d9c8a9c5d --- /dev/null +++ b/src/or/onion.c @@ -0,0 +1,62 @@ + +#include "or.h" + +/********* START VARIABLES **********/ + +tracked_onion_t *tracked_onions = NULL; /* linked list of tracked onions */ +tracked_onion_t *last_tracked_onion = NULL; + +/********* END VARIABLES ************/ + + +int decide_aci_type(uint32_t local_addr, uint16_t local_port, + uint32_t remote_addr, uint16_t remote_port) { + + if(local_addr > remote_addr) + return ACI_TYPE_HIGHER; + if(local_addr < remote_addr) + return ACI_TYPE_LOWER; + if(local_port > remote_port) + return ACI_TYPE_HIGHER; + /* else */ + return ACI_TYPE_LOWER; +} + +int process_onion(circuit_t *circ, connection_t *conn) { + aci_t aci_type; + + if(!decrypt_onion((onion_layer_t *)circ->onion,circ->onionlen,conn->prkey)) { + log(LOG_DEBUG,"command_process_create_cell(): decrypt_onion() failed, closing circuit."); + return -1; + } + log(LOG_DEBUG,"command_process_create_cell(): Onion decrypted."); + + /* check freshness */ + if (((onion_layer_t *)circ->onion)->expire < time(NULL)) /* expired onion */ + { + log(LOG_NOTICE,"I have just received an expired onion. This could be a replay attack."); + return -1; + } + + aci_type = decide_aci_type(conn->local.sin_addr.s_addr, conn->local.sin_port, + ((onion_layer_t *)circ->onion)->addr,((onion_layer_t *)circ->onion)->port); + + if(circuit_init(circ, aci_type) < 0) { + log(LOG_ERR,"process_onion(): init_circuit() failed."); + return -1; + } + + /* check for replay */ + if(id_tracked_onion(circ->onion, circ->onionlen, tracked_onions)) { + log(LOG_NOTICE,"process_onion(): I have just received a replayed onion. This could be a replay attack."); + return -1; + } + + /* track the new onion */ + if(!new_tracked_onion(circ->onion,circ->onionlen, &tracked_onions, &last_tracked_onion)) { + log(LOG_DEBUG,"process_onion(): Onion tracking failed. Will ignore."); + } + + return 0; +} + diff --git a/src/or/or.h b/src/or/or.h new file mode 100644 index 000000000..1d73e123a --- /dev/null +++ b/src/or/or.h @@ -0,0 +1,411 @@ +/* Copyright (c) 2002 Roger Dingledine. See LICENSE for licensing information */ +/* $Id$ */ + +#ifndef __OR_H +#define __OR_H + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <netdb.h> +#include <ctype.h> +#include <sys/poll.h> +#include <sys/types.h> +#include <sys/fcntl.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <assert.h> + +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/pem.h> +#include <openssl/evp.h> +#include <openssl/rand.h> + +#include "../common/cell.h" +#include "../common/config.h" +#include "../common/key.h" +#include "../common/log.h" +#include "../common/onion.h" +#include "../common/ss.h" + +#define MAXCONNECTIONS 200 /* upper bound on max connections. + can be overridden by config file */ + +#define MAX_BUF_SIZE (64*1024) + +#define ACI_TYPE_LOWER 0 +#define ACI_TYPE_HIGHER 1 + +#define CONN_TYPE_OP_LISTENER 1 +#define CONN_TYPE_OP 2 +#define CONN_TYPE_OR_LISTENER 3 +#define CONN_TYPE_OR 4 +#define CONN_TYPE_APP 5 + +#define LISTENER_STATE_READY 0 + +#define OP_CONN_STATE_AWAITING_KEYS 0 +#define OP_CONN_STATE_OPEN 1 +#if 0 +#define OP_CONN_STATE_CLOSE 2 /* flushing the buffer, then will close */ +#define OP_CONN_STATE_CLOSE_WAIT 3 /* have sent a destroy, awaiting a confirmation */ +#endif + +#define OR_CONN_STATE_CLIENT_CONNECTING 0 +#define OR_CONN_STATE_CLIENT_SENDING_AUTH 1 /* sending address and info */ +#define OR_CONN_STATE_CLIENT_AUTH_WAIT 2 /* have sent address and info, waiting */ +#define OR_CONN_STATE_CLIENT_SENDING_NONCE 3 /* sending nonce, last piece of handshake */ +#define OR_CONN_STATE_SERVER_AUTH_WAIT 4 /* waiting for address and info */ +#define OR_CONN_STATE_SERVER_SENDING_AUTH 5 /* writing auth and nonce */ +#define OR_CONN_STATE_SERVER_NONCE_WAIT 6 /* waiting for confirmation of nonce */ +#define OR_CONN_STATE_OPEN 7 /* ready to send/receive cells. */ + +#define APP_CONN_STATE_CONNECTING_WAIT 0 /* waiting for standard structure or dest info */ +#define APP_CONN_STATE_CONNECTING 1 +#define APP_CONN_STATE_OPEN 2 +#if 0 +#define APP_CONN_STATE_CLOSE 3 /* flushing the buffer, then will close */ +#define APP_CONN_STATE_CLOSE_WAIT 4 /* have sent a destroy, awaiting a confirmation */ +#endif + +#define CIRCUIT_STATE_OPEN_WAIT 0 /* receiving/processing the onion */ +#define CIRCUIT_STATE_OPEN 1 /* onion processed, ready to send data along the connection */ +#define CIRCUIT_STATE_CLOSE_WAIT1 2 /* sent two "destroy" signals, waiting for acks */ +#define CIRCUIT_STATE_CLOSE_WAIT2 3 /* received one ack, waiting for one more + (or if just one was sent, waiting for that one */ +//#define CIRCUIT_STATE_CLOSE 4 /* both acks received, connection is dead */ /* NOT USED */ + +typedef uint16_t aci_t; + +typedef struct +{ + +/* Used by all types: */ + + unsigned char type; + int state; + int s; /* our socket */ + int poll_index; + int marked_for_close; + + char *inbuf; + size_t inbuflen; + size_t inbuf_datalen; + int inbuf_reached_eof; + + char *outbuf; + size_t outbuflen; + size_t outbuf_datalen; + +/* used by OP and App: */ + + uint16_t aci; /* anonymous connection identifier */ + +/* used by OR and OP: */ + + uint32_t bandwidth; /* connection bandwidth */ + int window_sent; /* how many cells can i still send? */ + int window_received; /* how many cells do i still expect to receive? */ + + /* link encryption */ + unsigned char f_session_key[8]; + unsigned char b_session_key[8]; + unsigned char f_session_iv[8]; + unsigned char b_session_iv[8]; + EVP_CIPHER_CTX f_ctx; + EVP_CIPHER_CTX b_ctx; + +// struct timeval lastsend; /* time of last transmission to the client */ +// struct timeval interval; /* transmission interval */ + + uint32_t addr; /* these two uniquely identify a router */ + uint16_t port; + +/* used by app: */ + + ss_t ss; /* standard structure */ + int ss_received; /* size of ss, received so far */ + +/* used by OR, to keep state while connect()ing: Kludge. */ + + RSA *prkey; + struct sockaddr_in local; + + /* link info */ + uint32_t min; + uint32_t max; + + char *address; /* strdup into this, gets free_connection frees it */ + RSA *pkey; /* public RSA key for the other side */ + + char nonce[8]; + +} connection_t; + +/* config stuff we know about the other ORs in the network */ +typedef struct +{ + char *address; + + uint32_t addr; + uint16_t port; + + RSA *pkey; /* public RSA key */ + + /* link info */ + uint32_t min; + uint32_t max; +// struct timeval min_interval; + + /* time when last data was sent to that router */ +// struct timeval lastsend; + + /* socket */ +// int s; + + void *next; +} routerinfo_t; + +/* per-anonymous-connection struct */ +typedef struct +{ +#if 0 + uint32_t p_addr; /* all in network order */ + uint16_t p_port; +#endif + uint32_t n_addr; + uint16_t n_port; + connection_t *p_conn; + connection_t *n_conn; + + aci_t p_aci; /* connection identifiers */ + aci_t n_aci; + + unsigned char p_f; /* crypto functions */ + unsigned char n_f; + + unsigned char p_key[128]; /* crypto keys */ + unsigned char n_key[128]; + + unsigned char p_iv[16]; /* initialization vectors */ + unsigned char n_iv[16]; + + EVP_CIPHER_CTX p_ctx; /* cipher context */ + EVP_CIPHER_CTX n_ctx; + + uint32_t expire; /* expiration time for the corresponding onion */ + + int state; + + unsigned char *onion; /* stores the onion when state is CONN_STATE_OPEN_WAIT */ + uint32_t onionlen; /* total onion length */ + uint32_t recvlen; /* length of the onion so far */ + + void *next; +} circuit_t; + + + + + /* all the function prototypes go here */ + + +/********************************* args.c ***************************/ + +/* print help*/ +void print_usage(); + +/* get command-line arguments */ +int getargs(int argc,char *argv[], char *args,char **conf_filename, int *loglevel); + +/********************************* buffers.c ***************************/ + +int buf_new(char **pbuf, size_t *pbuflen, size_t *pbuf_datalen); + +int buf_free(char *buf); + +int read_to_buf(int s, char **pbuf, size_t *pbuflen, size_t *pbuf_datalen, int *preached_eof); + /* grab from s, put onto buf, return how many bytes read */ + +int flush_buf(int s, char **pbuf, size_t *pbuflen, size_t *pbuf_datalen); + /* push from buf onto s + * then memmove to front of buf + * return -1 or how many bytes remain on the buf */ + +int write_to_buf(char *string, size_t string_len, + char **pbuf, size_t *pbuflen, size_t *pbuf_datalen); + /* append string to buf (growing as needed, return -1 if "too big") + * return total number of bytes on the buf + */ + +int fetch_from_buf(char *string, size_t string_len, + char **pbuf, size_t *pbuflen, size_t *pbuf_datalen); + /* if there is string_len bytes in buf, write them onto string, + * * then memmove buf back (that is, remove them from buf) */ + +/********************************* cell.c ***************************/ + +int check_sane_cell(cell_t *cell); + +/********************************* circuit.c ***************************/ + +void circuit_add(circuit_t *circ); +void circuit_remove(circuit_t *circ); + +circuit_t *circuit_new(aci_t p_aci, connection_t *p_conn); + +/* internal */ +aci_t get_unique_aci_by_addr_port(uint32_t addr, uint16_t port, int aci_type); + +circuit_t *circuit_get_by_aci_conn(aci_t aci, connection_t *conn); +circuit_t *circuit_get_by_conn(connection_t *conn); + +int circuit_deliver_data_cell(cell_t *cell, circuit_t *circ, connection_t *conn, int crypt_type); +int circuit_crypt(circuit_t *circ, char *in, size_t inlen, char crypt_type); + +int circuit_init(circuit_t *circ, int aci_type); +void circuit_free(circuit_t *circ); + +void circuit_close(circuit_t *circ); + +void circuit_about_to_close_connection(connection_t *conn); + /* flush and send destroys for all circuits using conn */ + +/********************************* command.c ***************************/ + +void command_process_cell(cell_t *cell, connection_t *conn); + +void command_process_create_cell(cell_t *cell, connection_t *conn); +void command_process_data_cell(cell_t *cell, connection_t *conn); +void command_process_destroy_cell(cell_t *cell, connection_t *conn); + +/********************************* config.c ***************************/ + +/* loads the configuration file */ +int getconfig(char *filename, config_opt_t *options); + +/********************************* connection.c ***************************/ + +connection_t *connection_new(int type); + +void connection_free(connection_t *conn); + +int connection_create_listener(RSA *prkey, struct sockaddr_in *local, int type); + +int connection_handle_listener_read(connection_t *conn, int new_type, int new_state); + +/* start all connections that should be up but aren't */ +int retry_all_connections(routerinfo_t **router_array, int rarray_len, + RSA *prkey, uint16_t or_port, uint16_t op_port); + +int connection_read_to_buf(connection_t *conn); + +int connection_fetch_from_buf(char *string, int len, connection_t *conn); + +int connection_flush_buf(connection_t *conn); + +int connection_write_to_buf(char *string, int len, connection_t *conn); +int connection_send_destroy(aci_t aci, connection_t *conn); +int connection_encrypt_cell_header(cell_t *cellp, connection_t *conn); +int connection_write_cell_to_buf(cell_t *cellp, connection_t *conn); + +int connection_process_inbuf(connection_t *conn); +int connection_process_cell_from_inbuf(connection_t *conn); + +int connection_finished_flushing(connection_t *conn); + +/********************************* connection_or.c ***************************/ + +int connection_or_process_inbuf(connection_t *conn); +int connection_or_finished_flushing(connection_t *conn); + +connection_t *connection_or_new(void); +connection_t *connection_or_listener_new(void); + +void conn_or_init_crypto(connection_t *conn); + +int or_handshake_client_process_auth(connection_t *conn); +int or_handshake_client_send_auth(connection_t *conn); + +int or_handshake_server_process_auth(connection_t *conn); +int or_handshake_server_process_nonce(connection_t *conn); + +int connect_to_router(routerinfo_t *router, RSA *prkey, struct sockaddr_in *local); + +int connection_or_create_listener(RSA *prkey, struct sockaddr_in *local); +int connection_or_handle_listener_read(connection_t *conn); + +/********************************* connection_op.c ***************************/ + +connection_t *connection_op_new(void); +connection_t *connection_op_listener_new(void); + +int op_handshake_process_keys(connection_t *conn); + +int connection_op_process_inbuf(connection_t *conn); + +int connection_op_finished_flushing(connection_t *conn); + +int connection_op_create_listener(RSA *prkey, struct sockaddr_in *local); + +int connection_op_handle_listener_read(connection_t *conn); + +/********************************* connection_app.c ***************************/ + +connection_t *connection_app_new(void); + +int connection_app_process_inbuf(connection_t *conn); +int connection_app_package_inbuf(connection_t *conn); +int connection_app_process_data_cell(cell_t *cell, connection_t *conn); + +int connection_app_finished_flushing(connection_t *conn); + +/********************************* main.c ***************************/ + +int connection_add(connection_t *conn); +int connection_remove(connection_t *conn); +void connection_set_poll_socket(connection_t *conn); + +connection_t *connection_get_by_addr_port(uint32_t addr, uint16_t port); + +connection_t *connection_get_by_type(int type); + +routerinfo_t *router_get_by_addr_port(uint32_t addr, uint16_t port); + +void connection_watch_events(connection_t *conn, short events); + +void check_conn_read(int i); +void check_conn_marked(int i); +void check_conn_write(int i); + +void check_conn_hup(int i); + +int do_main_loop(void); + +int main(int argc, char *argv[]); + +/********************************* onion.c ***************************/ + +int decide_aci_type(uint32_t local_addr, uint16_t local_port, + uint32_t remote_addr, uint16_t remote_port); + +int process_onion(circuit_t *circ, connection_t *conn); + +/********************************* routers.c ***************************/ + +routerinfo_t **getrouters(char *routerfile, size_t *listlenp); +void delete_routerlist(routerinfo_t *list); +/* create an NULL-terminated array of pointers pointing to elements of a router list */ +routerinfo_t **make_rarray(routerinfo_t* list, size_t *listlenp); + + +#endif diff --git a/src/or/routers.c b/src/or/routers.c new file mode 100644 index 000000000..d24d4d1bd --- /dev/null +++ b/src/or/routers.c @@ -0,0 +1,365 @@ +/** + * routers.c + * Routines for loading the list of routers and their public RSA keys. + * + * Matej Pfajfar <mp292@cam.ac.uk> + */ + +#define OR_ROUTERLIST_SEPCHARS " \t\n" +#define OR_PUBLICKEY_BEGIN_TAG "-----BEGIN RSA PUBLIC KEY-----\n" + +#include "or.h" + +/* delete a list of routers from memory */ +void delete_routerlist(routerinfo_t *list) +{ + routerinfo_t *tmp = NULL; + + if (!list) + return; + + do + { + tmp=list->next; + free((void *)list->address); + RSA_free(list->pkey); + free((void *)list); + list = tmp; + } + while (list != NULL); + + return; +} + +/* create an NULL-terminated array of pointers pointing to elements of a router list */ +/* this is done in two passes through the list - inefficient but irrelevant as this is + * only done once when op/or start up */ +routerinfo_t **make_rarray(routerinfo_t* list, size_t *len) +{ + routerinfo_t *tmp=NULL; + int listlen = 0; + routerinfo_t **array=NULL; + routerinfo_t **p=NULL; + + if ((!list) || (!len)) + return NULL; + + /* get the length of the list */ + tmp = list; + do + { + listlen++; + tmp = tmp->next; + } + while (tmp != NULL); + + array = malloc((listlen+1)*sizeof(routerinfo_t *)); + if (!array) + { + log(LOG_ERR,"Error allocating memory."); + return NULL; + } + + tmp=list; + p = array; + do + { + *p = tmp; + p++; + tmp = tmp->next; + } + while(tmp != NULL); + *p=NULL; + + *len = listlen; + return array; +} + +/* load the router list */ +routerinfo_t **getrouters(char *routerfile, size_t *lenp) +{ + int retval = 0; + char *retp = NULL; + routerinfo_t *router=NULL, *routerlist=NULL, *lastrouter=NULL; + FILE *rf; /* router file */ + fpos_t fpos; + char line[512]; + char *token; + char *errtest; /* detecting errors in strtoul() calls */ + struct hostent *rent; + + if ((!routerfile) || (!lenp)) + return NULL; + + if (strcspn(routerfile,CONFIG_LEGAL_FILENAME_CHARACTERS) != 0) + { + log(LOG_ERR,"Filename %s contains illegal characters.",routerfile); + return NULL; + } + + /* open the router list */ + rf = fopen(routerfile,"r"); + if (!rf) + { + log(LOG_ERR,"Could not open %s.",routerfile); + return NULL; + } + + retp= fgets(line,512,rf); + while (retp) + { + log(LOG_DEBUG,"getrouters():Line :%s",line); + token = (char *)strtok(line,OR_ROUTERLIST_SEPCHARS); + if (token) + { + log(LOG_DEBUG,"getrouters():Token : %s",token); + if (token[0] != '#') /* ignore comment lines */ + { + router = malloc(sizeof(routerinfo_t)); + if (!router) + { + log(LOG_ERR,"Could not allocate memory."); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + +#if 0 + router->conn_bufs = NULL; /* no output buffers */ + router->last_conn_buf = NULL; + router->next_to_service = 0; + + router->s = -1; /* to signify this router is as yet unconnected */ + router->celllen = 0; /* cell buffer is empty */ +#endif + + /* read the address */ + router->address = malloc(strlen(token)+1); + if (!router->address) + { + log(LOG_ERR,"Could not allocate memory."); + fclose(rf); + free((void *)router); + delete_routerlist(routerlist); + return NULL; + } + strcpy(router->address,token); + + rent = (struct hostent *)gethostbyname(router->address); + if (!rent) + { + log(LOG_ERR,"Could not get address for router %s.",router->address); + fclose(rf); + free((void *)router->address); + free((void *)router); + delete_routerlist(routerlist); + return NULL; + } + + memcpy(&router->addr, rent->h_addr,rent->h_length); + + /* read the port */ + token = (char *)strtok(NULL,OR_ROUTERLIST_SEPCHARS); + if (token) + { + log(LOG_DEBUG,"getrouters():Token :%s",token); + router->port = (uint16_t)strtoul(token,&errtest,0); + if ((*token != '\0') && (*errtest == '\0')) /* conversion was successful */ + { + /* convert port to network format */ + router->port = htons(router->port); + + /* read min bandwidth */ + token = (char *)strtok(NULL,OR_ROUTERLIST_SEPCHARS); + if (token) /* min bandwidth */ + { + router->min = (uint32_t)strtoul(token,&errtest,0); + if ((*token != '\0') && (*errtest == '\0')) /* conversion was successful */ + { + if (router->min) /* must not be zero */ + { + /* read max bandwidth */ + token = (char *)strtok(NULL,OR_ROUTERLIST_SEPCHARS); + if (token) /* max bandwidth */ + { + router->max = (uint32_t)strtoul(token,&errtest,0); + if ((*token != '\0') && (*errtest == '\0')) /* conversion was successful */ + { + if (router->max) /* must not be zero */ + { + /* check that there is a public key entry for that router */ + retval = fgetpos(rf, &fpos); /* save the current file position + * we wil return to it later if we find a public key */ + if (retval == -1) + { + log(LOG_ERR,"Could not save position in %s.",routerfile); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + do /* read through to the next non-empty line */ + { + retp=fgets(line,512,rf); + if (!retp) + { + log(LOG_ERR,"Could not find a public key entry for router %s:%u.",router->address,router->port); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + log(LOG_DEBUG,"getrouters():Line:%s",line); + if ((*line != '#') && ( strspn(line,OR_ROUTERLIST_SEPCHARS) != strlen(line) )) + { + break; + } + } while (1); + + if (!strcmp(line,OR_PUBLICKEY_BEGIN_TAG)) /* we've got the public key */ + { + retval = fsetpos(rf,&fpos); /* get us back to where we were otherwise crypto lib won't find the key */ + if (retval == -1) + { + log(LOG_ERR,"Could not set position in %s.",routerfile); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + else /* we found something else; this isn't right */ + { + log(LOG_ERR,"Could not find a public key entry for router %s:%u.",router->address,router->port); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + + log(LOG_DEBUG,"getrouters():Reading the key ..."); + /* read the public key into router->pkey */ + router->pkey=NULL; + router->pkey = PEM_read_RSAPublicKey(rf,&router->pkey,NULL,NULL); + if (!router->pkey) /* something went wrong */ + { + log(LOG_ERR,"Could not read public key for router %s:%u.",router->address,router->port); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + else /* read the key */ + { + log(LOG_DEBUG,"getrouters():Public key size = %u.", RSA_size(router->pkey)); + if (RSA_size(router->pkey) != 128) /* keys MUST be 1024 bits in size */ + { + log(LOG_ERR,"Key for router %s:%u is not 1024 bits. All keys must be exactly 1024 bits long.",router->address,router->port); + free((void *)router->address); + RSA_free(router->pkey); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + router->next = NULL; + /* save the entry into the routerlist linked list */ + if (!routerlist) /* this is the first entry */ + routerlist = router; + else + lastrouter->next = (void *)router; + lastrouter = router; + } + } + else /* maximum link utilisation is zero */ + { + log(LOG_ERR,"Entry for router %s doesn't contain a valid maximum bandwidth entry (must be > 0).",router->address); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + else + { + log(LOG_ERR,"Entry for router %s doesn't seem to contain a valid maximum bandwidth entry.",router->address); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + else + { + log(LOG_ERR,"Entry for router %s doesn't seem to contain a maximum bandwidth entry.",router->address); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + else + { + log(LOG_ERR,"Entry for router %s doesn't contain a valid minimum bandwidth entry (must be > 0).",router->address); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + else + { + log(LOG_ERR,"Entry for router %s doesn't seem to contain a valid minimum bandwidth entry.",router->address); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + else + { + log(LOG_ERR,"Entry for router %s doesn't seem to contain a minimum bandwidth entry.",router->address); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + else + { + log(LOG_ERR,"Entry for router %s doesn't seem to contain a valid port number.",router->address); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + else + { + log(LOG_ERR,"Entry for router %s doesn't seem to contain a port number.",router->address); + free((void *)router->address); + free((void *)router); + fclose(rf); + delete_routerlist(routerlist); + return NULL; + } + } + } + retp=fgets(line,512,rf); + } + + fclose(rf); + return make_rarray(routerlist, lenp); +} |