diff --git a/src/common/crypto.c b/src/common/crypto.c index b2ddbb1735..f6c7360d53 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -472,6 +472,28 @@ int crypto_pk_read_public_key_from_string(crypto_pk_env_t *env, char *src, int l return 0; } +int +crypto_pk_write_private_key_to_filename(crypto_pk_env_t *env, + const char *fname) +{ + BIO *bio; + char *cp; + long len; + int r; + assert(env->type == CRYPTO_PK_RSA); + if (!(bio = BIO_new(BIO_s_mem()))) + return -1; + if (PEM_write_bio_RSAPrivateKey(bio, (RSA*)env->key, NULL,NULL,0,0,NULL)) { + BIO_free(bio); + return -1; + } + len = BIO_get_mem_data(bio, &cp); + assert(len == strlen(cp)); + r = write_str_to_file(fname, cp); + BIO_free(bio); + return r; +} + int crypto_pk_write_private_key_to_file(crypto_pk_env_t *env, FILE *dest) { assert(env && dest); diff --git a/src/common/crypto.h b/src/common/crypto.h index ccd48fa56a..79e2447766 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -40,6 +40,7 @@ int crypto_pk_read_public_key_from_file(crypto_pk_env_t *env, FILE *src); int crypto_pk_write_public_key_to_string(crypto_pk_env_t *env, char **dest, int *len); int crypto_pk_read_public_key_from_string(crypto_pk_env_t *env, char *src, int len); int crypto_pk_write_private_key_to_file(crypto_pk_env_t *env, FILE *dest); +int crypto_pk_write_private_key_to_filename(crypto_pk_env_t *env, const char *fname); int crypto_pk_write_public_key_to_file(crypto_pk_env_t *env, FILE *dest); int crypto_pk_check_key(crypto_pk_env_t *env); int crypto_pk_read_private_key_from_filename(crypto_pk_env_t *env, const char *keyfile); diff --git a/src/common/util.c b/src/common/util.c index c942c5d731..a7a35acc94 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -13,6 +13,10 @@ #include "util.h" #include "log.h" +/* + * Memory + */ + void *tor_malloc(size_t size) { void *result; @@ -26,6 +30,10 @@ void *tor_malloc(size_t size) { return result; } +/* + * Time + */ + void my_gettimeofday(struct timeval *timeval) { @@ -88,6 +96,10 @@ void tv_addms(struct timeval *a, long ms) { a->tv_usec %= 1000000; } +/* + * Low-level I/O. + */ + /* a wrapper for write(2) that makes sure to write all count bytes. * Only use if fd is a blocking socket. */ int write_all(int fd, const void *buf, size_t count) { @@ -129,6 +141,10 @@ void set_socket_nonblocking(int socket) #endif } +/* + * Process control + */ + int spawn_func(int (*func)(void *), void *data) { #ifdef MS_WINDOWS @@ -164,7 +180,9 @@ void spawn_exit() } -/* Fake socket pair over TCP. Code adapted from perl 5.8.0's util.c */ +/* + * Windows compatibility. + */ int tor_socketpair(int family, int type, int protocol, int fd[2]) { @@ -276,3 +294,100 @@ int correct_socket_errno(int s) return WSAEWOULDBLOCK; } #endif + +/* + * Filesystem operations. + */ +file_status_t file_status(const char *fname) +{ + struct stat st; + if (stat(fname, &st)) { + if (errno == ENOENT) { + return FN_NOENT; + } + return FN_ERROR; + } + if (st.st_mode & S_IFDIR) + return FN_DIR; + else if (st.st_mode & S_IFREG) + return FN_FILE; + else + return FN_ERROR; +} + +int check_private_dir(const char *dirname, int create) +{ + struct stat st; + if (stat(dirname, &st)) { + if (errno != ENOENT) { + log(LOG_ERR, "Directory %s cannot be read: %s", dirname, + strerror(errno)); + return -1; + } + if (!create) { + log(LOG_ERR, "Directory %s does not exist.", dirname); + return -1; + } + log(LOG_INFO, "Creating directory %s", dirname); + if (mkdir(dirname, 0700)) { + log(LOG_ERR, "Error creating directory %s: %s", dirname, + strerror(errno)); + return -1; + } else { + return 0; + } + } + if (!(st.st_mode & S_IFDIR)) { + log(LOG_ERR, "%s is not a directory", dirname); + return -1; + } + if (st.st_uid != getuid()) { + log(LOG_ERR, "%s is not owned by this UID (%d)", dirname, getuid()); + return -1; + } + if (st.st_mode & 0077) { + log(LOG_WARNING, "Fixing permissions on directory %s", dirname); + if (chmod(dirname, 0700)) { + log(LOG_ERR, "Could not chmod directory %s: %s", dirname, + strerror(errno)); + return -1; + } else { + return 0; + } + } + return 0; +} + +int +write_str_to_file(const char *fname, const char *str) +{ + char tempname[1024]; + int fd; + FILE *file; + if (strlen(fname) > 1000) { + log(LOG_ERR, "Filename %s is too long.", fname); + return -1; + } + strcpy(tempname,fname); + strcat(tempname,".tmp"); + if ((fd = open(tempname, O_WRONLY|O_CREAT|O_TRUNC, 0600)) < 0) { + log(LOG_ERR, "Couldn't open %s for writing: %s", tempname, + strerror(errno)); + return -1; + } + if (!(file = fdopen(fd, "w"))) { + log(LOG_ERR, "Couldn't fdopen %s for writing: %s", tempname, + strerror(errno)); + close(fd); return -1; + } + if (fputs(str,file)) { + log(LOG_ERR, "Error writing to %s: %s", tempname, strerror(errno)); + fclose(file); return -1; + } + fclose(file); + if (rename(tempname, fname)) { + log(LOG_ERR, "Error replacing %s: %s", fname, strerror(errno)); + return -1; + } + return 0; +} diff --git a/src/common/util.h b/src/common/util.h index 94fcd512dd..c90175669f 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -56,6 +56,18 @@ int read_all(int fd, void *buf, size_t count); void set_socket_nonblocking(int socket); +typedef enum { FN_ERROR, FN_NOENT, FN_FILE, FN_DIR} file_status_t; + +/* Return FN_ERROR if filename can't be read, FN_NOENT if it doesn't + * exist, FN_FILE if it is a regular file, or FN_DIR if it's a + * directory. */ +file_status_t file_status(const char *filename); +/* Check whether dirname exists and is private. If yes returns + * 0. Else returns -1. + */ +int check_private_dir(const char *dirname, int create); +int write_str_to_file(const char *fname, const char *str); + /* Minimalist interface to run a void function in the background. On unix calls fork, on win32 calls beginthread. Returns -1 on failure. func should not return, but rather should call spawn_exit. diff --git a/src/or/main.c b/src/or/main.c index f23176b22f..ee6cd149a3 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -433,25 +433,6 @@ static int prepare_for_poll(void) { return (1000 - (now.tv_usec / 1000)); /* how many milliseconds til the next second? */ } -#define FN_ERROR -1 -#define FN_NOENT 0 -#define FN_FILE 1 -#define FN_DIR 2 -static int fn_exists(const char *fname) -{ - struct stat st; - if (stat(fname, &st)) { - if (errno == ENOENT) { - return FN_NOENT; - } - return FN_ERROR; - } - if (st.st_mode & S_IFDIR) - return FN_DIR; - else - return FN_FILE; -} - static crypto_pk_env_t *init_key_from_file(const char *fname) { crypto_pk_env_t *prkey = NULL; @@ -463,7 +444,7 @@ static crypto_pk_env_t *init_key_from_file(const char *fname) goto error; } - switch(fn_exists(fname)) { + switch(file_status(fname)) { case FN_DIR: case FN_ERROR: log(LOG_ERR, "Can't read key from %s", fname); @@ -479,22 +460,10 @@ static crypto_pk_env_t *init_key_from_file(const char *fname) goto error; } log(LOG_INFO, "Generated key seems valid"); - fd = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0400); - if (fd == -1) { - log(LOG_ERR, "Can't open %s for writing", fname); + if (crypto_pk_write_private_key_to_filename(prkey, fname)) { + log(LOG_ERR, "Couldn't write generated key to %s.", fname); goto error; } - file = fdopen(fd, "w"); - if (!file) { - log(LOG_ERR, "Can't fdopen %s for writing", fname); - goto error; - } - if (crypto_pk_write_private_key_to_file(prkey, file) < 0) { - log(LOG_ERR, "Can't write private key to %s", fname); - goto error; - } - fclose(file); - /* XXX fingerprint */ return prkey; case FN_FILE: if (crypto_pk_read_private_key_from_filename(prkey, fname)) { @@ -519,10 +488,9 @@ static crypto_pk_env_t *init_key_from_file(const char *fname) static int init_keys(void) { char keydir[512]; - char fingerprint[FINGERPRINT_LEN+1]; + char fingerprint[FINGERPRINT_LEN+MAX_NICKNAME_LEN+3]; char *cp; crypto_pk_env_t *prkey; - FILE *file; /* OP's don't need keys. Just initialize the TLS context.*/ if (!options.OnionRouter && !options.DirPort) { @@ -538,34 +506,12 @@ static int init_keys(void) return -1; } strcpy(keydir, options.DataDirectory); - switch (fn_exists(keydir)) { - case FN_NOENT: - log_fn(LOG_ERR, "DataDirectory does not exist"); - return -1; - case FN_ERROR: - log_fn(LOG_ERR, "DataDirectory can't be read"); - return -1; - case FN_FILE: - log_fn(LOG_ERR, "DataDirectory is not a directory."); + if (check_private_dir(keydir, 1)) { return -1; } strcat(keydir, "/keys"); - switch (fn_exists(keydir)) { - case FN_NOENT: - if (mkdir(keydir, 0700)) { - log_fn(LOG_ERR, "Error making key directory."); - return -1; - } - break; - case FN_ERROR: - log_fn(LOG_ERR, "Error reading key directory."); + if (check_private_dir(keydir, 1)) { return -1; - case FN_FILE: - log_fn(LOG_ERR, "Key directory is not a directory."); - return -1; - case FN_DIR: - chmod(keydir, 0700); - break; } cp = keydir + strlen(keydir); /* End of string. */ assert(!*cp); @@ -600,28 +546,23 @@ static int init_keys(void) } strcpy(keydir, options.DataDirectory); strcat(keydir, "/router.desc"); - file = fopen(keydir, "w"); - if (!file) { - log_fn(LOG_ERR, "Error opening %s for writing", keydir); + if (write_str_to_file(keydir, router_get_my_descriptor())) { return -1; } - fputs(router_get_my_descriptor(), file); - fclose(file); /* 5. Dump fingerprint to 'fingerprint' */ strcpy(keydir, options.DataDirectory); strcat(keydir, "/fingerprint"); - file = fopen(keydir, "w"); - if (!file) { - log_fn(LOG_ERR, "Error opening %s for writing", keydir); - return -1; - } - if (crypto_pk_get_fingerprint(get_identity_key(), fingerprint)<0) { + assert(strlen(options.Nickname) <= MAX_NICKNAME_LEN); + strcpy(fingerprint, options.Nickname); + strcat(fingerprint, " "); + if (crypto_pk_get_fingerprint(get_identity_key(), + fingerprint+strlen(fingerprint))<0) { log_fn(LOG_ERR, "Error computing fingerprint"); return -1; } - fprintf(file, "%s %s\n", options.Nickname, fingerprint); - fclose(file); - + strcat(fingerprint, "\n"); + if (write_str_to_file(keydir, fingerprint)) + return -1; return 0; } @@ -774,6 +715,7 @@ int dump_router_to_string(char *s, int maxlen, routerinfo_t *router, char *identity_pkey; char digest[20]; char signature[128]; + char published[32]; int onion_pkeylen, link_pkeylen, identity_pkeylen; int written; int result=0; @@ -796,9 +738,12 @@ int dump_router_to_string(char *s, int maxlen, routerinfo_t *router, log_fn(LOG_WARNING,"write link_pkey to string failed!"); return -1; } + strftime(published, 32, "%Y-%m-%d %H:%M:%S", gmtime(&router->published_on)); result = snprintf(s, maxlen, - "router %s %d %d %d %d\nonion-key\n%s" + "router %s %d %d %d %d\n" + "published %s\n" + "onion-key\n%s" "link-key\n%s" "signing-key\n%s", router->address, @@ -806,6 +751,7 @@ int dump_router_to_string(char *s, int maxlen, routerinfo_t *router, router->ap_port, router->dir_port, router->bandwidth, + published, onion_pkey, link_pkey, identity_pkey); free(onion_pkey); @@ -1005,6 +951,7 @@ static int init_descriptor(void) { ri->or_port = options.ORPort; ri->ap_port = options.APPort; ri->dir_port = options.DirPort; + ri->published_on = time(NULL); ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); ri->link_pkey = crypto_pk_dup_key(get_link_key()); ri->identity_pkey = crypto_pk_dup_key(get_identity_key()); diff --git a/src/or/or.h b/src/or/or.h index ce617ccda3..474ffa2308 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -101,6 +101,7 @@ #define MAX_BUF_SIZE (640*1024) #define DEFAULT_BANDWIDTH_OP (1024 * 1000) +#define MAX_NICKNAME_LEN 32 #define ACI_TYPE_LOWER 0 #define ACI_TYPE_HIGHER 1 @@ -326,6 +327,8 @@ typedef struct { uint16_t or_port; uint16_t ap_port; uint16_t dir_port; + + time_t published_on; crypto_pk_env_t *onion_pkey; /* public RSA key for onions */ crypto_pk_env_t *link_pkey; /* public RSA key for TLS */ diff --git a/src/or/routers.c b/src/or/routers.c index eb8c6163a8..6872771538 100644 --- a/src/or/routers.c +++ b/src/or/routers.c @@ -318,6 +318,7 @@ typedef enum { K_ONION_KEY, K_LINK_KEY, K_ROUTER_SIGNATURE, + K_PUBLISHED, _SIGNATURE, _PUBLIC_KEY, _ERR, @@ -337,6 +338,7 @@ static struct token_table_ent token_table[] = { { "onion-key", K_ONION_KEY }, { "link-key", K_LINK_KEY }, { "router-signature", K_ROUTER_SIGNATURE }, + { "published", K_PUBLISHED }, { NULL, -1 } }; @@ -492,6 +494,7 @@ router_dump_token(directory_token_t *tok) { case K_ONION_KEY: printf("Onion-key"); break; case K_LINK_KEY: printf("Link-key"); break; case K_ROUTER_SIGNATURE: printf("Router-signature"); break; + case K_PUBLISHED: printf("Published"); break; default: printf("?????? %d\n", tok->tp); return; } @@ -513,7 +516,6 @@ router_get_next_token(char **s, directory_token_t *tok) { #endif - /* return the first char of s that is not whitespace and not a comment */ static char *eat_whitespace(char *s) { assert(s); @@ -817,6 +819,7 @@ routerinfo_t *router_get_entry_from_string(char**s) { char digest[128]; directory_token_t _tok; directory_token_t *tok = &_tok; + struct tm published; #define NEXT_TOKEN() \ do { if (router_get_next_token(s, tok)) { \ @@ -875,6 +878,19 @@ routerinfo_t *router_get_entry_from_string(char**s) { log_fn(LOG_DEBUG,"or_port %d, ap_port %d, dir_port %d, bandwidth %d.", router->or_port, router->ap_port, router->dir_port, router->bandwidth); + NEXT_TOKEN(); + if (tok->tp != K_PUBLISHED) { + log_fn(LOG_WARNING, "Missing published time"); goto err; + } + if (tok->val.cmd.n_args != 2) { + log_fn(LOG_WARNING, "Wrong number of arguments to published"); goto err; + } + tok->val.cmd.args[1][-1] = ' '; /* Re-insert space. */ + if (!strptime(tok->val.cmd.args[0], "%Y-%m-%d %H:%M:%S", &published)) { + log_fn(LOG_WARNING, "Published time was unparseable"); goto err; + } + router->published_on = timegm(&published); + NEXT_TOKEN(); if (tok->tp != K_ONION_KEY) { log_fn(LOG_WARNING, "Missing onion-key"); goto err; diff --git a/src/or/test.c b/src/or/test.c index b3cc777c46..5680166e32 100644 --- a/src/or/test.c +++ b/src/or/test.c @@ -519,6 +519,7 @@ test_dir_format() r1.address = "testaddr1.foo.bar"; r1.addr = 0xc0a80001u; /* 192.168.0.1 */ + r1.published_on = 0; r1.or_port = 9000; r1.ap_port = 9002; r1.dir_port = 9003; @@ -539,6 +540,7 @@ test_dir_format() ex2.next = NULL; r2.address = "tor.tor.tor"; r2.addr = 0x0a030201u; /* 10.3.2.1 */ + r2.published_on = 5; r2.or_port = 9005; r2.ap_port = 0; r2.dir_port = 0; @@ -555,7 +557,9 @@ test_dir_format() test_assert(!crypto_pk_write_public_key_to_string(pk3 , &pk3_str, &pk3_str_len)); - strcpy(buf2, "router testaddr1.foo.bar 9000 9002 9003 1000\nonion-key\n"); + strcpy(buf2, "router testaddr1.foo.bar 9000 9002 9003 1000\n" + "published 1970-01-01 00:00:00\n" + "onion-key\n"); strcat(buf2, pk1_str); strcat(buf2, "link-key\n"); strcat(buf2, pk3_str);