diff options
Diffstat (limited to 'src/or')
72 files changed, 2912 insertions, 3222 deletions
diff --git a/src/or/addressmap.c b/src/or/addressmap.c index c92af38254..f278564e80 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -214,7 +214,7 @@ addressmap_clear_excluded_trackexithosts(const or_options_t *options) dot--; if (*dot == '.') dot++; nodename = tor_strndup(dot, len-5-(dot-target));; - node = node_get_by_nickname(nodename, 0); + node = node_get_by_nickname(nodename, NNF_NO_WARN_UNNAMED); tor_free(nodename); if (!node || (allow_nodes && !routerset_contains_node(allow_nodes, node)) || diff --git a/src/or/buffers.c b/src/or/buffers.c deleted file mode 100644 index bd84103c37..0000000000 --- a/src/or/buffers.c +++ /dev/null @@ -1,2214 +0,0 @@ -/* Copyright (c) 2001 Matej Pfajfar. - * Copyright (c) 2001-2004, Roger Dingledine. - * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2017, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file buffers.c - * \brief Implements a generic buffer interface. - * - * A buf_t is a (fairly) opaque byte-oriented FIFO that can read to or flush - * from memory, sockets, file descriptors, TLS connections, or another buf_t. - * Buffers are implemented as linked lists of memory chunks. - * - * All socket-backed and TLS-based connection_t objects have a pair of - * buffers: one for incoming data, and one for outcoming data. These are fed - * and drained from functions in connection.c, trigged by events that are - * monitored in main.c. - * - * This module has basic support for reading and writing on buf_t objects. It - * also contains specialized functions for handling particular protocols - * on a buf_t backend, including SOCKS (used in connection_edge.c), Tor cells - * (used in connection_or.c and channeltls.c), HTTP (used in directory.c), and - * line-oriented communication (used in control.c). - **/ -#define BUFFERS_PRIVATE -#include "or.h" -#include "addressmap.h" -#include "buffers.h" -#include "config.h" -#include "connection_edge.h" -#include "connection_or.h" -#include "control.h" -#include "reasons.h" -#include "ext_orport.h" -#include "util.h" -#include "torlog.h" -#ifdef HAVE_UNISTD_H -#include <unistd.h> -#endif - -//#define PARANOIA - -#ifdef PARANOIA -/** Helper: If PARANOIA is defined, assert that the buffer in local variable - * <b>buf</b> is well-formed. */ -#define check() STMT_BEGIN assert_buf_ok(buf); STMT_END -#else -#define check() STMT_NIL -#endif - -/* Implementation notes: - * - * After flirting with memmove, and dallying with ring-buffers, we're finally - * getting up to speed with the 1970s and implementing buffers as a linked - * list of small chunks. Each buffer has such a list; data is removed from - * the head of the list, and added at the tail. The list is singly linked, - * and the buffer keeps a pointer to the head and the tail. - * - * Every chunk, except the tail, contains at least one byte of data. Data in - * each chunk is contiguous. - * - * When you need to treat the first N characters on a buffer as a contiguous - * string, use the buf_pullup function to make them so. Don't do this more - * than necessary. - * - * The major free Unix kernels have handled buffers like this since, like, - * forever. - */ - -static void socks_request_set_socks5_error(socks_request_t *req, - socks5_reply_status_t reason); - -static int parse_socks(const char *data, size_t datalen, socks_request_t *req, - int log_sockstype, int safe_socks, ssize_t *drain_out, - size_t *want_length_out); -static int parse_socks_client(const uint8_t *data, size_t datalen, - int state, char **reason, - ssize_t *drain_out); - -/* Chunk manipulation functions */ - -#define CHUNK_HEADER_LEN offsetof(chunk_t, mem[0]) - -/* We leave this many NUL bytes at the end of the buffer. */ -#ifdef DISABLE_MEMORY_SENTINELS -#define SENTINEL_LEN 0 -#else -#define SENTINEL_LEN 4 -#endif - -/* Header size plus NUL bytes at the end */ -#define CHUNK_OVERHEAD (CHUNK_HEADER_LEN + SENTINEL_LEN) - -/** Return the number of bytes needed to allocate a chunk to hold - * <b>memlen</b> bytes. */ -#define CHUNK_ALLOC_SIZE(memlen) (CHUNK_OVERHEAD + (memlen)) -/** Return the number of usable bytes in a chunk allocated with - * malloc(<b>memlen</b>). */ -#define CHUNK_SIZE_WITH_ALLOC(memlen) ((memlen) - CHUNK_OVERHEAD) - -#define DEBUG_SENTINEL - -#if defined(DEBUG_SENTINEL) && !defined(DISABLE_MEMORY_SENTINELS) -#define DBG_S(s) s -#else -#define DBG_S(s) (void)0 -#endif - -#ifdef DISABLE_MEMORY_SENTINELS -#define CHUNK_SET_SENTINEL(chunk, alloclen) STMT_NIL -#else -#define CHUNK_SET_SENTINEL(chunk, alloclen) do { \ - uint8_t *a = (uint8_t*) &(chunk)->mem[(chunk)->memlen]; \ - DBG_S(uint8_t *b = &((uint8_t*)(chunk))[(alloclen)-SENTINEL_LEN]); \ - DBG_S(tor_assert(a == b)); \ - memset(a,0,SENTINEL_LEN); \ - } while (0) -#endif - -/** Return the next character in <b>chunk</b> onto which data can be appended. - * If the chunk is full, this might be off the end of chunk->mem. */ -static inline char * -CHUNK_WRITE_PTR(chunk_t *chunk) -{ - return chunk->data + chunk->datalen; -} - -/** Return the number of bytes that can be written onto <b>chunk</b> without - * running out of space. */ -static inline size_t -CHUNK_REMAINING_CAPACITY(const chunk_t *chunk) -{ - return (chunk->mem + chunk->memlen) - (chunk->data + chunk->datalen); -} - -/** Move all bytes stored in <b>chunk</b> to the front of <b>chunk</b>->mem, - * to free up space at the end. */ -static inline void -chunk_repack(chunk_t *chunk) -{ - if (chunk->datalen && chunk->data != &chunk->mem[0]) { - memmove(chunk->mem, chunk->data, chunk->datalen); - } - chunk->data = &chunk->mem[0]; -} - -/** Keep track of total size of allocated chunks for consistency asserts */ -static size_t total_bytes_allocated_in_chunks = 0; -static void -buf_chunk_free_unchecked(chunk_t *chunk) -{ - if (!chunk) - return; -#ifdef DEBUG_CHUNK_ALLOC - tor_assert(CHUNK_ALLOC_SIZE(chunk->memlen) == chunk->DBG_alloc); -#endif - tor_assert(total_bytes_allocated_in_chunks >= - CHUNK_ALLOC_SIZE(chunk->memlen)); - total_bytes_allocated_in_chunks -= CHUNK_ALLOC_SIZE(chunk->memlen); - tor_free(chunk); -} -static inline chunk_t * -chunk_new_with_alloc_size(size_t alloc) -{ - chunk_t *ch; - ch = tor_malloc(alloc); - ch->next = NULL; - ch->datalen = 0; -#ifdef DEBUG_CHUNK_ALLOC - ch->DBG_alloc = alloc; -#endif - ch->memlen = CHUNK_SIZE_WITH_ALLOC(alloc); - total_bytes_allocated_in_chunks += alloc; - ch->data = &ch->mem[0]; - CHUNK_SET_SENTINEL(ch, alloc); - return ch; -} - -/** Expand <b>chunk</b> until it can hold <b>sz</b> bytes, and return a - * new pointer to <b>chunk</b>. Old pointers are no longer valid. */ -static inline chunk_t * -chunk_grow(chunk_t *chunk, size_t sz) -{ - off_t offset; - const size_t memlen_orig = chunk->memlen; - const size_t orig_alloc = CHUNK_ALLOC_SIZE(memlen_orig); - const size_t new_alloc = CHUNK_ALLOC_SIZE(sz); - tor_assert(sz > chunk->memlen); - offset = chunk->data - chunk->mem; - chunk = tor_realloc(chunk, new_alloc); - chunk->memlen = sz; - chunk->data = chunk->mem + offset; -#ifdef DEBUG_CHUNK_ALLOC - tor_assert(chunk->DBG_alloc == orig_alloc); - chunk->DBG_alloc = new_alloc; -#endif - total_bytes_allocated_in_chunks += new_alloc - orig_alloc; - CHUNK_SET_SENTINEL(chunk, new_alloc); - return chunk; -} - -/** If a read onto the end of a chunk would be smaller than this number, then - * just start a new chunk. */ -#define MIN_READ_LEN 8 -/** Every chunk should take up at least this many bytes. */ -#define MIN_CHUNK_ALLOC 256 -/** No chunk should take up more than this many bytes. */ -#define MAX_CHUNK_ALLOC 65536 - -/** Return the allocation size we'd like to use to hold <b>target</b> - * bytes. */ -STATIC size_t -preferred_chunk_size(size_t target) -{ - tor_assert(target <= SIZE_T_CEILING - CHUNK_OVERHEAD); - if (CHUNK_ALLOC_SIZE(target) >= MAX_CHUNK_ALLOC) - return CHUNK_ALLOC_SIZE(target); - size_t sz = MIN_CHUNK_ALLOC; - while (CHUNK_SIZE_WITH_ALLOC(sz) < target) { - sz <<= 1; - } - return sz; -} - -/** Collapse data from the first N chunks from <b>buf</b> into buf->head, - * growing it as necessary, until buf->head has the first <b>bytes</b> bytes - * of data from the buffer, or until buf->head has all the data in <b>buf</b>. - */ -STATIC void -buf_pullup(buf_t *buf, size_t bytes) -{ - chunk_t *dest, *src; - size_t capacity; - if (!buf->head) - return; - - check(); - if (buf->datalen < bytes) - bytes = buf->datalen; - - capacity = bytes; - if (buf->head->datalen >= bytes) - return; - - if (buf->head->memlen >= capacity) { - /* We don't need to grow the first chunk, but we might need to repack it.*/ - size_t needed = capacity - buf->head->datalen; - if (CHUNK_REMAINING_CAPACITY(buf->head) < needed) - chunk_repack(buf->head); - tor_assert(CHUNK_REMAINING_CAPACITY(buf->head) >= needed); - } else { - chunk_t *newhead; - size_t newsize; - /* We need to grow the chunk. */ - chunk_repack(buf->head); - newsize = CHUNK_SIZE_WITH_ALLOC(preferred_chunk_size(capacity)); - newhead = chunk_grow(buf->head, newsize); - tor_assert(newhead->memlen >= capacity); - if (newhead != buf->head) { - if (buf->tail == buf->head) - buf->tail = newhead; - buf->head = newhead; - } - } - - dest = buf->head; - while (dest->datalen < bytes) { - size_t n = bytes - dest->datalen; - src = dest->next; - tor_assert(src); - if (n >= src->datalen) { - memcpy(CHUNK_WRITE_PTR(dest), src->data, src->datalen); - dest->datalen += src->datalen; - dest->next = src->next; - if (buf->tail == src) - buf->tail = dest; - buf_chunk_free_unchecked(src); - } else { - memcpy(CHUNK_WRITE_PTR(dest), src->data, n); - dest->datalen += n; - src->data += n; - src->datalen -= n; - tor_assert(dest->datalen == bytes); - } - } - - check(); -} - -#ifdef TOR_UNIT_TESTS -/* Return the data from the first chunk of buf in cp, and its length in sz. */ -void -buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz) -{ - if (!buf || !buf->head) { - *cp = NULL; - *sz = 0; - } else { - *cp = buf->head->data; - *sz = buf->head->datalen; - } -} - -/* Write sz bytes from cp into a newly allocated buffer buf. - * Returns NULL when passed a NULL cp or zero sz. - * Asserts on failure: only for use in unit tests. - * buf must be freed using buf_free(). */ -buf_t * -buf_new_with_data(const char *cp, size_t sz) -{ - /* Validate arguments */ - if (!cp || sz <= 0) { - return NULL; - } - - tor_assert(sz < SSIZE_T_CEILING); - - /* Allocate a buffer */ - buf_t *buf = buf_new_with_capacity(sz); - tor_assert(buf); - assert_buf_ok(buf); - tor_assert(!buf->head); - - /* Allocate a chunk that is sz bytes long */ - buf->head = chunk_new_with_alloc_size(CHUNK_ALLOC_SIZE(sz)); - buf->tail = buf->head; - tor_assert(buf->head); - assert_buf_ok(buf); - tor_assert(buf_allocation(buf) >= sz); - - /* Copy the data and size the buffers */ - tor_assert(sz <= buf_slack(buf)); - tor_assert(sz <= CHUNK_REMAINING_CAPACITY(buf->head)); - memcpy(&buf->head->mem[0], cp, sz); - buf->datalen = sz; - buf->head->datalen = sz; - buf->head->data = &buf->head->mem[0]; - assert_buf_ok(buf); - - /* Make sure everything is large enough */ - tor_assert(buf_allocation(buf) >= sz); - tor_assert(buf_allocation(buf) >= buf_datalen(buf) + buf_slack(buf)); - /* Does the buffer implementation allocate more than the requested size? - * (for example, by rounding up). If so, these checks will fail. */ - tor_assert(buf_datalen(buf) == sz); - tor_assert(buf_slack(buf) == 0); - - return buf; -} -#endif - -/** Remove the first <b>n</b> bytes from buf. */ -static inline void -buf_remove_from_front(buf_t *buf, size_t n) -{ - tor_assert(buf->datalen >= n); - while (n) { - tor_assert(buf->head); - if (buf->head->datalen > n) { - buf->head->datalen -= n; - buf->head->data += n; - buf->datalen -= n; - return; - } else { - chunk_t *victim = buf->head; - n -= victim->datalen; - buf->datalen -= victim->datalen; - buf->head = victim->next; - if (buf->tail == victim) - buf->tail = NULL; - buf_chunk_free_unchecked(victim); - } - } - check(); -} - -/** Create and return a new buf with default chunk capacity <b>size</b>. - */ -buf_t * -buf_new_with_capacity(size_t size) -{ - buf_t *b = buf_new(); - b->default_chunk_size = preferred_chunk_size(size); - return b; -} - -/** Allocate and return a new buffer with default capacity. */ -buf_t * -buf_new(void) -{ - buf_t *buf = tor_malloc_zero(sizeof(buf_t)); - buf->magic = BUFFER_MAGIC; - buf->default_chunk_size = 4096; - return buf; -} - -size_t -buf_get_default_chunk_size(const buf_t *buf) -{ - return buf->default_chunk_size; -} - -/** Remove all data from <b>buf</b>. */ -void -buf_clear(buf_t *buf) -{ - chunk_t *chunk, *next; - buf->datalen = 0; - for (chunk = buf->head; chunk; chunk = next) { - next = chunk->next; - buf_chunk_free_unchecked(chunk); - } - buf->head = buf->tail = NULL; -} - -/** Return the number of bytes stored in <b>buf</b> */ -MOCK_IMPL(size_t, -buf_datalen, (const buf_t *buf)) -{ - return buf->datalen; -} - -/** Return the total length of all chunks used in <b>buf</b>. */ -size_t -buf_allocation(const buf_t *buf) -{ - size_t total = 0; - const chunk_t *chunk; - for (chunk = buf->head; chunk; chunk = chunk->next) { - total += CHUNK_ALLOC_SIZE(chunk->memlen); - } - return total; -} - -/** Return the number of bytes that can be added to <b>buf</b> without - * performing any additional allocation. */ -size_t -buf_slack(const buf_t *buf) -{ - if (!buf->tail) - return 0; - else - return CHUNK_REMAINING_CAPACITY(buf->tail); -} - -/** Release storage held by <b>buf</b>. */ -void -buf_free(buf_t *buf) -{ - if (!buf) - return; - - buf_clear(buf); - buf->magic = 0xdeadbeef; - tor_free(buf); -} - -/** Return a new copy of <b>in_chunk</b> */ -static chunk_t * -chunk_copy(const chunk_t *in_chunk) -{ - chunk_t *newch = tor_memdup(in_chunk, CHUNK_ALLOC_SIZE(in_chunk->memlen)); - total_bytes_allocated_in_chunks += CHUNK_ALLOC_SIZE(in_chunk->memlen); -#ifdef DEBUG_CHUNK_ALLOC - newch->DBG_alloc = CHUNK_ALLOC_SIZE(in_chunk->memlen); -#endif - newch->next = NULL; - if (in_chunk->data) { - off_t offset = in_chunk->data - in_chunk->mem; - newch->data = newch->mem + offset; - } - return newch; -} - -/** Return a new copy of <b>buf</b> */ -buf_t * -buf_copy(const buf_t *buf) -{ - chunk_t *ch; - buf_t *out = buf_new(); - out->default_chunk_size = buf->default_chunk_size; - for (ch = buf->head; ch; ch = ch->next) { - chunk_t *newch = chunk_copy(ch); - if (out->tail) { - out->tail->next = newch; - out->tail = newch; - } else { - out->head = out->tail = newch; - } - } - out->datalen = buf->datalen; - return out; -} - -/** Append a new chunk with enough capacity to hold <b>capacity</b> bytes to - * the tail of <b>buf</b>. If <b>capped</b>, don't allocate a chunk bigger - * than MAX_CHUNK_ALLOC. */ -static chunk_t * -buf_add_chunk_with_capacity(buf_t *buf, size_t capacity, int capped) -{ - chunk_t *chunk; - - if (CHUNK_ALLOC_SIZE(capacity) < buf->default_chunk_size) { - chunk = chunk_new_with_alloc_size(buf->default_chunk_size); - } else if (capped && CHUNK_ALLOC_SIZE(capacity) > MAX_CHUNK_ALLOC) { - chunk = chunk_new_with_alloc_size(MAX_CHUNK_ALLOC); - } else { - chunk = chunk_new_with_alloc_size(preferred_chunk_size(capacity)); - } - - chunk->inserted_time = (uint32_t)monotime_coarse_absolute_msec(); - - if (buf->tail) { - tor_assert(buf->head); - buf->tail->next = chunk; - buf->tail = chunk; - } else { - tor_assert(!buf->head); - buf->head = buf->tail = chunk; - } - check(); - return chunk; -} - -/** Return the age of the oldest chunk in the buffer <b>buf</b>, in - * milliseconds. Requires the current monotonic time, in truncated msec, - * as its input <b>now</b>. - */ -uint32_t -buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now) -{ - if (buf->head) { - return now - buf->head->inserted_time; - } else { - return 0; - } -} - -size_t -buf_get_total_allocation(void) -{ - return total_bytes_allocated_in_chunks; -} - -/** Read up to <b>at_most</b> bytes from the socket <b>fd</b> into - * <b>chunk</b> (which must be on <b>buf</b>). If we get an EOF, set - * *<b>reached_eof</b> to 1. Return -1 on error, 0 on eof or blocking, - * and the number of bytes read otherwise. */ -static inline int -read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, - int *reached_eof, int *socket_error) -{ - ssize_t read_result; - if (at_most > CHUNK_REMAINING_CAPACITY(chunk)) - at_most = CHUNK_REMAINING_CAPACITY(chunk); - read_result = tor_socket_recv(fd, CHUNK_WRITE_PTR(chunk), at_most, 0); - - if (read_result < 0) { - int e = tor_socket_errno(fd); - if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ -#ifdef _WIN32 - if (e == WSAENOBUFS) - log_warn(LD_NET,"recv() failed: WSAENOBUFS. Not enough ram?"); -#endif - *socket_error = e; - return -1; - } - return 0; /* would block. */ - } else if (read_result == 0) { - log_debug(LD_NET,"Encountered eof on fd %d", (int)fd); - *reached_eof = 1; - return 0; - } else { /* actually got bytes. */ - buf->datalen += read_result; - chunk->datalen += read_result; - log_debug(LD_NET,"Read %ld bytes. %d on inbuf.", (long)read_result, - (int)buf->datalen); - tor_assert(read_result < INT_MAX); - return (int)read_result; - } -} - -/** As read_to_chunk(), but return (negative) error code on error, blocking, - * or TLS, and the number of bytes read otherwise. */ -static inline int -read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls, - size_t at_most) -{ - int read_result; - - tor_assert(CHUNK_REMAINING_CAPACITY(chunk) >= at_most); - read_result = tor_tls_read(tls, CHUNK_WRITE_PTR(chunk), at_most); - if (read_result < 0) - return read_result; - buf->datalen += read_result; - chunk->datalen += read_result; - return read_result; -} - -/** Read from socket <b>s</b>, writing onto end of <b>buf</b>. Read at most - * <b>at_most</b> bytes, growing the buffer as necessary. If recv() returns 0 - * (because of EOF), set *<b>reached_eof</b> to 1 and return 0. Return -1 on - * error; else return the number of bytes read. - */ -/* XXXX indicate "read blocked" somehow? */ -int -read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof, - int *socket_error) -{ - /* XXXX It's stupid to overload the return values for these functions: - * "error status" and "number of bytes read" are not mutually exclusive. - */ - int r = 0; - size_t total_read = 0; - - check(); - tor_assert(reached_eof); - tor_assert(SOCKET_OK(s)); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - at_most)) - return -1; - - while (at_most > total_read) { - size_t readlen = at_most - total_read; - chunk_t *chunk; - if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { - chunk = buf_add_chunk_with_capacity(buf, at_most, 1); - if (readlen > chunk->memlen) - readlen = chunk->memlen; - } else { - size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); - chunk = buf->tail; - if (cap < readlen) - readlen = cap; - } - - r = read_to_chunk(buf, chunk, s, readlen, reached_eof, socket_error); - check(); - if (r < 0) - return r; /* Error */ - tor_assert(total_read+r < INT_MAX); - total_read += r; - if ((size_t)r < readlen) { /* eof, block, or no more to read. */ - break; - } - } - return (int)total_read; -} - -/** As read_to_buf, but reads from a TLS connection, and returns a TLS - * status value rather than the number of bytes read. - * - * Using TLS on OR connections complicates matters in two ways. - * - * First, a TLS stream has its own read buffer independent of the - * connection's read buffer. (TLS needs to read an entire frame from - * the network before it can decrypt any data. Thus, trying to read 1 - * byte from TLS can require that several KB be read from the network - * and decrypted. The extra data is stored in TLS's decrypt buffer.) - * Because the data hasn't been read by Tor (it's still inside the TLS), - * this means that sometimes a connection "has stuff to read" even when - * poll() didn't return POLLIN. The tor_tls_get_pending_bytes function is - * used in connection.c to detect TLS objects with non-empty internal - * buffers and read from them again. - * - * Second, the TLS stream's events do not correspond directly to network - * events: sometimes, before a TLS stream can read, the network must be - * ready to write -- or vice versa. - */ -int -read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf) -{ - int r = 0; - size_t total_read = 0; - - check_no_tls_errors(); - - check(); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - at_most)) - return -1; - - while (at_most > total_read) { - size_t readlen = at_most - total_read; - chunk_t *chunk; - if (!buf->tail || CHUNK_REMAINING_CAPACITY(buf->tail) < MIN_READ_LEN) { - chunk = buf_add_chunk_with_capacity(buf, at_most, 1); - if (readlen > chunk->memlen) - readlen = chunk->memlen; - } else { - size_t cap = CHUNK_REMAINING_CAPACITY(buf->tail); - chunk = buf->tail; - if (cap < readlen) - readlen = cap; - } - - r = read_to_chunk_tls(buf, chunk, tls, readlen); - check(); - if (r < 0) - return r; /* Error */ - tor_assert(total_read+r < INT_MAX); - total_read += r; - if ((size_t)r < readlen) /* eof, block, or no more to read. */ - break; - } - return (int)total_read; -} - -/** Helper for flush_buf(): try to write <b>sz</b> bytes from chunk - * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. On success, deduct - * the bytes written from *<b>buf_flushlen</b>. Return the number of bytes - * written on success, 0 on blocking, -1 on failure. - */ -static inline int -flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, - size_t *buf_flushlen) -{ - ssize_t write_result; - - if (sz > chunk->datalen) - sz = chunk->datalen; - write_result = tor_socket_send(s, chunk->data, sz, 0); - - if (write_result < 0) { - int e = tor_socket_errno(s); - if (!ERRNO_IS_EAGAIN(e)) { /* it's a real error */ -#ifdef _WIN32 - if (e == WSAENOBUFS) - log_warn(LD_NET,"write() failed: WSAENOBUFS. Not enough ram?"); -#endif - return -1; - } - log_debug(LD_NET,"write() would block, returning."); - return 0; - } else { - *buf_flushlen -= write_result; - buf_remove_from_front(buf, write_result); - tor_assert(write_result < INT_MAX); - return (int)write_result; - } -} - -/** Helper for flush_buf_tls(): try to write <b>sz</b> bytes from chunk - * <b>chunk</b> of buffer <b>buf</b> onto socket <b>s</b>. (Tries to write - * more if there is a forced pending write size.) On success, deduct the - * bytes written from *<b>buf_flushlen</b>. Return the number of bytes - * written on success, and a TOR_TLS error code on failure or blocking. - */ -static inline int -flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk, - size_t sz, size_t *buf_flushlen) -{ - int r; - size_t forced; - char *data; - - forced = tor_tls_get_forced_write_size(tls); - if (forced > sz) - sz = forced; - if (chunk) { - data = chunk->data; - tor_assert(sz <= chunk->datalen); - } else { - data = NULL; - tor_assert(sz == 0); - } - r = tor_tls_write(tls, data, sz); - if (r < 0) - return r; - if (*buf_flushlen > (size_t)r) - *buf_flushlen -= r; - else - *buf_flushlen = 0; - buf_remove_from_front(buf, r); - log_debug(LD_NET,"flushed %d bytes, %d ready to flush, %d remain.", - r,(int)*buf_flushlen,(int)buf->datalen); - return r; -} - -/** Write data from <b>buf</b> to the socket <b>s</b>. Write at most - * <b>sz</b> bytes, decrement *<b>buf_flushlen</b> by - * the number of bytes actually written, and remove the written bytes - * from the buffer. Return the number of bytes written on success, - * -1 on failure. Return 0 if write() would block. - */ -int -flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen) -{ - /* XXXX It's stupid to overload the return values for these functions: - * "error status" and "number of bytes flushed" are not mutually exclusive. - */ - int r; - size_t flushed = 0; - tor_assert(buf_flushlen); - tor_assert(SOCKET_OK(s)); - tor_assert(*buf_flushlen <= buf->datalen); - tor_assert(sz <= *buf_flushlen); - - check(); - while (sz) { - size_t flushlen0; - tor_assert(buf->head); - if (buf->head->datalen >= sz) - flushlen0 = sz; - else - flushlen0 = buf->head->datalen; - - r = flush_chunk(s, buf, buf->head, flushlen0, buf_flushlen); - check(); - if (r < 0) - return r; - flushed += r; - sz -= r; - if (r == 0 || (size_t)r < flushlen0) /* can't flush any more now. */ - break; - } - tor_assert(flushed < INT_MAX); - return (int)flushed; -} - -/** As flush_buf(), but writes data to a TLS connection. Can write more than - * <b>flushlen</b> bytes. - */ -int -flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t flushlen, - size_t *buf_flushlen) -{ - int r; - size_t flushed = 0; - ssize_t sz; - tor_assert(buf_flushlen); - tor_assert(*buf_flushlen <= buf->datalen); - tor_assert(flushlen <= *buf_flushlen); - sz = (ssize_t) flushlen; - - /* we want to let tls write even if flushlen is zero, because it might - * have a partial record pending */ - check_no_tls_errors(); - - check(); - do { - size_t flushlen0; - if (buf->head) { - if ((ssize_t)buf->head->datalen >= sz) - flushlen0 = sz; - else - flushlen0 = buf->head->datalen; - } else { - flushlen0 = 0; - } - - r = flush_chunk_tls(tls, buf, buf->head, flushlen0, buf_flushlen); - check(); - if (r < 0) - return r; - flushed += r; - sz -= r; - if (r == 0) /* Can't flush any more now. */ - break; - } while (sz > 0); - tor_assert(flushed < INT_MAX); - return (int)flushed; -} - -/** Append <b>string_len</b> bytes from <b>string</b> to the end of - * <b>buf</b>. - * - * Return the new length of the buffer on success, -1 on failure. - */ -int -write_to_buf(const char *string, size_t string_len, buf_t *buf) -{ - if (!string_len) - return (int)buf->datalen; - check(); - - if (BUG(buf->datalen >= INT_MAX)) - return -1; - if (BUG(buf->datalen >= INT_MAX - string_len)) - return -1; - - while (string_len) { - size_t copy; - if (!buf->tail || !CHUNK_REMAINING_CAPACITY(buf->tail)) - buf_add_chunk_with_capacity(buf, string_len, 1); - - copy = CHUNK_REMAINING_CAPACITY(buf->tail); - if (copy > string_len) - copy = string_len; - memcpy(CHUNK_WRITE_PTR(buf->tail), string, copy); - string_len -= copy; - string += copy; - buf->datalen += copy; - buf->tail->datalen += copy; - } - - check(); - tor_assert(buf->datalen < INT_MAX); - return (int)buf->datalen; -} - -/** Helper: copy the first <b>string_len</b> bytes from <b>buf</b> - * onto <b>string</b>. - */ -static inline void -peek_from_buf(char *string, size_t string_len, const buf_t *buf) -{ - chunk_t *chunk; - - tor_assert(string); - /* make sure we don't ask for too much */ - tor_assert(string_len <= buf->datalen); - /* assert_buf_ok(buf); */ - - chunk = buf->head; - while (string_len) { - size_t copy = string_len; - tor_assert(chunk); - if (chunk->datalen < copy) - copy = chunk->datalen; - memcpy(string, chunk->data, copy); - string_len -= copy; - string += copy; - chunk = chunk->next; - } -} - -/** Remove <b>string_len</b> bytes from the front of <b>buf</b>, and store - * them into <b>string</b>. Return the new buffer size. <b>string_len</b> - * must be \<= the number of bytes on the buffer. - */ -int -fetch_from_buf(char *string, size_t string_len, buf_t *buf) -{ - /* There must be string_len bytes in buf; write them onto string, - * then memmove buf back (that is, remove them from buf). - * - * Return the number of bytes still on the buffer. */ - - check(); - peek_from_buf(string, string_len, buf); - buf_remove_from_front(buf, string_len); - check(); - tor_assert(buf->datalen < INT_MAX); - return (int)buf->datalen; -} - -/** True iff the cell command <b>command</b> is one that implies a - * variable-length cell in Tor link protocol <b>linkproto</b>. */ -static inline int -cell_command_is_var_length(uint8_t command, int linkproto) -{ - /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells - * work as implemented here. If it's 1, there are no variable-length cells. - * Tor does not support other versions right now, and so can't negotiate - * them. - */ - switch (linkproto) { - case 1: - /* Link protocol version 1 has no variable-length cells. */ - return 0; - case 2: - /* In link protocol version 2, VERSIONS is the only variable-length cell */ - return command == CELL_VERSIONS; - case 0: - case 3: - default: - /* In link protocol version 3 and later, and in version "unknown", - * commands 128 and higher indicate variable-length. VERSIONS is - * grandfathered in. */ - return command == CELL_VERSIONS || command >= 128; - } -} - -/** Check <b>buf</b> for a variable-length cell according to the rules of link - * protocol version <b>linkproto</b>. If one is found, pull it off the buffer - * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. - * Return 0 if whatever is on the start of buf_t is not a variable-length - * cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start - * of a variable-length cell on <b>buf</b>, but the whole thing isn't there - * yet. */ -int -fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) -{ - char hdr[VAR_CELL_MAX_HEADER_SIZE]; - var_cell_t *result; - uint8_t command; - uint16_t length; - const int wide_circ_ids = linkproto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; - const int circ_id_len = get_circ_id_size(wide_circ_ids); - const unsigned header_len = get_var_cell_header_size(wide_circ_ids); - check(); - *out = NULL; - if (buf->datalen < header_len) - return 0; - peek_from_buf(hdr, header_len, buf); - - command = get_uint8(hdr + circ_id_len); - if (!(cell_command_is_var_length(command, linkproto))) - return 0; - - length = ntohs(get_uint16(hdr + circ_id_len + 1)); - if (buf->datalen < (size_t)(header_len+length)) - return 1; - result = var_cell_new(length); - result->command = command; - if (wide_circ_ids) - result->circ_id = ntohl(get_uint32(hdr)); - else - result->circ_id = ntohs(get_uint16(hdr)); - - buf_remove_from_front(buf, header_len); - peek_from_buf((char*) result->payload, length, buf); - buf_remove_from_front(buf, length); - check(); - - *out = result; - return 1; -} - -/** Move up to *<b>buf_flushlen</b> bytes from <b>buf_in</b> to - * <b>buf_out</b>, and modify *<b>buf_flushlen</b> appropriately. - * Return the number of bytes actually copied. - */ -int -move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen) -{ - /* We can do way better here, but this doesn't turn up in any profiles. */ - char b[4096]; - size_t cp, len; - - if (BUG(buf_out->datalen >= INT_MAX)) - return -1; - if (BUG(buf_out->datalen >= INT_MAX - *buf_flushlen)) - return -1; - - len = *buf_flushlen; - if (len > buf_in->datalen) - len = buf_in->datalen; - - cp = len; /* Remember the number of bytes we intend to copy. */ - tor_assert(cp < INT_MAX); - while (len) { - /* This isn't the most efficient implementation one could imagine, since - * it does two copies instead of 1, but I kinda doubt that this will be - * critical path. */ - size_t n = len > sizeof(b) ? sizeof(b) : len; - fetch_from_buf(b, n, buf_in); - write_to_buf(b, n, buf_out); - len -= n; - } - *buf_flushlen -= cp; - return (int)cp; -} - -/** Internal structure: represents a position in a buffer. */ -typedef struct buf_pos_t { - const chunk_t *chunk; /**< Which chunk are we pointing to? */ - int pos;/**< Which character inside the chunk's data are we pointing to? */ - size_t chunk_pos; /**< Total length of all previous chunks. */ -} buf_pos_t; - -/** Initialize <b>out</b> to point to the first character of <b>buf</b>.*/ -static void -buf_pos_init(const buf_t *buf, buf_pos_t *out) -{ - out->chunk = buf->head; - out->pos = 0; - out->chunk_pos = 0; -} - -/** Advance <b>out</b> to the first appearance of <b>ch</b> at the current - * position of <b>out</b>, or later. Return -1 if no instances are found; - * otherwise returns the absolute position of the character. */ -static off_t -buf_find_pos_of_char(char ch, buf_pos_t *out) -{ - const chunk_t *chunk; - int pos; - tor_assert(out); - if (out->chunk) { - if (out->chunk->datalen) { - tor_assert(out->pos < (off_t)out->chunk->datalen); - } else { - tor_assert(out->pos == 0); - } - } - pos = out->pos; - for (chunk = out->chunk; chunk; chunk = chunk->next) { - char *cp = memchr(chunk->data+pos, ch, chunk->datalen - pos); - if (cp) { - out->chunk = chunk; - tor_assert(cp - chunk->data < INT_MAX); - out->pos = (int)(cp - chunk->data); - return out->chunk_pos + out->pos; - } else { - out->chunk_pos += chunk->datalen; - pos = 0; - } - } - return -1; -} - -/** Advance <b>pos</b> by a single character, if there are any more characters - * in the buffer. Returns 0 on success, -1 on failure. */ -static inline int -buf_pos_inc(buf_pos_t *pos) -{ - ++pos->pos; - if (pos->pos == (off_t)pos->chunk->datalen) { - if (!pos->chunk->next) - return -1; - pos->chunk_pos += pos->chunk->datalen; - pos->chunk = pos->chunk->next; - pos->pos = 0; - } - return 0; -} - -/** Return true iff the <b>n</b>-character string in <b>s</b> appears - * (verbatim) at <b>pos</b>. */ -static int -buf_matches_at_pos(const buf_pos_t *pos, const char *s, size_t n) -{ - buf_pos_t p; - if (!n) - return 1; - - memcpy(&p, pos, sizeof(p)); - - while (1) { - char ch = p.chunk->data[p.pos]; - if (ch != *s) - return 0; - ++s; - /* If we're out of characters that don't match, we match. Check this - * _before_ we test incrementing pos, in case we're at the end of the - * string. */ - if (--n == 0) - return 1; - if (buf_pos_inc(&p)<0) - return 0; - } -} - -/** Return the first position in <b>buf</b> at which the <b>n</b>-character - * string <b>s</b> occurs, or -1 if it does not occur. */ -STATIC int -buf_find_string_offset(const buf_t *buf, const char *s, size_t n) -{ - buf_pos_t pos; - buf_pos_init(buf, &pos); - while (buf_find_pos_of_char(*s, &pos) >= 0) { - if (buf_matches_at_pos(&pos, s, n)) { - tor_assert(pos.chunk_pos + pos.pos < INT_MAX); - return (int)(pos.chunk_pos + pos.pos); - } else { - if (buf_pos_inc(&pos)<0) - return -1; - } - } - return -1; -} - -/** - * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at - * <b>headers</b>, looking for a "Content-Length" header. Try to set - * *<b>result_out</b> to the numeric value of that header if possible. - * Return -1 if the header was malformed, 0 if it was missing, and 1 if - * it was present and well-formed. - */ -STATIC int -buf_http_find_content_length(const char *headers, size_t headerlen, - size_t *result_out) -{ - const char *p, *newline; - char *len_str, *eos=NULL; - size_t remaining, result; - int ok; - *result_out = 0; /* The caller shouldn't look at this unless the - * return value is 1, but let's prevent confusion */ - -#define CONTENT_LENGTH "\r\nContent-Length: " - p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH); - if (p == NULL) - return 0; - - tor_assert(p >= headers && p < headers+headerlen); - remaining = (headers+headerlen)-p; - p += strlen(CONTENT_LENGTH); - remaining -= strlen(CONTENT_LENGTH); - - newline = memchr(p, '\n', remaining); - if (newline == NULL) - return -1; - - len_str = tor_memdup_nulterm(p, newline-p); - /* We limit the size to INT_MAX because other parts of the buffer.c - * code don't like buffers to be any bigger than that. */ - result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos); - if (eos && !tor_strisspace(eos)) { - ok = 0; - } else { - *result_out = result; - } - tor_free(len_str); - - return ok ? 1 : -1; -} - -/** There is a (possibly incomplete) http statement on <b>buf</b>, of the - * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) - * If a) the headers include a Content-Length field and all bytes in - * the body are present, or b) there's no Content-Length field and - * all headers are present, then: - * - * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. - * - memdup body into <b>*body_out</b>, and NUL-terminate it. - * - Then remove them from <b>buf</b>, and return 1. - * - * - If headers or body is NULL, discard that part of the buf. - * - If a headers or body doesn't fit in the arg, return -1. - * (We ensure that the headers or body don't exceed max len, - * _even if_ we're planning to discard them.) - * - If force_complete is true, then succeed even if not all of the - * content has arrived. - * - * Else, change nothing and return 0. - */ -int -fetch_from_buf_http(buf_t *buf, - char **headers_out, size_t max_headerlen, - char **body_out, size_t *body_used, size_t max_bodylen, - int force_complete) -{ - char *headers; - size_t headerlen, bodylen, contentlen=0; - int crlf_offset; - int r; - - check(); - if (!buf->head) - return 0; - - crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); - if (crlf_offset > (int)max_headerlen || - (crlf_offset < 0 && buf->datalen > max_headerlen)) { - log_debug(LD_HTTP,"headers too long."); - return -1; - } else if (crlf_offset < 0) { - log_debug(LD_HTTP,"headers not all here yet."); - return 0; - } - /* Okay, we have a full header. Make sure it all appears in the first - * chunk. */ - if ((int)buf->head->datalen < crlf_offset + 4) - buf_pullup(buf, crlf_offset+4); - headerlen = crlf_offset + 4; - - headers = buf->head->data; - bodylen = buf->datalen - headerlen; - log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); - - if (max_headerlen <= headerlen) { - log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", - (int)headerlen, (int)max_headerlen-1); - return -1; - } - if (max_bodylen <= bodylen) { - log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", - (int)bodylen, (int)max_bodylen-1); - return -1; - } - - r = buf_http_find_content_length(headers, headerlen, &contentlen); - if (r == -1) { - log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe " - "someone is trying to crash us."); - return -1; - } else if (r == 1) { - /* if content-length is malformed, then our body length is 0. fine. */ - log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); - if (bodylen < contentlen) { - if (!force_complete) { - log_debug(LD_HTTP,"body not all here yet."); - return 0; /* not all there yet */ - } - } - if (bodylen > contentlen) { - bodylen = contentlen; - log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); - } - } else { - tor_assert(r == 0); - /* Leave bodylen alone */ - } - - /* all happy. copy into the appropriate places, and return 1 */ - if (headers_out) { - *headers_out = tor_malloc(headerlen+1); - fetch_from_buf(*headers_out, headerlen, buf); - (*headers_out)[headerlen] = 0; /* NUL terminate it */ - } - if (body_out) { - tor_assert(body_used); - *body_used = bodylen; - *body_out = tor_malloc(bodylen+1); - fetch_from_buf(*body_out, bodylen, buf); - (*body_out)[bodylen] = 0; /* NUL terminate it */ - } - check(); - return 1; -} - -/** - * Wait this many seconds before warning the user about using SOCKS unsafely - * again. */ -#define SOCKS_WARN_INTERVAL 5 - -/** Warn that the user application has made an unsafe socks request using - * protocol <b>socks_protocol</b> on port <b>port</b>. Don't warn more than - * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */ -static void -log_unsafe_socks_warning(int socks_protocol, const char *address, - uint16_t port, int safe_socks) -{ - static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); - - if (safe_socks) { - log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP, - "Your application (using socks%d to port %d) is giving " - "Tor only an IP address. Applications that do DNS resolves " - "themselves may leak information. Consider using Socks4A " - "(e.g. via privoxy or socat) instead. For more information, " - "please see https://wiki.torproject.org/TheOnionRouter/" - "TorFAQ#SOCKSAndDNS.%s", - socks_protocol, - (int)port, - safe_socks ? " Rejecting." : ""); - } - control_event_client_status(LOG_WARN, - "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d", - socks_protocol, address, (int)port); -} - -/** Do not attempt to parse socks messages longer than this. This value is - * actually significantly higher than the longest possible socks message. */ -#define MAX_SOCKS_MESSAGE_LEN 512 - -/** Return a new socks_request_t. */ -socks_request_t * -socks_request_new(void) -{ - return tor_malloc_zero(sizeof(socks_request_t)); -} - -/** Free all storage held in the socks_request_t <b>req</b>. */ -void -socks_request_free(socks_request_t *req) -{ - if (!req) - return; - if (req->username) { - memwipe(req->username, 0x10, req->usernamelen); - tor_free(req->username); - } - if (req->password) { - memwipe(req->password, 0x04, req->passwordlen); - tor_free(req->password); - } - memwipe(req, 0xCC, sizeof(socks_request_t)); - tor_free(req); -} - -/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one - * of the forms - * - socks4: "socksheader username\\0" - * - socks4a: "socksheader username\\0 destaddr\\0" - * - socks5 phase one: "version #methods methods" - * - socks5 phase two: "version command 0 addresstype..." - * If it's a complete and valid handshake, and destaddr fits in - * MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf, - * assign to <b>req</b>, and return 1. - * - * If it's invalid or too big, return -1. - * - * Else it's not all there yet, leave buf alone and return 0. - * - * If you want to specify the socks reply, write it into <b>req->reply</b> - * and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone. - * - * If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether - * the connection is possibly leaking DNS requests locally or not. - * - * If <b>safe_socks</b> is true, then reject unsafe socks protocols. - * - * If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are - * undefined. - */ -int -fetch_from_buf_socks(buf_t *buf, socks_request_t *req, - int log_sockstype, int safe_socks) -{ - int res; - ssize_t n_drain; - size_t want_length = 128; - - if (buf->datalen < 2) /* version and another byte */ - return 0; - - do { - n_drain = 0; - buf_pullup(buf, want_length); - tor_assert(buf->head && buf->head->datalen >= 2); - want_length = 0; - - res = parse_socks(buf->head->data, buf->head->datalen, req, log_sockstype, - safe_socks, &n_drain, &want_length); - - if (n_drain < 0) - buf_clear(buf); - else if (n_drain > 0) - buf_remove_from_front(buf, n_drain); - - } while (res == 0 && buf->head && want_length < buf->datalen && - buf->datalen >= 2); - - return res; -} - -/** The size of the header of an Extended ORPort message: 2 bytes for - * COMMAND, 2 bytes for BODYLEN */ -#define EXT_OR_CMD_HEADER_SIZE 4 - -/** Read <b>buf</b>, which should contain an Extended ORPort message - * from a transport proxy. If well-formed, create and populate - * <b>out</b> with the Extended ORport message. Return 0 if the - * buffer was incomplete, 1 if it was well-formed and -1 if we - * encountered an error while parsing it. */ -int -fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out) -{ - char hdr[EXT_OR_CMD_HEADER_SIZE]; - uint16_t len; - - check(); - if (buf->datalen < EXT_OR_CMD_HEADER_SIZE) - return 0; - peek_from_buf(hdr, sizeof(hdr), buf); - len = ntohs(get_uint16(hdr+2)); - if (buf->datalen < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) - return 0; - *out = ext_or_cmd_new(len); - (*out)->cmd = ntohs(get_uint16(hdr)); - (*out)->len = len; - buf_remove_from_front(buf, EXT_OR_CMD_HEADER_SIZE); - fetch_from_buf((*out)->body, len, buf); - return 1; -} - -/** Create a SOCKS5 reply message with <b>reason</b> in its REP field and - * have Tor send it as error response to <b>req</b>. - */ -static void -socks_request_set_socks5_error(socks_request_t *req, - socks5_reply_status_t reason) -{ - req->replylen = 10; - memset(req->reply,0,10); - - req->reply[0] = 0x05; // VER field. - req->reply[1] = reason; // REP field. - req->reply[3] = 0x01; // ATYP field. -} - -static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] = - "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" - "Content-Type: text/html; charset=iso-8859-1\r\n\r\n" - "<html>\n" - "<head>\n" - "<title>Tor is not an HTTP Proxy</title>\n" - "</head>\n" - "<body>\n" - "<h1>Tor is not an HTTP Proxy</h1>\n" - "<p>\n" - "It appears you have configured your web browser to use Tor as " - "an HTTP proxy.\n\n" - "This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n" - "Please configure your client accordingly.\n" - "</p>\n" - "<p>\n" - "See <a href=\"https://www.torproject.org/documentation.html\">" - "https://www.torproject.org/documentation.html</a> for more " - "information.\n" - "<!-- Plus this comment, to make the body response more than 512 bytes, so " - " IE will be willing to display it. Comment comment comment comment " - " comment comment comment comment comment comment comment comment.-->\n" - "</p>\n" - "</body>\n" - "</html>\n"; - -/** Implementation helper to implement fetch_from_*_socks. Instead of looking - * at a buffer's contents, we look at the <b>datalen</b> bytes of data in - * <b>data</b>. Instead of removing data from the buffer, we set - * <b>drain_out</b> to the amount of data that should be removed (or -1 if the - * buffer should be cleared). Instead of pulling more data into the first - * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes - * we'd like to see in the input buffer, if they're available. */ -static int -parse_socks(const char *data, size_t datalen, socks_request_t *req, - int log_sockstype, int safe_socks, ssize_t *drain_out, - size_t *want_length_out) -{ - unsigned int len; - char tmpbuf[TOR_ADDR_BUF_LEN+1]; - tor_addr_t destaddr; - uint32_t destip; - uint8_t socksver; - char *next, *startaddr; - unsigned char usernamelen, passlen; - struct in_addr in; - - if (datalen < 2) { - /* We always need at least 2 bytes. */ - *want_length_out = 2; - return 0; - } - - if (req->socks_version == 5 && !req->got_auth) { - /* See if we have received authentication. Strictly speaking, we should - also check whether we actually negotiated username/password - authentication. But some broken clients will send us authentication - even if we negotiated SOCKS_NO_AUTH. */ - if (*data == 1) { /* username/pass version 1 */ - /* Format is: authversion [1 byte] == 1 - usernamelen [1 byte] - username [usernamelen bytes] - passlen [1 byte] - password [passlen bytes] */ - usernamelen = (unsigned char)*(data + 1); - if (datalen < 2u + usernamelen + 1u) { - *want_length_out = 2u + usernamelen + 1u; - return 0; - } - passlen = (unsigned char)*(data + 2u + usernamelen); - if (datalen < 2u + usernamelen + 1u + passlen) { - *want_length_out = 2u + usernamelen + 1u + passlen; - return 0; - } - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 1; /* authversion == 1 */ - req->reply[1] = 0; /* authentication successful */ - log_debug(LD_APP, - "socks5: Accepted username/password without checking."); - if (usernamelen) { - req->username = tor_memdup(data+2u, usernamelen); - req->usernamelen = usernamelen; - } - if (passlen) { - req->password = tor_memdup(data+3u+usernamelen, passlen); - req->passwordlen = passlen; - } - *drain_out = 2u + usernamelen + 1u + passlen; - req->got_auth = 1; - *want_length_out = 7; /* Minimal socks5 command. */ - return 0; - } else if (req->auth_type == SOCKS_USER_PASS) { - /* unknown version byte */ - log_warn(LD_APP, "Socks5 username/password version %d not recognized; " - "rejecting.", (int)*data); - return -1; - } - } - - socksver = *data; - - switch (socksver) { /* which version of socks? */ - case 5: /* socks5 */ - - if (req->socks_version != 5) { /* we need to negotiate a method */ - unsigned char nummethods = (unsigned char)*(data+1); - int have_user_pass, have_no_auth; - int r=0; - tor_assert(!req->socks_version); - if (datalen < 2u+nummethods) { - *want_length_out = 2u+nummethods; - return 0; - } - if (!nummethods) - return -1; - req->replylen = 2; /* 2 bytes of response */ - req->reply[0] = 5; /* socks5 reply */ - have_user_pass = (memchr(data+2, SOCKS_USER_PASS, nummethods) !=NULL); - have_no_auth = (memchr(data+2, SOCKS_NO_AUTH, nummethods) !=NULL); - if (have_user_pass && !(have_no_auth && req->socks_prefer_no_auth)) { - req->auth_type = SOCKS_USER_PASS; - req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" - auth method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); - r=0; - } else if (have_no_auth) { - req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth - method */ - req->socks_version = 5; /* remember we've already negotiated auth */ - log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); - r=0; - } else { - log_warn(LD_APP, - "socks5: offered methods don't include 'no auth' or " - "username/password. Rejecting."); - req->reply[1] = '\xFF'; /* reject all methods */ - r=-1; - } - /* Remove packet from buf. Some SOCKS clients will have sent extra - * junk at this point; let's hope it's an authentication message. */ - *drain_out = 2u + nummethods; - - return r; - } - if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { - log_warn(LD_APP, - "socks5: negotiated authentication, but none provided"); - return -1; - } - /* we know the method; read in the request */ - log_debug(LD_APP,"socks5: checking request"); - if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ - *want_length_out = 7; - return 0; /* not yet */ - } - req->command = (unsigned char) *(data+1); - if (req->command != SOCKS_COMMAND_CONNECT && - req->command != SOCKS_COMMAND_RESOLVE && - req->command != SOCKS_COMMAND_RESOLVE_PTR) { - /* not a connect or resolve or a resolve_ptr? we don't support it. */ - socks_request_set_socks5_error(req,SOCKS5_COMMAND_NOT_SUPPORTED); - - log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", - req->command); - return -1; - } - switch (*(data+3)) { /* address type */ - case 1: /* IPv4 address */ - case 4: /* IPv6 address */ { - const int is_v6 = *(data+3) == 4; - const unsigned addrlen = is_v6 ? 16 : 4; - log_debug(LD_APP,"socks5: ipv4 address type"); - if (datalen < 6+addrlen) {/* ip/port there? */ - *want_length_out = 6+addrlen; - return 0; /* not yet */ - } - - if (is_v6) - tor_addr_from_ipv6_bytes(&destaddr, data+4); - else - tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); - - tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); - - if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - log_warn(LD_APP, - "socks5 IP takes %d bytes, which doesn't fit in %d. " - "Rejecting.", - (int)strlen(tmpbuf)+1,(int)MAX_SOCKS_ADDR_LEN); - return -1; - } - strlcpy(req->address,tmpbuf,sizeof(req->address)); - req->port = ntohs(get_uint16(data+4+addrlen)); - *drain_out = 6+addrlen; - if (req->command != SOCKS_COMMAND_RESOLVE_PTR && - !addressmap_have_mapping(req->address,0)) { - log_unsafe_socks_warning(5, req->address, req->port, safe_socks); - if (safe_socks) { - socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); - return -1; - } - } - return 1; - } - case 3: /* fqdn */ - log_debug(LD_APP,"socks5: fqdn address type"); - if (req->command == SOCKS_COMMAND_RESOLVE_PTR) { - socks_request_set_socks5_error(req, - SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); - log_warn(LD_APP, "socks5 received RESOLVE_PTR command with " - "hostname type. Rejecting."); - return -1; - } - len = (unsigned char)*(data+4); - if (datalen < 7+len) { /* addr/port there? */ - *want_length_out = 7+len; - return 0; /* not yet */ - } - if (len+1 > MAX_SOCKS_ADDR_LEN) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - log_warn(LD_APP, - "socks5 hostname is %d bytes, which doesn't fit in " - "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); - return -1; - } - memcpy(req->address,data+5,len); - req->address[len] = 0; - req->port = ntohs(get_uint16(data+5+len)); - *drain_out = 5+len+2; - - if (!string_is_valid_hostname(req->address)) { - socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); - - log_warn(LD_PROTOCOL, - "Your application (using socks5 to port %d) gave Tor " - "a malformed hostname: %s. Rejecting the connection.", - req->port, escaped_safe_str_client(req->address)); - return -1; - } - if (log_sockstype) - log_notice(LD_APP, - "Your application (using socks5 to port %d) instructed " - "Tor to take care of the DNS resolution itself if " - "necessary. This is good.", req->port); - return 1; - default: /* unsupported */ - socks_request_set_socks5_error(req, - SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); - log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", - (int) *(data+3)); - return -1; - } - tor_assert(0); - break; - case 4: { /* socks4 */ - enum {socks4, socks4a} socks4_prot = socks4a; - const char *authstart, *authend; - /* http://ss5.sourceforge.net/socks4.protocol.txt */ - /* http://ss5.sourceforge.net/socks4A.protocol.txt */ - - req->socks_version = 4; - if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ - *want_length_out = SOCKS4_NETWORK_LEN; - return 0; /* not yet */ - } - // buf_pullup(buf, 1280); - req->command = (unsigned char) *(data+1); - if (req->command != SOCKS_COMMAND_CONNECT && - req->command != SOCKS_COMMAND_RESOLVE) { - /* not a connect or resolve? we don't support it. (No resolve_ptr with - * socks4.) */ - log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", - req->command); - return -1; - } - - req->port = ntohs(get_uint16(data+2)); - destip = ntohl(get_uint32(data+4)); - if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { - log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); - return -1; - } - if (destip >> 8) { - log_debug(LD_APP,"socks4: destip not in form 0.0.0.x."); - in.s_addr = htonl(destip); - tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); - if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { - log_debug(LD_APP,"socks4 addr (%d bytes) too long. Rejecting.", - (int)strlen(tmpbuf)); - return -1; - } - log_debug(LD_APP, - "socks4: successfully read destip (%s)", - safe_str_client(tmpbuf)); - socks4_prot = socks4; - } - - authstart = data + SOCKS4_NETWORK_LEN; - next = memchr(authstart, 0, - datalen-SOCKS4_NETWORK_LEN); - if (!next) { - if (datalen >= 1024) { - log_debug(LD_APP, "Socks4 user name too long; rejecting."); - return -1; - } - log_debug(LD_APP,"socks4: Username not here yet."); - *want_length_out = datalen+1024; /* More than we need, but safe */ - return 0; - } - authend = next; - tor_assert(next < data+datalen); - - startaddr = NULL; - if (socks4_prot != socks4a && - !addressmap_have_mapping(tmpbuf,0)) { - log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks); - - if (safe_socks) - return -1; - } - if (socks4_prot == socks4a) { - if (next+1 == data+datalen) { - log_debug(LD_APP,"socks4: No part of destaddr here yet."); - *want_length_out = datalen + 1024; /* More than we need, but safe */ - return 0; - } - startaddr = next+1; - next = memchr(startaddr, 0, data + datalen - startaddr); - if (!next) { - if (datalen >= 1024) { - log_debug(LD_APP,"socks4: Destaddr too long."); - return -1; - } - log_debug(LD_APP,"socks4: Destaddr not all here yet."); - *want_length_out = datalen + 1024; /* More than we need, but safe */ - return 0; - } - if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { - log_warn(LD_APP,"socks4: Destaddr too long. Rejecting."); - return -1; - } - // tor_assert(next < buf->cur+buf->datalen); - - if (log_sockstype) - log_notice(LD_APP, - "Your application (using socks4a to port %d) instructed " - "Tor to take care of the DNS resolution itself if " - "necessary. This is good.", req->port); - } - log_debug(LD_APP,"socks4: Everything is here. Success."); - strlcpy(req->address, startaddr ? startaddr : tmpbuf, - sizeof(req->address)); - if (!string_is_valid_hostname(req->address)) { - log_warn(LD_PROTOCOL, - "Your application (using socks4 to port %d) gave Tor " - "a malformed hostname: %s. Rejecting the connection.", - req->port, escaped_safe_str_client(req->address)); - return -1; - } - if (authend != authstart) { - req->got_auth = 1; - req->usernamelen = authend - authstart; - req->username = tor_memdup(authstart, authend - authstart); - } - /* next points to the final \0 on inbuf */ - *drain_out = next - data + 1; - return 1; - } - case 'G': /* get */ - case 'H': /* head */ - case 'P': /* put/post */ - case 'C': /* connect */ - strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG, - MAX_SOCKS_REPLY_LEN); - req->replylen = strlen((char*)req->reply)+1; - /* fall through */ - default: /* version is not socks4 or socks5 */ - log_warn(LD_APP, - "Socks version %d not recognized. (Tor is not an http proxy.)", - *(data)); - { - /* Tell the controller the first 8 bytes. */ - char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); - control_event_client_status(LOG_WARN, - "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", - escaped(tmp)); - tor_free(tmp); - } - return -1; - } -} - -/** Inspect a reply from SOCKS server stored in <b>buf</b> according - * to <b>state</b>, removing the protocol data upon success. Return 0 on - * incomplete response, 1 on success and -1 on error, in which case - * <b>reason</b> is set to a descriptive message (free() when finished - * with it). - * - * As a special case, 2 is returned when user/pass is required - * during SOCKS5 handshake and user/pass is configured. - */ -int -fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) -{ - ssize_t drain = 0; - int r; - if (buf->datalen < 2) - return 0; - - buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN); - tor_assert(buf->head && buf->head->datalen >= 2); - - r = parse_socks_client((uint8_t*)buf->head->data, buf->head->datalen, - state, reason, &drain); - if (drain > 0) - buf_remove_from_front(buf, drain); - else if (drain < 0) - buf_clear(buf); - - return r; -} - -/** Implementation logic for fetch_from_*_socks_client. */ -static int -parse_socks_client(const uint8_t *data, size_t datalen, - int state, char **reason, - ssize_t *drain_out) -{ - unsigned int addrlen; - *drain_out = 0; - if (datalen < 2) - return 0; - - switch (state) { - case PROXY_SOCKS4_WANT_CONNECT_OK: - /* Wait for the complete response */ - if (datalen < 8) - return 0; - - if (data[1] != 0x5a) { - *reason = tor_strdup(socks4_response_code_to_string(data[1])); - return -1; - } - - /* Success */ - *drain_out = 8; - return 1; - - case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: - /* we don't have any credentials */ - if (data[1] != 0x00) { - *reason = tor_strdup("server doesn't support any of our " - "available authentication methods"); - return -1; - } - - log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); - *drain_out = -1; - return 1; - - case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: - /* we have a username and password. return 1 if we can proceed without - * providing authentication, or 2 otherwise. */ - switch (data[1]) { - case 0x00: - log_info(LD_NET, "SOCKS 5 client: we have auth details but server " - "doesn't require authentication."); - *drain_out = -1; - return 1; - case 0x02: - log_info(LD_NET, "SOCKS 5 client: need authentication."); - *drain_out = -1; - return 2; - /* fall through */ - } - - *reason = tor_strdup("server doesn't support any of our available " - "authentication methods"); - return -1; - - case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: - /* handle server reply to rfc1929 authentication */ - if (data[1] != 0x00) { - *reason = tor_strdup("authentication failed"); - return -1; - } - - log_info(LD_NET, "SOCKS 5 client: authentication successful."); - *drain_out = -1; - return 1; - - case PROXY_SOCKS5_WANT_CONNECT_OK: - /* response is variable length. BND.ADDR, etc, isn't needed - * (don't bother with buf_pullup()), but make sure to eat all - * the data used */ - - /* wait for address type field to arrive */ - if (datalen < 4) - return 0; - - switch (data[3]) { - case 0x01: /* ip4 */ - addrlen = 4; - break; - case 0x04: /* ip6 */ - addrlen = 16; - break; - case 0x03: /* fqdn (can this happen here?) */ - if (datalen < 5) - return 0; - addrlen = 1 + data[4]; - break; - default: - *reason = tor_strdup("invalid response to connect request"); - return -1; - } - - /* wait for address and port */ - if (datalen < 6 + addrlen) - return 0; - - if (data[1] != 0x00) { - *reason = tor_strdup(socks5_response_code_to_string(data[1])); - return -1; - } - - *drain_out = 6 + addrlen; - return 1; - } - - /* shouldn't get here... */ - tor_assert(0); - - return -1; -} - -/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */ -int -peek_buf_has_http_command(const buf_t *buf) -{ - if (peek_buf_startswith(buf, "CONNECT ") || - peek_buf_startswith(buf, "DELETE ") || - peek_buf_startswith(buf, "GET ") || - peek_buf_startswith(buf, "POST ") || - peek_buf_startswith(buf, "PUT " )) - return 1; - return 0; -} - -/** Return 1 iff <b>buf</b> starts with <b>cmd</b>. <b>cmd</b> must be a null - * terminated string, of no more than PEEK_BUF_STARTSWITH_MAX bytes. */ -int -peek_buf_startswith(const buf_t *buf, const char *cmd) -{ - char tmp[PEEK_BUF_STARTSWITH_MAX]; - size_t clen = strlen(cmd); - if (BUG(clen > sizeof(tmp))) - return 0; - if (buf->datalen < clen) - return 0; - peek_from_buf(tmp, clen, buf); - return fast_memeq(tmp, cmd, clen); -} - -/** Return 1 iff buf looks more like it has an (obsolete) v0 controller - * command on it than any valid v1 controller command. */ -int -peek_buf_has_control0_command(buf_t *buf) -{ - if (buf->datalen >= 4) { - char header[4]; - uint16_t cmd; - peek_from_buf(header, sizeof(header), buf); - cmd = ntohs(get_uint16(header+2)); - if (cmd <= 0x14) - return 1; /* This is definitely not a v1 control command. */ - } - return 0; -} - -/** Return the index within <b>buf</b> at which <b>ch</b> first appears, - * or -1 if <b>ch</b> does not appear on buf. */ -static off_t -buf_find_offset_of_char(buf_t *buf, char ch) -{ - chunk_t *chunk; - off_t offset = 0; - for (chunk = buf->head; chunk; chunk = chunk->next) { - char *cp = memchr(chunk->data, ch, chunk->datalen); - if (cp) - return offset + (cp - chunk->data); - else - offset += chunk->datalen; - } - return -1; -} - -/** Try to read a single LF-terminated line from <b>buf</b>, and write it - * (including the LF), NUL-terminated, into the *<b>data_len</b> byte buffer - * at <b>data_out</b>. Set *<b>data_len</b> to the number of bytes in the - * line, not counting the terminating NUL. Return 1 if we read a whole line, - * return 0 if we don't have a whole line yet, and return -1 if the line - * length exceeds *<b>data_len</b>. - */ -int -fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len) -{ - size_t sz; - off_t offset; - - if (!buf->head) - return 0; - - offset = buf_find_offset_of_char(buf, '\n'); - if (offset < 0) - return 0; - sz = (size_t) offset; - if (sz+2 > *data_len) { - *data_len = sz + 2; - return -1; - } - fetch_from_buf(data_out, sz+1, buf); - data_out[sz+1] = '\0'; - *data_len = sz+1; - return 1; -} - -/** Compress on uncompress the <b>data_len</b> bytes in <b>data</b> using the - * compression state <b>state</b>, appending the result to <b>buf</b>. If - * <b>done</b> is true, flush the data in the state and finish the - * compression/uncompression. Return -1 on failure, 0 on success. */ -int -write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, - const char *data, size_t data_len, - const int done) -{ - char *next; - size_t old_avail, avail; - int over = 0; - - do { - int need_new_chunk = 0; - if (!buf->tail || ! CHUNK_REMAINING_CAPACITY(buf->tail)) { - size_t cap = data_len / 4; - buf_add_chunk_with_capacity(buf, cap, 1); - } - next = CHUNK_WRITE_PTR(buf->tail); - avail = old_avail = CHUNK_REMAINING_CAPACITY(buf->tail); - switch (tor_compress_process(state, &next, &avail, - &data, &data_len, done)) { - case TOR_COMPRESS_DONE: - over = 1; - break; - case TOR_COMPRESS_ERROR: - return -1; - case TOR_COMPRESS_OK: - if (data_len == 0) { - tor_assert_nonfatal(!done); - over = 1; - } - break; - case TOR_COMPRESS_BUFFER_FULL: - if (avail) { - /* The compression module says we need more room - * (TOR_COMPRESS_BUFFER_FULL). Start a new chunk automatically, - * whether were going to or not. */ - need_new_chunk = 1; - } - if (data_len == 0 && !done) { - /* We've consumed all the input data, though, so there's no - * point in forging ahead right now. */ - over = 1; - } - break; - } - buf->datalen += old_avail - avail; - buf->tail->datalen += old_avail - avail; - if (need_new_chunk) { - buf_add_chunk_with_capacity(buf, data_len/4, 1); - } - - } while (!over); - check(); - return 0; -} - -/** Set *<b>output</b> to contain a copy of the data in *<b>input</b> */ -int -buf_set_to_copy(buf_t **output, - const buf_t *input) -{ - if (*output) - buf_free(*output); - *output = buf_copy(input); - return 0; -} - -/** Log an error and exit if <b>buf</b> is corrupted. - */ -void -assert_buf_ok(buf_t *buf) -{ - tor_assert(buf); - tor_assert(buf->magic == BUFFER_MAGIC); - - if (! buf->head) { - tor_assert(!buf->tail); - tor_assert(buf->datalen == 0); - } else { - chunk_t *ch; - size_t total = 0; - tor_assert(buf->tail); - for (ch = buf->head; ch; ch = ch->next) { - total += ch->datalen; - tor_assert(ch->datalen <= ch->memlen); - tor_assert(ch->data >= &ch->mem[0]); - tor_assert(ch->data <= &ch->mem[0]+ch->memlen); - if (ch->data == &ch->mem[0]+ch->memlen) { - static int warned = 0; - if (! warned) { - log_warn(LD_BUG, "Invariant violation in buf.c related to #15083"); - warned = 1; - } - } - tor_assert(ch->data+ch->datalen <= &ch->mem[0] + ch->memlen); - if (!ch->next) - tor_assert(ch == buf->tail); - } - tor_assert(buf->datalen == total); - } -} - diff --git a/src/or/buffers.h b/src/or/buffers.h deleted file mode 100644 index d884084385..0000000000 --- a/src/or/buffers.h +++ /dev/null @@ -1,112 +0,0 @@ -/* Copyright (c) 2001 Matej Pfajfar. - * Copyright (c) 2001-2004, Roger Dingledine. - * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2017, The Tor Project, Inc. */ -/* See LICENSE for licensing information */ - -/** - * \file buffers.h - * \brief Header file for buffers.c. - **/ - -#ifndef TOR_BUFFERS_H -#define TOR_BUFFERS_H - -#include "testsupport.h" - -buf_t *buf_new(void); -buf_t *buf_new_with_capacity(size_t size); -size_t buf_get_default_chunk_size(const buf_t *buf); -void buf_free(buf_t *buf); -void buf_clear(buf_t *buf); -buf_t *buf_copy(const buf_t *buf); - -MOCK_DECL(size_t, buf_datalen, (const buf_t *buf)); -size_t buf_allocation(const buf_t *buf); -size_t buf_slack(const buf_t *buf); - -uint32_t buf_get_oldest_chunk_timestamp(const buf_t *buf, uint32_t now); -size_t buf_get_total_allocation(void); - -int read_to_buf(tor_socket_t s, size_t at_most, buf_t *buf, int *reached_eof, - int *socket_error); -int read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf); - -int flush_buf(tor_socket_t s, buf_t *buf, size_t sz, size_t *buf_flushlen); -int flush_buf_tls(tor_tls_t *tls, buf_t *buf, size_t sz, size_t *buf_flushlen); - -int write_to_buf(const char *string, size_t string_len, buf_t *buf); -int write_to_buf_compress(buf_t *buf, tor_compress_state_t *state, - const char *data, size_t data_len, int done); -int move_buf_to_buf(buf_t *buf_out, buf_t *buf_in, size_t *buf_flushlen); -int fetch_from_buf(char *string, size_t string_len, buf_t *buf); -int fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto); -int fetch_from_buf_http(buf_t *buf, - char **headers_out, size_t max_headerlen, - char **body_out, size_t *body_used, size_t max_bodylen, - int force_complete); -socks_request_t *socks_request_new(void); -void socks_request_free(socks_request_t *req); -int fetch_from_buf_socks(buf_t *buf, socks_request_t *req, - int log_sockstype, int safe_socks); -int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); -int fetch_from_buf_line(buf_t *buf, char *data_out, size_t *data_len); - -int peek_buf_has_control0_command(buf_t *buf); -#define PEEK_BUF_STARTSWITH_MAX 16 -int peek_buf_startswith(const buf_t *buf, const char *cmd); -int peek_buf_has_http_command(const buf_t *buf); - -int fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out); - -int buf_set_to_copy(buf_t **output, - const buf_t *input); - -void assert_buf_ok(buf_t *buf); - -#ifdef BUFFERS_PRIVATE -STATIC int buf_find_string_offset(const buf_t *buf, const char *s, size_t n); -STATIC void buf_pullup(buf_t *buf, size_t bytes); -#ifdef TOR_UNIT_TESTS -void buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz); -buf_t *buf_new_with_data(const char *cp, size_t sz); -#endif -STATIC size_t preferred_chunk_size(size_t target); - -#define DEBUG_CHUNK_ALLOC -/** A single chunk on a buffer. */ -typedef struct chunk_t { - struct chunk_t *next; /**< The next chunk on the buffer. */ - size_t datalen; /**< The number of bytes stored in this chunk */ - size_t memlen; /**< The number of usable bytes of storage in <b>mem</b>. */ -#ifdef DEBUG_CHUNK_ALLOC - size_t DBG_alloc; -#endif - char *data; /**< A pointer to the first byte of data stored in <b>mem</b>. */ - uint32_t inserted_time; /**< Timestamp in truncated ms since epoch - * when this chunk was inserted. */ - char mem[FLEXIBLE_ARRAY_MEMBER]; /**< The actual memory used for storage in - * this chunk. */ -} chunk_t; - -/** Magic value for buf_t.magic, to catch pointer errors. */ -#define BUFFER_MAGIC 0xB0FFF312u -/** A resizeable buffer, optimized for reading and writing. */ -struct buf_t { - uint32_t magic; /**< Magic cookie for debugging: Must be set to - * BUFFER_MAGIC. */ - size_t datalen; /**< How many bytes is this buffer holding right now? */ - size_t default_chunk_size; /**< Don't allocate any chunks smaller than - * this for this buffer. */ - chunk_t *head; /**< First chunk in the list, or NULL for none. */ - chunk_t *tail; /**< Last chunk in the list, or NULL for none. */ -}; -#endif - -#ifdef BUFFERS_PRIVATE -STATIC int buf_http_find_content_length(const char *headers, size_t headerlen, - size_t *result_out); -#endif - -#endif - diff --git a/src/or/channel.c b/src/or/channel.c index 9f8a03683f..56eeccc2a7 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -65,6 +65,8 @@ #include "routerlist.h" #include "scheduler.h" #include "compat_time.h" +#include "networkstatus.h" +#include "rendservice.h" /* Global lists of channels */ @@ -2731,12 +2733,25 @@ channel_do_open_actions(channel_t *chan) /* Disable or reduce padding according to user prefs. */ if (chan->padding_enabled || get_options()->ConnectionPadding == 1) { if (!get_options()->ConnectionPadding) { + /* Disable if torrc disabled */ channelpadding_disable_padding_on_channel(chan); - } - - /* Padding can be forced and/or reduced by clients, regardless of if - * the channel supports it */ - if (get_options()->ReducedConnectionPadding) { + } else if (get_options()->Tor2webMode && + !networkstatus_get_param(NULL, + CHANNELPADDING_TOR2WEB_PARAM, + CHANNELPADDING_TOR2WEB_DEFAULT, 0, 1)) { + /* Disable if we're using tor2web and the consensus disabled padding + * for tor2web */ + channelpadding_disable_padding_on_channel(chan); + } else if (rend_service_allow_non_anonymous_connection(get_options()) && + !networkstatus_get_param(NULL, + CHANNELPADDING_SOS_PARAM, + CHANNELPADDING_SOS_DEFAULT, 0, 1)) { + /* Disable if we're using RSOS and the consensus disabled padding + * for RSOS*/ + channelpadding_disable_padding_on_channel(chan); + } else if (get_options()->ReducedConnectionPadding) { + /* Padding can be forced and/or reduced by clients, regardless of if + * the channel supports it */ channelpadding_reduce_padding_on_channel(chan); } } diff --git a/src/or/channelpadding.c b/src/or/channelpadding.c index bed2489837..ccaf5b4ec8 100644 --- a/src/or/channelpadding.c +++ b/src/or/channelpadding.c @@ -21,6 +21,7 @@ #include "router.h" #include "compat_time.h" #include <event2/event.h> +#include "rendservice.h" STATIC int channelpadding_get_netflow_inactive_timeout_ms(const channel_t *); STATIC int channelpadding_send_disable_command(channel_t *); @@ -46,6 +47,10 @@ static int consensus_nf_conntimeout_clients; static int consensus_nf_pad_before_usage; /** Should we pad relay-to-relay connections? */ static int consensus_nf_pad_relays; +/** Should we pad tor2web connections? */ +static int consensus_nf_pad_tor2web; +/** Should we pad rosos connections? */ +static int consensus_nf_pad_single_onion; #define TOR_MSEC_PER_SEC 1000 #define TOR_USEC_PER_MSEC 1000 @@ -130,6 +135,16 @@ channelpadding_new_consensus_params(networkstatus_t *ns) consensus_nf_pad_relays = networkstatus_get_param(ns, "nf_pad_relays", 0, 0, 1); + + consensus_nf_pad_tor2web = + networkstatus_get_param(ns, + CHANNELPADDING_TOR2WEB_PARAM, + CHANNELPADDING_TOR2WEB_DEFAULT, 0, 1); + + consensus_nf_pad_single_onion = + networkstatus_get_param(ns, + CHANNELPADDING_SOS_PARAM, + CHANNELPADDING_SOS_DEFAULT, 0, 1); } /** @@ -717,6 +732,25 @@ channelpadding_decide_to_pad_channel(channel_t *chan) return CHANNELPADDING_WONTPAD; } + if (options->Tor2webMode && !consensus_nf_pad_tor2web) { + /* If the consensus just changed values, this channel may still + * think padding is enabled. Negotiate it off. */ + if (chan->padding_enabled) + channelpadding_disable_padding_on_channel(chan); + + return CHANNELPADDING_WONTPAD; + } + + if (rend_service_allow_non_anonymous_connection(options) && + !consensus_nf_pad_single_onion) { + /* If the consensus just changed values, this channel may still + * think padding is enabled. Negotiate it off. */ + if (chan->padding_enabled) + channelpadding_disable_padding_on_channel(chan); + + return CHANNELPADDING_WONTPAD; + } + if (!chan->has_queued_writes(chan)) { int is_client_channel = 0; diff --git a/src/or/channelpadding.h b/src/or/channelpadding.h index 2708ee9739..a227e27d5b 100644 --- a/src/or/channelpadding.h +++ b/src/or/channelpadding.h @@ -13,6 +13,11 @@ #include "channelpadding_negotiation.h" +#define CHANNELPADDING_TOR2WEB_PARAM "nf_pad_tor2web" +#define CHANNELPADDING_TOR2WEB_DEFAULT 1 +#define CHANNELPADDING_SOS_PARAM "nf_pad_single_onion" +#define CHANNELPADDING_SOS_DEFAULT 1 + typedef enum { CHANNELPADDING_WONTPAD, CHANNELPADDING_PADLATER, diff --git a/src/or/channeltls.c b/src/or/channeltls.c index 6547451181..4ccd3b5fbf 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -847,7 +847,7 @@ channel_tls_write_packed_cell_method(channel_t *chan, tor_assert(packed_cell); if (tlschan->conn) { - connection_write_to_buf(packed_cell->body, cell_network_size, + connection_buf_add(packed_cell->body, cell_network_size, TO_CONN(tlschan->conn)); /* This is where the cell is finished; used to be done from relay.c */ diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 65cd7bd5dc..279308afcb 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -71,7 +71,8 @@ static channel_t * channel_connect_for_circuit(const tor_addr_t *addr, static int circuit_deliver_create_cell(circuit_t *circ, const create_cell_t *create_cell, int relayed); -static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit); +static int onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit, + int is_hs_v3_rp_circuit); static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath); static int onion_extend_cpath(origin_circuit_t *circ); static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice); @@ -289,14 +290,9 @@ circuit_list_path_impl(origin_circuit_t *circ, int verbose, int verbose_names) base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } } else { /* ! verbose_names */ - node = node_get_by_id(id); - if (node && node_is_named(node)) { - elt = tor_strdup(node_get_nickname(node)); - } else { - elt = tor_malloc(HEX_DIGEST_LEN+2); - elt[0] = '$'; - base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); - } + elt = tor_malloc(HEX_DIGEST_LEN+2); + elt[0] = '$'; + base16_encode(elt+1, HEX_DIGEST_LEN+1, id, DIGEST_LEN); } tor_assert(elt); if (verbose) { @@ -505,10 +501,15 @@ circuit_establish_circuit(uint8_t purpose, extend_info_t *exit_ei, int flags) { origin_circuit_t *circ; int err_reason = 0; + int is_hs_v3_rp_circuit = 0; + + if (flags & CIRCLAUNCH_IS_V3_RP) { + is_hs_v3_rp_circuit = 1; + } circ = origin_circuit_init(purpose, flags); - if (onion_pick_cpath_exit(circ, exit_ei) < 0 || + if (onion_pick_cpath_exit(circ, exit_ei, is_hs_v3_rp_circuit) < 0 || onion_populate_cpath(circ) < 0) { circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_NOPATH); return NULL; @@ -2156,7 +2157,8 @@ pick_rendezvous_node(router_crn_flags_t flags) */ static const node_t * choose_good_exit_server(uint8_t purpose, - int need_uptime, int need_capacity, int is_internal) + int need_uptime, int need_capacity, int is_internal, + int need_hs_v3) { const or_options_t *options = get_options(); router_crn_flags_t flags = CRN_NEED_DESC; @@ -2164,6 +2166,8 @@ choose_good_exit_server(uint8_t purpose, flags |= CRN_NEED_UPTIME; if (need_capacity) flags |= CRN_NEED_CAPACITY; + if (need_hs_v3) + flags |= CRN_RENDEZVOUS_V3; switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: @@ -2263,9 +2267,15 @@ warn_if_last_router_excluded(origin_circuit_t *circ, /** Decide a suitable length for circ's cpath, and pick an exit * router (or use <b>exit</b> if provided). Store these in the - * cpath. Return 0 if ok, -1 if circuit should be closed. */ + * cpath. + * + * If <b>is_hs_v3_rp_circuit</b> is set, then this exit should be suitable to + * be used as an HS v3 rendezvous point. + * + * Return 0 if ok, -1 if circuit should be closed. */ static int -onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) +onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei, + int is_hs_v3_rp_circuit) { cpath_build_state_t *state = circ->build_state; @@ -2289,7 +2299,8 @@ onion_pick_cpath_exit(origin_circuit_t *circ, extend_info_t *exit_ei) } else { /* we have to decide one */ const node_t *node = choose_good_exit_server(circ->base_.purpose, state->need_uptime, - state->need_capacity, state->is_internal); + state->need_capacity, state->is_internal, + is_hs_v3_rp_circuit); if (!node) { log_warn(LD_CIRC,"Failed to choose an exit server"); return -1; diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 2f3fe327e6..5cc6252325 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -1609,6 +1609,30 @@ circuit_get_next_by_pk_and_purpose(origin_circuit_t *start, return NULL; } +/** We might cannibalize this circuit: Return true if its last hop can be used + * as a v3 rendezvous point. */ +static int +circuit_can_be_cannibalized_for_v3_rp(const origin_circuit_t *circ) +{ + if (!circ->build_state) { + return 0; + } + + extend_info_t *chosen_exit = circ->build_state->chosen_exit; + if (BUG(!chosen_exit)) { + return 0; + } + + const node_t *rp_node = node_get_by_id(chosen_exit->identity_digest); + if (rp_node) { + if (node_supports_v3_rendezvous_point(rp_node)) { + return 1; + } + } + + return 0; +} + /** Return a circuit that is open, is CIRCUIT_PURPOSE_C_GENERAL, * has a timestamp_dirty value of 0, has flags matching the CIRCLAUNCH_* * flags in <b>flags</b>, and if info is defined, does not already use info @@ -1691,6 +1715,14 @@ circuit_find_to_cannibalize(uint8_t purpose, extend_info_t *info, hop = hop->next; } while (hop != circ->cpath); } + + if ((flags & CIRCLAUNCH_IS_V3_RP) && + !circuit_can_be_cannibalized_for_v3_rp(circ)) { + log_debug(LD_GENERAL, "Skipping uncannibalizable circuit for v3 " + "rendezvous point."); + goto next; + } + if (!best || (best->build_state->need_uptime && !need_uptime)) best = circ; next: ; @@ -1880,6 +1912,10 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, circuits_pending_close = smartlist_new(); smartlist_add(circuits_pending_close, circ); + + log_info(LD_GENERAL, "Circuit %u marked for close at %s:%d (orig reason: " + "%u, new reason: %u)", + circ->n_circ_id, file, line, orig_reason, reason); } /** Called immediately before freeing a marked circuit <b>circ</b> from diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c index 51d580a1a4..963892c9d1 100644 --- a/src/or/circuitstats.c +++ b/src/or/circuitstats.c @@ -939,7 +939,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, uint32_t count, k; build_time_t ms; int ok; - ms = (build_time_t)tor_parse_ulong(ms_str, 0, 0, + ms = (build_time_t)tor_parse_ulong(ms_str, 10, 0, CBT_BUILD_TIME_MAX, &ok, NULL); if (!ok) { log_warn(LD_GENERAL, "Unable to parse circuit build times: " @@ -949,7 +949,7 @@ circuit_build_times_parse_state(circuit_build_times_t *cbt, smartlist_free(args); break; } - count = (uint32_t)tor_parse_ulong(count_str, 0, 0, + count = (uint32_t)tor_parse_ulong(count_str, 10, 0, UINT32_MAX, &ok, NULL); if (!ok) { log_warn(LD_GENERAL, "Unable to parse circuit build times: " diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 570b05e572..7545dea290 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -2121,7 +2121,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, } else { /* XXXX Duplicates checks in connection_ap_handshake_attach_circuit: * refactor into a single function. */ - const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0); int opt = conn->chosen_exit_optional; if (node && !connection_ap_can_use_exit(conn, node)) { log_fn(opt ? LOG_INFO : LOG_WARN, LD_APP, @@ -2204,7 +2204,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (conn->chosen_exit_name) { const node_t *r; int opt = conn->chosen_exit_optional; - r = node_get_by_nickname(conn->chosen_exit_name, 1); + r = node_get_by_nickname(conn->chosen_exit_name, 0); if (r && node_has_descriptor(r)) { /* We might want to connect to an IPv6 bridge for loading descriptors so we use the preferred address rather than @@ -2290,6 +2290,16 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (want_onehop) flags |= CIRCLAUNCH_ONEHOP_TUNNEL; if (need_uptime) flags |= CIRCLAUNCH_NEED_UPTIME; if (need_internal) flags |= CIRCLAUNCH_IS_INTERNAL; + + /* If we are about to pick a v3 RP right now, make sure we pick a + * rendezvous point that supports the v3 protocol! */ + if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_REND_JOINED && + new_circ_purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND && + ENTRY_TO_EDGE_CONN(conn)->hs_ident) { + flags |= CIRCLAUNCH_IS_V3_RP; + log_info(LD_GENERAL, "Getting rendezvous circuit to v3 service!"); + } + circ = circuit_launch_by_extend_info(new_circ_purpose, extend_info, flags); } @@ -2610,7 +2620,7 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) * open to that exit. See what exit we meant, and whether we can use it. */ if (conn->chosen_exit_name) { - const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 1); + const node_t *node = node_get_by_nickname(conn->chosen_exit_name, 0); int opt = conn->chosen_exit_optional; if (!node && !want_onehop) { /* We ran into this warning when trying to extend a circuit to a diff --git a/src/or/circuituse.h b/src/or/circuituse.h index e66679586d..6c0bcbb357 100644 --- a/src/or/circuituse.h +++ b/src/or/circuituse.h @@ -44,6 +44,9 @@ void circuit_build_failed(origin_circuit_t *circ); /** Flag to set when the last hop of a circuit doesn't need to be an * exit node. */ #define CIRCLAUNCH_IS_INTERNAL (1<<3) +/** Flag to set when we are trying to launch a v3 rendezvous circuit. We need + * to apply some additional filters on the node picked. */ +#define CIRCLAUNCH_IS_V3_RP (1<<4) origin_circuit_t *circuit_launch_by_extend_info(uint8_t purpose, extend_info_t *info, int flags); diff --git a/src/or/config.c b/src/or/config.c index 90ab0e57fe..9a7251c41f 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -372,6 +372,7 @@ static config_var_t option_vars_[] = { V(HTTPProxyAuthenticator, STRING, NULL), V(HTTPSProxy, STRING, NULL), V(HTTPSProxyAuthenticator, STRING, NULL), + VPORT(HTTPTunnelPort), V(IPv6Exit, BOOL, "0"), VAR("ServerTransportPlugin", LINELIST, ServerTransportPlugin, NULL), V(ServerTransportListenAddr, LINELIST, NULL), @@ -776,6 +777,9 @@ static int have_parsed_cmdline = 0; static char *global_dirfrontpagecontents = NULL; /** List of port_cfg_t for all configured ports. */ static smartlist_t *configured_ports = NULL; +/** True iff we're currently validating options, and any calls to + * get_options() are likely to be bugs. */ +static int in_option_validation = 0; /** Return the contents of our frontpage string, or NULL if not configured. */ MOCK_IMPL(const char*, @@ -789,6 +793,7 @@ MOCK_IMPL(or_options_t *, get_options_mutable, (void)) { tor_assert(global_options); + tor_assert_nonfatal(! in_option_validation); return global_options; } @@ -1782,9 +1787,13 @@ options_act(const or_options_t *old_options) } /* Write our PID to the PID file. If we do not have write permissions we - * will log a warning */ + * will log a warning and exit. */ if (options->PidFile && !sandbox_is_active()) { - write_pidfile(options->PidFile); + if (write_pidfile(options->PidFile) < 0) { + log_err(LD_CONFIG, "Unable to write PIDFile %s", + escaped(options->PidFile)); + return -1; + } } /* Register addressmap directives */ @@ -2314,24 +2323,36 @@ options_trial_assign(config_line_t *list, unsigned flags, char **msg) return r; } - if (options_validate(get_options_mutable(), trial_options, + setopt_err_t rv; + or_options_t *cur_options = get_options_mutable(); + + in_option_validation = 1; + + if (options_validate(cur_options, trial_options, global_default_options, 1, msg) < 0) { or_options_free(trial_options); - return SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ + rv = SETOPT_ERR_PARSE; /*XXX make this a separate return value. */ + goto done; } - if (options_transition_allowed(get_options(), trial_options, msg) < 0) { + if (options_transition_allowed(cur_options, trial_options, msg) < 0) { or_options_free(trial_options); - return SETOPT_ERR_TRANSITION; + rv = SETOPT_ERR_TRANSITION; + goto done; } + in_option_validation = 0; if (set_options(trial_options, msg)<0) { or_options_free(trial_options); - return SETOPT_ERR_SETTING; + rv = SETOPT_ERR_SETTING; + goto done; } /* we liked it. put it in place. */ - return SETOPT_OK; + rv = SETOPT_OK; + done: + in_option_validation = 0; + return rv; } /** Print a usage message for tor. */ @@ -2834,8 +2855,11 @@ static int options_validate_cb(void *old_options, void *options, void *default_options, int from_setconf, char **msg) { - return options_validate(old_options, options, default_options, + in_option_validation = 1; + int rv = options_validate(old_options, options, default_options, from_setconf, msg); + in_option_validation = 0; + return rv; } #define REJECT(arg) \ @@ -2853,8 +2877,10 @@ options_validate_cb(void *old_options, void *options, void *default_options, * an absolute path that <b>filepath</b> will resolve to. * * In case <b>filepath</b> is absolute, do nothing. + * + * Return 1 if there were relative paths; 0 otherwise. */ -static void +static int warn_if_option_path_is_relative(const char *option, char *filepath) { @@ -2863,39 +2889,45 @@ warn_if_option_path_is_relative(const char *option, COMPLAIN("Path for %s (%s) is relative and will resolve to %s." " Is this what you wanted?", option, filepath, abs_path); tor_free(abs_path); + return 1; } + return 0; } /** Scan <b>options</b> for occurances of relative file/directory * path and log a warning whenever it is found. + * + * Return 1 if there were relative paths; 0 otherwise. */ -static void +static int warn_about_relative_paths(or_options_t *options) { tor_assert(options); + int n = 0; - warn_if_option_path_is_relative("CookieAuthFile", - options->CookieAuthFile); - warn_if_option_path_is_relative("ExtORPortCookieAuthFile", - options->ExtORPortCookieAuthFile); - warn_if_option_path_is_relative("DirPortFrontPage", - options->DirPortFrontPage); - warn_if_option_path_is_relative("V3BandwidthsFile", - options->V3BandwidthsFile); - warn_if_option_path_is_relative("ControlPortWriteToFile", - options->ControlPortWriteToFile); - warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile); - warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File); - warn_if_option_path_is_relative("Log",options->DebugLogFile); - warn_if_option_path_is_relative("AccelDir",options->AccelDir); - warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); - warn_if_option_path_is_relative("PidFile",options->PidFile); + n += warn_if_option_path_is_relative("CookieAuthFile", + options->CookieAuthFile); + n += warn_if_option_path_is_relative("ExtORPortCookieAuthFile", + options->ExtORPortCookieAuthFile); + n += warn_if_option_path_is_relative("DirPortFrontPage", + options->DirPortFrontPage); + n += warn_if_option_path_is_relative("V3BandwidthsFile", + options->V3BandwidthsFile); + n += warn_if_option_path_is_relative("ControlPortWriteToFile", + options->ControlPortWriteToFile); + n += warn_if_option_path_is_relative("GeoIPFile",options->GeoIPFile); + n += warn_if_option_path_is_relative("GeoIPv6File",options->GeoIPv6File); + n += warn_if_option_path_is_relative("Log",options->DebugLogFile); + n += warn_if_option_path_is_relative("AccelDir",options->AccelDir); + n += warn_if_option_path_is_relative("DataDirectory",options->DataDirectory); + n += warn_if_option_path_is_relative("PidFile",options->PidFile); for (config_line_t *hs_line = options->RendConfigLines; hs_line; hs_line = hs_line->next) { if (!strcasecmp(hs_line->key, "HiddenServiceDir")) - warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value); + n += warn_if_option_path_is_relative("HiddenServiceDir",hs_line->value); } + return n != 0; } /* Validate options related to single onion services. @@ -2926,7 +2958,8 @@ options_validate_single_onion(or_options_t *options, char **msg) const int client_port_set = (options->SocksPort_set || options->TransPort_set || options->NATDPort_set || - options->DNSPort_set); + options->DNSPort_set || + options->HTTPTunnelPort_set); if (rend_service_non_anonymous_mode_enabled(options) && client_port_set && !options->Tor2webMode) { REJECT("HiddenServiceNonAnonymousMode is incompatible with using Tor as " @@ -2998,7 +3031,11 @@ options_validate(or_options_t *old_options, or_options_t *options, * Always use the value of UseEntryGuards, not UseEntryGuards_option. */ options->UseEntryGuards = options->UseEntryGuards_option; - warn_about_relative_paths(options); + if (warn_about_relative_paths(options) && options->RunAsDaemon) { + REJECT("You have specified at least one relative path (see above) " + "with the RunAsDaemon option. RunAsDaemon is not compatible " + "with relative paths."); + } if (server_mode(options) && (!strcmpstart(uname, "Windows 95") || @@ -5210,6 +5247,7 @@ options_init_from_string(const char *cf_defaults, const char *cf, } newoptions->IncludeUsed = cf_has_include; + in_option_validation = 1; /* Validate newoptions */ if (options_validate(oldoptions, newoptions, newdefaultoptions, @@ -5222,17 +5260,20 @@ options_init_from_string(const char *cf_defaults, const char *cf, err = SETOPT_ERR_TRANSITION; goto err; } + in_option_validation = 0; if (set_options(newoptions, msg)) { err = SETOPT_ERR_SETTING; goto err; /* frees and replaces old options */ } + or_options_free(global_default_options); global_default_options = newdefaultoptions; return SETOPT_OK; err: + in_option_validation = 0; or_options_free(newoptions); or_options_free(newdefaultoptions); if (*msg) { @@ -7012,6 +7053,15 @@ parse_ports(or_options_t *options, int validate_only, *msg = tor_strdup("Invalid NatdPort configuration"); goto err; } + if (parse_port_config(ports, + options->HTTPTunnelPort_lines, + "HTTP Tunnel", CONN_TYPE_AP_HTTP_CONNECT_LISTENER, + "127.0.0.1", 0, + ((validate_only ? 0 : CL_PORT_WARN_NONLOCAL) + | CL_PORT_TAKES_HOSTNAMES | gw_flag)) < 0) { + *msg = tor_strdup("Invalid HTTPTunnelPort configuration"); + goto err; + } { unsigned control_port_flags = CL_PORT_NO_STREAM_OPTIONS | CL_PORT_WARN_NONLOCAL; @@ -7089,6 +7139,8 @@ parse_ports(or_options_t *options, int validate_only, !! count_real_listeners(ports, CONN_TYPE_AP_TRANS_LISTENER, 1); options->NATDPort_set = !! count_real_listeners(ports, CONN_TYPE_AP_NATD_LISTENER, 1); + options->HTTPTunnelPort_set = + !! count_real_listeners(ports, CONN_TYPE_AP_HTTP_CONNECT_LISTENER, 1); /* Use options->ControlSocket to test if a control socket is set */ options->ControlPort_set = !! count_real_listeners(ports, CONN_TYPE_CONTROL_LISTENER, 0); @@ -7933,13 +7985,28 @@ parse_outbound_addresses(or_options_t *options, int validate_only, char **msg) memset(&options->OutboundBindAddresses, 0, sizeof(options->OutboundBindAddresses)); } - parse_outbound_address_lines(options->OutboundBindAddress, - OUTBOUND_ADDR_EXIT_AND_OR, options, validate_only, msg); - parse_outbound_address_lines(options->OutboundBindAddressOR, - OUTBOUND_ADDR_OR, options, validate_only, msg); - parse_outbound_address_lines(options->OutboundBindAddressExit, - OUTBOUND_ADDR_EXIT, options, validate_only, msg); + + if (parse_outbound_address_lines(options->OutboundBindAddress, + OUTBOUND_ADDR_EXIT_AND_OR, options, + validate_only, msg) < 0) { + goto err; + } + + if (parse_outbound_address_lines(options->OutboundBindAddressOR, + OUTBOUND_ADDR_OR, options, validate_only, + msg) < 0) { + goto err; + } + + if (parse_outbound_address_lines(options->OutboundBindAddressExit, + OUTBOUND_ADDR_EXIT, options, validate_only, + msg) < 0) { + goto err; + } + return 0; + err: + return -1; } /** Load one of the geoip files, <a>family</a> determining which diff --git a/src/or/connection.c b/src/or/connection.c index 31a682387d..5c675acca4 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -37,7 +37,7 @@ * they call connection_stop_reading() or connection_stop_writing(). * * To queue data to be written on a connection, call - * connection_write_to_buf(). When data arrives, the + * connection_buf_add(). When data arrives, the * connection_process_inbuf() callback is invoked, which dispatches to a * type-specific function (such as connection_edge_process_inbuf() for * example). Connection types that need notice of when data has been written @@ -58,6 +58,7 @@ #include "or.h" #include "bridges.h" #include "buffers.h" +#include "buffers_tls.h" /* * Define this so we get channel internal functions, since we're implementing * part of a subclass (channel_tls_t). @@ -86,6 +87,8 @@ #include "hs_common.h" #include "hs_ident.h" #include "nodelist.h" +#include "proto_http.h" +#include "proto_socks.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -124,8 +127,9 @@ static int connection_finished_flushing(connection_t *conn); static int connection_flushed_some(connection_t *conn); static int connection_finished_connecting(connection_t *conn); static int connection_reached_eof(connection_t *conn); -static int connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, - int *socket_error); +static int connection_buf_read_from_socket(connection_t *conn, + ssize_t *max_to_read, + int *socket_error); static int connection_process_inbuf(connection_t *conn, int package_partial); static void client_check_address_changed(tor_socket_t sock); static void set_constrained_socket_buffers(tor_socket_t sock, int size); @@ -158,7 +162,8 @@ static smartlist_t *outgoing_addrs = NULL; case CONN_TYPE_CONTROL_LISTENER: \ case CONN_TYPE_AP_TRANS_LISTENER: \ case CONN_TYPE_AP_NATD_LISTENER: \ - case CONN_TYPE_AP_DNS_LISTENER + case CONN_TYPE_AP_DNS_LISTENER: \ + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER /**************************************************************/ @@ -185,6 +190,7 @@ conn_type_to_string(int type) case CONN_TYPE_CONTROL: return "Control"; case CONN_TYPE_EXT_OR: return "Extended OR"; case CONN_TYPE_EXT_OR_LISTENER: return "Extended OR listener"; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return "HTTP tunnel listener"; default: log_warn(LD_BUG, "unknown connection type %d", type); tor_snprintf(buf, sizeof(buf), "unknown [%d]", type); @@ -1702,6 +1708,8 @@ connection_init_accepted_conn(connection_t *conn, TO_ENTRY_CONN(conn)->is_transparent_ap = 1; conn->state = AP_CONN_STATE_NATD_WAIT; break; + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: + conn->state = AP_CONN_STATE_HTTP_CONNECT_WAIT; } break; case CONN_TYPE_DIR: @@ -2140,7 +2148,7 @@ connection_proxy_connect(connection_t *conn, int type) fmt_addrport(&conn->addr, conn->port)); } - connection_write_to_buf(buf, strlen(buf), conn); + connection_buf_add(buf, strlen(buf), conn); conn->proxy_state = PROXY_HTTPS_WANT_CONNECT_OK; break; } @@ -2206,7 +2214,7 @@ connection_proxy_connect(connection_t *conn, int type) buf[8] = 0; /* no userid */ } - connection_write_to_buf((char *)buf, buf_size, conn); + connection_buf_add((char *)buf, buf_size, conn); tor_free(buf); conn->proxy_state = PROXY_SOCKS4_WANT_CONNECT_OK; @@ -2237,7 +2245,7 @@ connection_proxy_connect(connection_t *conn, int type) conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_METHOD_NONE; } - connection_write_to_buf((char *)buf, 2 + buf[1], conn); + connection_buf_add((char *)buf, 2 + buf[1], conn); break; } @@ -2343,7 +2351,7 @@ connection_send_socks5_connect(connection_t *conn) memcpy(buf + 20, &port, 2); } - connection_write_to_buf((char *)buf, reqsize, conn); + connection_buf_add((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_CONNECT_OK; } @@ -2469,7 +2477,7 @@ connection_read_proxy_handshake(connection_t *conn) if (socks_args_string) tor_free(socks_args_string); - connection_write_to_buf((char *)buf, reqsize, conn); + connection_buf_add((char *)buf, reqsize, conn); conn->proxy_state = PROXY_SOCKS5_WANT_AUTH_RFC1929_OK; ret = 0; @@ -3368,7 +3376,7 @@ connection_bucket_should_increase(int bucket, or_connection_t *conn) /** Read bytes from conn-\>s and process them. * - * It calls connection_read_to_buf() to bring in any new bytes, + * It calls connection_buf_read_from_socket() to bring in any new bytes, * and then calls connection_process_inbuf() to process them. * * Mark the connection and return -1 if you want to close it, else @@ -3394,6 +3402,7 @@ connection_handle_read_impl(connection_t *conn) case CONN_TYPE_AP_LISTENER: case CONN_TYPE_AP_TRANS_LISTENER: case CONN_TYPE_AP_NATD_LISTENER: + case CONN_TYPE_AP_HTTP_CONNECT_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_AP); case CONN_TYPE_DIR_LISTENER: return connection_handle_listener_read(conn, CONN_TYPE_DIR); @@ -3410,7 +3419,7 @@ connection_handle_read_impl(connection_t *conn) tor_assert(!conn->marked_for_close); before = buf_datalen(conn->inbuf); - if (connection_read_to_buf(conn, &max_to_read, &socket_error) < 0) { + if (connection_buf_read_from_socket(conn, &max_to_read, &socket_error) < 0) { /* There's a read error; kill the connection.*/ if (conn->type == CONN_TYPE_OR) { connection_or_notify_error(TO_OR_CONN(conn), @@ -3507,7 +3516,7 @@ connection_handle_read(connection_t *conn) * Return -1 if we want to break conn, else return 0. */ static int -connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, +connection_buf_read_from_socket(connection_t *conn, ssize_t *max_to_read, int *socket_error) { int result; @@ -3548,7 +3557,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, initial_size = buf_datalen(conn->inbuf); /* else open, or closing */ - result = read_to_buf_tls(or_conn->tls, at_most, conn->inbuf); + result = buf_read_from_tls(conn->inbuf, or_conn->tls, at_most); if (TOR_TLS_IS_ERROR(result) || result == TOR_TLS_CLOSE) or_conn->tls_error = result; else @@ -3597,7 +3606,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* If we have any pending bytes, we read them now. This *can* * take us over our read allotment, but really we shouldn't be * believing that SSL bytes are the same as TCP bytes anyway. */ - int r2 = read_to_buf_tls(or_conn->tls, pending, conn->inbuf); + int r2 = buf_read_from_tls(conn->inbuf, or_conn->tls, pending); if (BUG(r2<0)) { log_warn(LD_BUG, "apparently, reading pending bytes can fail."); return -1; @@ -3609,7 +3618,7 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, result, (long)n_read, (long)n_written); } else if (conn->linked) { if (conn->linked_conn) { - result = move_buf_to_buf(conn->inbuf, conn->linked_conn->outbuf, + result = buf_move_to_buf(conn->inbuf, conn->linked_conn->outbuf, &conn->linked_conn->outbuf_flushlen); } else { result = 0; @@ -3627,8 +3636,10 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /* !connection_speaks_cells, !conn->linked_conn. */ int reached_eof = 0; CONN_LOG_PROTECT(conn, - result = read_to_buf(conn->s, at_most, conn->inbuf, &reached_eof, - socket_error)); + result = buf_read_from_socket(conn->inbuf, conn->s, + at_most, + &reached_eof, + socket_error)); if (reached_eof) conn->inbuf_reached_eof = 1; @@ -3697,17 +3708,17 @@ connection_read_to_buf(connection_t *conn, ssize_t *max_to_read, /** A pass-through to fetch_from_buf. */ int -connection_fetch_from_buf(char *string, size_t len, connection_t *conn) +connection_buf_get_bytes(char *string, size_t len, connection_t *conn) { - return fetch_from_buf(string, len, conn->inbuf); + return buf_get_bytes(conn->inbuf, string, len); } -/** As fetch_from_buf_line(), but read from a connection's input buffer. */ +/** As buf_get_line(), but read from a connection's input buffer. */ int -connection_fetch_from_buf_line(connection_t *conn, char *data, +connection_buf_get_line(connection_t *conn, char *data, size_t *data_len) { - return fetch_from_buf_line(conn->inbuf, data, data_len); + return buf_get_line(conn->inbuf, data, data_len); } /** As fetch_from_buf_http, but fetches from a connection's input buffer_t as @@ -3744,7 +3755,7 @@ connection_outbuf_too_full(connection_t *conn) * * This function gets called either from conn_write_callback() in main.c * when libevent tells us that conn wants to write, or below - * from connection_write_to_buf() when an entire TLS record is ready. + * from connection_buf_add() when an entire TLS record is ready. * * Update <b>conn</b>-\>timestamp_lastwritten to now, and call flush_buf * or flush_buf_tls appropriately. If it succeeds and there are no more @@ -3855,7 +3866,7 @@ connection_handle_write_impl(connection_t *conn, int force) /* else open, or closing */ initial_size = buf_datalen(conn->outbuf); - result = flush_buf_tls(or_conn->tls, conn->outbuf, + result = buf_flush_to_tls(conn->outbuf, or_conn->tls, max_to_write, &conn->outbuf_flushlen); /* If we just flushed the last bytes, tell the channel on the @@ -3918,8 +3929,8 @@ connection_handle_write_impl(connection_t *conn, int force) result = (int)(initial_size-buf_datalen(conn->outbuf)); } else { CONN_LOG_PROTECT(conn, - result = flush_buf(conn->s, conn->outbuf, - max_to_write, &conn->outbuf_flushlen)); + result = buf_flush_to_socket(conn->outbuf, conn->s, + max_to_write, &conn->outbuf_flushlen)); if (result < 0) { if (CONN_IS_EDGE(conn)) connection_edge_end_errno(TO_EDGE_CONN(conn)); @@ -4059,11 +4070,11 @@ connection_write_to_buf_impl_,(const char *string, size_t len, if (zlib) { dir_connection_t *dir_conn = TO_DIR_CONN(conn); int done = zlib < 0; - CONN_LOG_PROTECT(conn, r = write_to_buf_compress(conn->outbuf, + CONN_LOG_PROTECT(conn, r = buf_add_compress(conn->outbuf, dir_conn->compress_state, string, len, done)); } else { - CONN_LOG_PROTECT(conn, r = write_to_buf(string, len, conn->outbuf)); + CONN_LOG_PROTECT(conn, r = buf_add(conn->outbuf, string, len)); } if (r < 0) { if (CONN_IS_EDGE(conn)) { @@ -4113,16 +4124,27 @@ connection_write_to_buf_impl_,(const char *string, size_t len, return ret_conns; \ STMT_END -/* Return a list of connections that aren't close and matches the given state. - * The returned list can be empty and must be freed using smartlist_free(). - * The caller does NOT have owernship of the objects in the list so it must - * not free them nor reference them as they can disapear. */ +/* Return a list of connections that aren't close and matches the given type + * and state. The returned list can be empty and must be freed using + * smartlist_free(). The caller does NOT have owernship of the objects in the + * list so it must not free them nor reference them as they can disappear. */ smartlist_t * connection_list_by_type_state(int type, int state) { CONN_GET_ALL_TEMPLATE(conn, (conn->type == type && conn->state == state)); } +/* Return a list of connections that aren't close and matches the given type + * and purpose. The returned list can be empty and must be freed using + * smartlist_free(). The caller does NOT have owernship of the objects in the + * list so it must not free them nor reference them as they can disappear. */ +smartlist_t * +connection_list_by_type_purpose(int type, int purpose) +{ + CONN_GET_ALL_TEMPLATE(conn, + (conn->type == type && conn->purpose == purpose)); +} + /** Return a connection_t * from get_connection_array() that satisfies test on * var, and that is not marked for close. */ #define CONN_GET_TEMPLATE(var, test) \ @@ -4307,6 +4329,7 @@ connection_is_listener(connection_t *conn) conn->type == CONN_TYPE_AP_TRANS_LISTENER || conn->type == CONN_TYPE_AP_DNS_LISTENER || conn->type == CONN_TYPE_AP_NATD_LISTENER || + conn->type == CONN_TYPE_AP_HTTP_CONNECT_LISTENER || conn->type == CONN_TYPE_DIR_LISTENER || conn->type == CONN_TYPE_CONTROL_LISTENER) return 1; @@ -4974,9 +4997,9 @@ assert_connection_ok(connection_t *conn, time_t now) /* buffers */ if (conn->inbuf) - assert_buf_ok(conn->inbuf); + buf_assert_ok(conn->inbuf); if (conn->outbuf) - assert_buf_ok(conn->outbuf); + buf_assert_ok(conn->outbuf); if (conn->type == CONN_TYPE_OR) { or_connection_t *or_conn = TO_OR_CONN(conn); diff --git a/src/or/connection.h b/src/or/connection.h index 0bcf0ccdce..5b51bb9fae 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -123,8 +123,8 @@ void connection_bucket_refill(int seconds_elapsed, time_t now); int connection_handle_read(connection_t *conn); -int connection_fetch_from_buf(char *string, size_t len, connection_t *conn); -int connection_fetch_from_buf_line(connection_t *conn, char *data, +int connection_buf_get_bytes(char *string, size_t len, connection_t *conn); +int connection_buf_get_line(connection_t *conn, char *data, size_t *data_len); int connection_fetch_from_buf_http(connection_t *conn, char **headers_out, size_t max_headerlen, @@ -139,18 +139,18 @@ int connection_flush(connection_t *conn); MOCK_DECL(void, connection_write_to_buf_impl_, (const char *string, size_t len, connection_t *conn, int zlib)); /* DOCDOC connection_write_to_buf */ -static void connection_write_to_buf(const char *string, size_t len, +static void connection_buf_add(const char *string, size_t len, connection_t *conn); /* DOCDOC connection_write_to_buf_compress */ -static void connection_write_to_buf_compress(const char *string, size_t len, +static void connection_buf_add_compress(const char *string, size_t len, dir_connection_t *conn, int done); static inline void -connection_write_to_buf(const char *string, size_t len, connection_t *conn) +connection_buf_add(const char *string, size_t len, connection_t *conn) { connection_write_to_buf_impl_(string, len, conn, 0); } static inline void -connection_write_to_buf_compress(const char *string, size_t len, +connection_buf_add_compress(const char *string, size_t len, dir_connection_t *conn, int done) { connection_write_to_buf_impl_(string, len, TO_CONN(conn), done ? -1 : 1); @@ -183,6 +183,7 @@ connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); smartlist_t *connection_list_by_type_state(int type, int state); +smartlist_t *connection_list_by_type_purpose(int type, int purpose); smartlist_t *connection_dir_list_by_purpose_and_resource( int purpose, const char *resource); diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index a98b32450b..c5bd39d172 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -82,6 +82,8 @@ #include "main.h" #include "nodelist.h" #include "policies.h" +#include "proto_http.h" +#include "proto_socks.h" #include "reasons.h" #include "relay.h" #include "rendclient.h" @@ -241,6 +243,11 @@ connection_edge_process_inbuf(edge_connection_t *conn, int package_partial) return -1; } return 0; + case AP_CONN_STATE_HTTP_CONNECT_WAIT: + if (connection_ap_process_http_connect(EDGE_TO_ENTRY_CONN(conn)) < 0) { + return -1; + } + return 0; case AP_CONN_STATE_OPEN: case EXIT_CONN_STATE_OPEN: if (connection_edge_package_raw_inbuf(conn, package_partial, NULL) < 0) { @@ -490,6 +497,7 @@ connection_edge_finished_flushing(edge_connection_t *conn) case AP_CONN_STATE_CONNECT_WAIT: case AP_CONN_STATE_CONTROLLER_WAIT: case AP_CONN_STATE_RESOLVE_WAIT: + case AP_CONN_STATE_HTTP_CONNECT_WAIT: return 0; default: log_warn(LD_BUG, "Called in unexpected state %d.",conn->base_.state); @@ -1078,7 +1086,8 @@ circuit_discard_optional_exit_enclaves(extend_info_t *info) if (!entry_conn->chosen_exit_optional && !entry_conn->chosen_exit_retries) continue; - r1 = node_get_by_nickname(entry_conn->chosen_exit_name, 0); + r1 = node_get_by_nickname(entry_conn->chosen_exit_name, + NNF_NO_WARN_UNNAMED); r2 = node_get_by_id(info->identity_digest); if (!r1 || !r2 || r1 != r2) continue; @@ -1181,10 +1190,10 @@ consider_plaintext_ports(entry_connection_t *conn, uint16_t port) * See connection_ap_handshake_rewrite_and_attach()'s * documentation for arguments and return value. */ -int -connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath) +MOCK_IMPL(int, +connection_ap_rewrite_and_attach_if_allowed,(entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath)) { const or_options_t *options = get_options(); @@ -1686,7 +1695,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, if (s[1] != '\0') { /* Looks like a real .exit one. */ conn->chosen_exit_name = tor_strdup(s+1); - node = node_get_by_nickname(conn->chosen_exit_name, 1); + node = node_get_by_nickname(conn->chosen_exit_name, 0); if (exit_source == ADDRMAPSRC_TRACKEXIT) { /* We 5 tries before it expires the addressmap */ @@ -1707,7 +1716,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, * form that means (foo's address).foo.exit. */ conn->chosen_exit_name = tor_strdup(socks->address); - node = node_get_by_nickname(conn->chosen_exit_name, 1); + node = node_get_by_nickname(conn->chosen_exit_name, 0); if (node) { *socks->address = 0; node_get_address_string(node, socks->address, sizeof(socks->address)); @@ -2275,7 +2284,7 @@ connection_ap_handshake_process_socks(entry_connection_t *conn) if (socks->replylen) { had_reply = 1; - connection_write_to_buf((const char*)socks->reply, socks->replylen, + connection_buf_add((const char*)socks->reply, socks->replylen, base_conn); socks->replylen = 0; if (sockshere == -1) { @@ -2372,7 +2381,7 @@ connection_ap_process_natd(entry_connection_t *conn) /* look for LF-terminated "[DEST ip_addr port]" * where ip_addr is a dotted-quad and port is in string form */ - err = connection_fetch_from_buf_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); + err = connection_buf_get_line(ENTRY_TO_CONN(conn), tmp_buf, &tlen); if (err == 0) return 0; if (err < 0) { @@ -2421,6 +2430,108 @@ connection_ap_process_natd(entry_connection_t *conn) return connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); } +/** Called on an HTTP CONNECT entry connection when some bytes have arrived, + * but we have not yet received a full HTTP CONNECT request. Try to parse an + * HTTP CONNECT request from the connection's inbuf. On success, set up the + * connection's socks_request field and try to attach the connection. On + * failure, send an HTTP reply, and mark the connection. + */ +STATIC int +connection_ap_process_http_connect(entry_connection_t *conn) +{ + if (BUG(ENTRY_TO_CONN(conn)->state != AP_CONN_STATE_HTTP_CONNECT_WAIT)) + return -1; + + char *headers = NULL, *body = NULL; + char *command = NULL, *addrport = NULL; + char *addr = NULL; + size_t bodylen = 0; + + const char *errmsg = NULL; + int rv = 0; + + const int http_status = + fetch_from_buf_http(ENTRY_TO_CONN(conn)->inbuf, &headers, 8192, + &body, &bodylen, 1024, 0); + if (http_status < 0) { + /* Bad http status */ + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } else if (http_status == 0) { + /* no HTTP request yet. */ + goto done; + } + + const int cmd_status = parse_http_command(headers, &command, &addrport); + if (cmd_status < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } + tor_assert(command); + tor_assert(addrport); + if (strcasecmp(command, "connect")) { + errmsg = "HTTP/1.0 405 Method Not Allowed\r\n\r\n"; + goto err; + } + + tor_assert(conn->socks_request); + socks_request_t *socks = conn->socks_request; + uint16_t port; + if (tor_addr_port_split(LOG_WARN, addrport, &addr, &port) < 0) { + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + goto err; + } + if (strlen(addr) >= MAX_SOCKS_ADDR_LEN) { + errmsg = "HTTP/1.0 414 Request-URI Too Long\r\n\r\n"; + goto err; + } + + /* Abuse the 'username' and 'password' fields here. They are already an + * abuse. */ + { + char *authorization = http_get_header(headers, "Proxy-Authorization: "); + if (authorization) { + socks->username = authorization; // steal reference + socks->usernamelen = strlen(authorization); + } + char *isolation = http_get_header(headers, "X-Tor-Stream-Isolation: "); + if (isolation) { + socks->password = isolation; // steal reference + socks->passwordlen = strlen(isolation); + } + } + + socks->command = SOCKS_COMMAND_CONNECT; + socks->listener_type = CONN_TYPE_AP_HTTP_CONNECT_LISTENER; + strlcpy(socks->address, addr, sizeof(socks->address)); + socks->port = port; + + control_event_stream_status(conn, STREAM_EVENT_NEW, 0); + + rv = connection_ap_rewrite_and_attach_if_allowed(conn, NULL, NULL); + + // XXXX send a "100 Continue" message? + + goto done; + + err: + if (BUG(errmsg == NULL)) + errmsg = "HTTP/1.0 400 Bad Request\r\n\r\n"; + log_warn(LD_EDGE, "Saying %s", escaped(errmsg)); + connection_buf_add(errmsg, strlen(errmsg), ENTRY_TO_CONN(conn)); + connection_mark_unattached_ap(conn, + END_STREAM_REASON_HTTPPROTOCOL| + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + + done: + tor_free(headers); + tor_free(body); + tor_free(command); + tor_free(addrport); + tor_free(addr); + return rv; +} + /** Iterate over the two bytes of stream_id until we get one that is not * already in use; return it. Return 0 if can't get a unique stream_id. */ @@ -3040,15 +3151,22 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, return; } if (replylen) { /* we already have a reply in mind */ - connection_write_to_buf(reply, replylen, ENTRY_TO_CONN(conn)); + connection_buf_add(reply, replylen, ENTRY_TO_CONN(conn)); conn->socks_request->has_finished = 1; return; } - if (conn->socks_request->socks_version == 4) { + if (conn->socks_request->listener_type == + CONN_TYPE_AP_HTTP_CONNECT_LISTENER) { + const char *response = end_reason_to_http_connect_response_line(endreason); + if (!response) { + response = "HTTP/1.0 400 Bad Request\r\n\r\n"; + } + connection_buf_add(response, strlen(response), ENTRY_TO_CONN(conn)); + } else if (conn->socks_request->socks_version == 4) { memset(buf,0,SOCKS4_NETWORK_LEN); buf[1] = (status==SOCKS5_SUCCEEDED ? SOCKS4_GRANTED : SOCKS4_REJECT); /* leave version, destport, destip zero */ - connection_write_to_buf(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); + connection_buf_add(buf, SOCKS4_NETWORK_LEN, ENTRY_TO_CONN(conn)); } else if (conn->socks_request->socks_version == 5) { size_t buf_len; memset(buf,0,sizeof(buf)); @@ -3067,7 +3185,7 @@ connection_ap_handshake_socks_reply(entry_connection_t *conn, char *reply, /* 4 bytes for the header, 2 bytes for the port, 16 for the address. */ buf_len = 22; } - connection_write_to_buf(buf,buf_len,ENTRY_TO_CONN(conn)); + connection_buf_add(buf,buf_len,ENTRY_TO_CONN(conn)); } /* If socks_version isn't 4 or 5, don't send anything. * This can happen in the case of AP bridges. */ @@ -3702,7 +3820,7 @@ connection_ap_can_use_exit(const entry_connection_t *conn, */ if (conn->chosen_exit_name) { const node_t *chosen_exit = - node_get_by_nickname(conn->chosen_exit_name, 1); + node_get_by_nickname(conn->chosen_exit_name, 0); if (!chosen_exit || tor_memneq(chosen_exit->identity, exit_node->identity, DIGEST_LEN)) { /* doesn't match */ diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 914238fc1f..e47043f7fe 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -89,9 +89,10 @@ int connection_ap_process_transparent(entry_connection_t *conn); int address_is_invalid_destination(const char *address, int client); -int connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath); +MOCK_DECL(int, connection_ap_rewrite_and_attach_if_allowed, + (entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath)); int connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, origin_circuit_t *circ, crypt_path_t *cpath); @@ -188,6 +189,8 @@ typedef struct { STATIC void connection_ap_handshake_rewrite(entry_connection_t *conn, rewrite_result_t *out); + +STATIC int connection_ap_process_http_connect(entry_connection_t *conn); #endif #endif diff --git a/src/or/connection_or.c b/src/or/connection_or.c index 7c929e5272..d890b58da6 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -46,6 +46,7 @@ #include "microdesc.h" #include "networkstatus.h" #include "nodelist.h" +#include "proto_cell.h" #include "reasons.h" #include "relay.h" #include "rephist.h" @@ -1548,7 +1549,10 @@ connection_or_check_valid_tls_handshake(or_connection_t *conn, } if (identity_rcvd) { - crypto_pk_get_digest(identity_rcvd, digest_rcvd_out); + if (crypto_pk_get_digest(identity_rcvd, digest_rcvd_out) < 0) { + crypto_pk_free(identity_rcvd); + return -1; + } } else { memset(digest_rcvd_out, 0, DIGEST_LEN); } @@ -1975,7 +1979,7 @@ connection_or_write_cell_to_buf(const cell_t *cell, or_connection_t *conn) if (cell->command == CELL_PADDING) rep_hist_padding_count_write(PADDING_TYPE_CELL); - connection_write_to_buf(networkcell.body, cell_network_size, TO_CONN(conn)); + connection_buf_add(networkcell.body, cell_network_size, TO_CONN(conn)); /* Touch the channel's active timestamp if there is one */ if (conn->chan) { @@ -2005,8 +2009,8 @@ connection_or_write_var_cell_to_buf,(const var_cell_t *cell, tor_assert(cell); tor_assert(conn); n = var_cell_pack_header(cell, hdr, conn->wide_circ_ids); - connection_write_to_buf(hdr, n, TO_CONN(conn)); - connection_write_to_buf((char*)cell->payload, + connection_buf_add(hdr, n, TO_CONN(conn)); + connection_buf_add((char*)cell->payload, cell->payload_len, TO_CONN(conn)); if (conn->base_.state == OR_CONN_STATE_OR_HANDSHAKING_V3) or_handshake_state_record_var_cell(conn, conn->handshake_state, cell, 0); @@ -2081,7 +2085,7 @@ connection_or_process_cells_from_inbuf(or_connection_t *conn) channel_timestamp_active(TLS_CHAN_TO_BASE(conn->chan)); circuit_build_times_network_is_live(get_circuit_build_times_mutable()); - connection_fetch_from_buf(buf, cell_network_size, TO_CONN(conn)); + connection_buf_get_bytes(buf, cell_network_size, TO_CONN(conn)); /* retrieve cell info from buf (create the host-order struct from the * network-order string) */ diff --git a/src/or/conscache.c b/src/or/conscache.c index 5ffa129bbe..9e13ce8e43 100644 --- a/src/or/conscache.c +++ b/src/or/conscache.c @@ -9,6 +9,14 @@ #define CCE_MAGIC 0x17162253 +#ifdef _WIN32 +/* On Windows, unlink won't work on a file if the file is actively mmap()ed. + * That forces us to be less aggressive about unlinking files, and causes other + * changes throughout our logic. + */ +#define MUST_UNMAP_TO_UNLINK +#endif + /** * A consensus_cache_entry_t is a reference-counted handle to an * item in a consensus_cache_t. It can be mmapped into RAM, or not, @@ -49,6 +57,11 @@ struct consensus_cache_t { storage_dir_t *dir; /** List of all the entries in the directory. */ smartlist_t *entries; + + /** The maximum number of entries that we'd like to allow in this cache. + * This is the same as the storagedir limit when MUST_UNMAP_TO_UNLINK is + * not defined. */ + unsigned max_entries; }; static void consensus_cache_clear(consensus_cache_t *cache); @@ -64,9 +77,26 @@ static void consensus_cache_entry_unmap(consensus_cache_entry_t *ent); consensus_cache_t * consensus_cache_open(const char *subdir, int max_entries) { + int storagedir_max_entries; consensus_cache_t *cache = tor_malloc_zero(sizeof(consensus_cache_t)); char *directory = get_datadir_fname(subdir); - cache->dir = storage_dir_new(directory, max_entries); + cache->max_entries = max_entries; + +#ifdef MUST_UNMAP_TO_UNLINK + /* If we can't unlink the files that we're still using, then we need to + * tell the storagedir backend to allow far more files than this consensus + * cache actually wants, so that it can hold files which, from this cache's + * perspective, have become useless. + */ +#define VERY_LARGE_STORAGEDIR_LIMIT (1000*1000) + storagedir_max_entries = VERY_LARGE_STORAGEDIR_LIMIT; +#else + /* Otherwise, we can just tell the storagedir to use the same limits + * as this cache. */ + storagedir_max_entries = max_entries; +#endif + + cache->dir = storage_dir_new(directory, storagedir_max_entries); tor_free(directory); if (!cache->dir) { tor_free(cache); @@ -77,6 +107,24 @@ consensus_cache_open(const char *subdir, int max_entries) return cache; } +/** Return true if it's okay to put more entries in this cache than + * its official file limit. + * + * (We need this method on Windows, where we can't unlink files that are still + * in use, and therefore might need to temporarily exceed the file limit until + * the no-longer-wanted files are deletable.) + */ +int +consensus_cache_may_overallocate(consensus_cache_t *cache) +{ + (void) cache; +#ifdef MUST_UNMAP_TO_UNLINK + return 1; +#else + return 0; +#endif +} + /** * Tell the sandbox (if any) configured by <b>cfg</b> to allow the * operations that <b>cache</b> will need. @@ -85,6 +133,19 @@ int consensus_cache_register_with_sandbox(consensus_cache_t *cache, struct sandbox_cfg_elem **cfg) { +#ifdef MUST_UNMAP_TO_UNLINK + /* Our Linux sandbox doesn't support huge file lists like the one that would + * be generated by using VERY_LARGE_STORAGEDIR_LIMIT above in + * consensus_cache_open(). Since the Linux sandbox is the only one we have + * right now, we just assert that we never reach this point when we've had + * to use VERY_LARGE_STORAGEDIR_LIMIT. + * + * If at some point in the future we have a different sandbox mechanism that + * can handle huge file lists, we can remove this assertion or make it + * conditional. + */ + tor_assert_nonfatal_unreached(); +#endif return storage_dir_register_with_sandbox(cache->dir, cfg); } @@ -406,23 +467,36 @@ int consensus_cache_get_n_filenames_available(consensus_cache_t *cache) { tor_assert(cache); - int max = storage_dir_get_max_files(cache->dir); + int max = cache->max_entries; int used = smartlist_len(storage_dir_list(cache->dir)); +#ifdef MUST_UNMAP_TO_UNLINK + if (used > max) + return 0; +#else tor_assert_nonfatal(max >= used); +#endif return max - used; } /** * Delete every element of <b>cache</b> has been marked with - * consensus_cache_entry_mark_for_removal. If <b>force</b> is false, - * retain those entries which are not in use except by the cache. + * consensus_cache_entry_mark_for_removal. If <b>force</b> is false, + * retain those entries which are in use by something other than the cache. */ void consensus_cache_delete_pending(consensus_cache_t *cache, int force) { SMARTLIST_FOREACH_BEGIN(cache->entries, consensus_cache_entry_t *, ent) { tor_assert_nonfatal(ent->in_cache == cache); - if (! force) { + int force_ent = force; +#ifdef MUST_UNMAP_TO_UNLINK + /* We cannot delete anything with an active mmap on win32, so no + * force-deletion. */ + if (ent->map) { + force_ent = 0; + } +#endif + if (! force_ent) { if (ent->refcnt > 1 || BUG(ent->in_cache == NULL)) { /* Somebody is using this entry right now */ continue; diff --git a/src/or/conscache.h b/src/or/conscache.h index aef54201f0..a0d74c4e08 100644 --- a/src/or/conscache.h +++ b/src/or/conscache.h @@ -14,6 +14,7 @@ HANDLE_DECL(consensus_cache_entry, consensus_cache_entry_t, ) consensus_cache_t *consensus_cache_open(const char *subdir, int max_entries); void consensus_cache_free(consensus_cache_t *cache); struct sandbox_cfg_elem; +int consensus_cache_may_overallocate(consensus_cache_t *cache); int consensus_cache_register_with_sandbox(consensus_cache_t *cache, struct sandbox_cfg_elem **cfg); void consensus_cache_unmap_lazy(consensus_cache_t *cache, time_t cutoff); diff --git a/src/or/consdiffmgr.c b/src/or/consdiffmgr.c index 928fc26f54..831d5d45c3 100644 --- a/src/or/consdiffmgr.c +++ b/src/or/consdiffmgr.c @@ -1136,7 +1136,7 @@ consdiffmgr_ensure_space_for_files(int n) return 0; } // Let's get more assertive: clean out unused stuff, and force-remove - // the files. + // the files that we can. consdiffmgr_cleanup(); consensus_cache_delete_pending(cache, 1); const int n_to_remove = n - consensus_cache_get_n_filenames_available(cache); @@ -1159,6 +1159,14 @@ consdiffmgr_ensure_space_for_files(int n) smartlist_free(objects); consensus_cache_delete_pending(cache, 1); + + if (consensus_cache_may_overallocate(cache)) { + /* If we're allowed to throw extra files into the cache, let's do so + * rather getting upset. + */ + return 0; + } + if (BUG(n_marked < n_to_remove)) return -1; else diff --git a/src/or/control.c b/src/or/control.c index bc173a6e1c..c11193f397 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -64,6 +64,8 @@ #include "networkstatus.h" #include "nodelist.h" #include "policies.h" +#include "proto_control0.h" +#include "proto_http.h" #include "reasons.h" #include "rendclient.h" #include "rendcommon.h" @@ -355,7 +357,7 @@ static inline void connection_write_str_to_buf(const char *s, control_connection_t *conn) { size_t len = strlen(s); - connection_write_to_buf(s, len, TO_CONN(conn)); + connection_buf_add(s, len, TO_CONN(conn)); } /** Given a <b>len</b>-character string in <b>data</b>, made of lines @@ -565,7 +567,7 @@ connection_printf_to_buf(control_connection_t *conn, const char *format, ...) tor_assert(0); } - connection_write_to_buf(buf, (size_t)len, TO_CONN(conn)); + connection_buf_add(buf, (size_t)len, TO_CONN(conn)); tor_free(buf); } @@ -790,7 +792,7 @@ queued_events_flush_all(int force) SMARTLIST_FOREACH_BEGIN(controllers, control_connection_t *, control_conn) { if (control_conn->event_mask & bit) { - connection_write_to_buf(ev->msg, msg_len, TO_CONN(control_conn)); + connection_buf_add(ev->msg, msg_len, TO_CONN(control_conn)); } } SMARTLIST_FOREACH_END(control_conn); @@ -1072,7 +1074,7 @@ handle_control_getconf(control_connection_t *conn, uint32_t body_len, tor_assert(strlen(tmp)>4); tmp[3] = ' '; msg = smartlist_join_strings(answers, "", 0, &msg_len); - connection_write_to_buf(msg, msg_len, TO_CONN(conn)); + connection_buf_add(msg, msg_len, TO_CONN(conn)); } else { connection_write_str_to_buf("250 OK\r\n", conn); } @@ -1154,7 +1156,6 @@ static const struct control_event_t control_event_table[] = { { EVENT_ERR_MSG, "ERR" }, { EVENT_NEW_DESC, "NEWDESC" }, { EVENT_ADDRMAP, "ADDRMAP" }, - { EVENT_AUTHDIR_NEWDESCS, "AUTHDIR_NEWDESCS" }, { EVENT_DESCCHANGED, "DESCCHANGED" }, { EVENT_NS, "NS" }, { EVENT_STATUS_GENERAL, "STATUS_GENERAL" }, @@ -1194,7 +1195,10 @@ handle_control_setevents(control_connection_t *conn, uint32_t len, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(events, const char *, ev) { - if (!strcasecmp(ev, "EXTENDED")) { + if (!strcasecmp(ev, "EXTENDED") || + !strcasecmp(ev, "AUTHDIR_NEWDESCS")) { + log_warn(LD_CONTROL, "The \"%s\" SETEVENTS argument is no longer " + "supported.", ev); continue; } else { int i; @@ -1654,12 +1658,12 @@ handle_control_mapaddress(control_connection_t *conn, uint32_t len, if (smartlist_len(reply)) { ((char*)smartlist_get(reply,smartlist_len(reply)-1))[3] = ' '; r = smartlist_join_strings(reply, "\r\n", 1, &sz); - connection_write_to_buf(r, sz, TO_CONN(conn)); + connection_buf_add(r, sz, TO_CONN(conn)); tor_free(r); } else { const char *response = "512 syntax error: not enough arguments to mapaddress.\r\n"; - connection_write_to_buf(response, strlen(response), TO_CONN(conn)); + connection_buf_add(response, strlen(response), TO_CONN(conn)); } SMARTLIST_FOREACH(reply, char *, cp, tor_free(cp)); @@ -1894,7 +1898,7 @@ getinfo_helper_dir(control_connection_t *control_conn, (void) control_conn; if (!strcmpstart(question, "desc/id/")) { const routerinfo_t *ri = NULL; - const node_t *node = node_get_by_hex_id(question+strlen("desc/id/")); + const node_t *node = node_get_by_hex_id(question+strlen("desc/id/"), 0); if (node) ri = node->ri; if (ri) { @@ -1913,7 +1917,7 @@ getinfo_helper_dir(control_connection_t *control_conn, /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ const node_t *node = - node_get_by_nickname(question+strlen("desc/name/"), 1); + node_get_by_nickname(question+strlen("desc/name/"), 0); if (node) ri = node->ri; if (ri) { @@ -1999,7 +2003,7 @@ getinfo_helper_dir(control_connection_t *control_conn, return -1; } } else if (!strcmpstart(question, "md/id/")) { - const node_t *node = node_get_by_hex_id(question+strlen("md/id/")); + const node_t *node = node_get_by_hex_id(question+strlen("md/id/"), 0); const microdesc_t *md = NULL; if (node) md = node->md; if (md && md->body) { @@ -2008,7 +2012,7 @@ getinfo_helper_dir(control_connection_t *control_conn, } else if (!strcmpstart(question, "md/name/")) { /* XXX Setting 'warn_if_unnamed' here is a bit silly -- the * warning goes to the user, not to the controller. */ - const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 1); + const node_t *node = node_get_by_nickname(question+strlen("md/name/"), 0); /* XXXX duplicated code */ const microdesc_t *md = NULL; if (node) md = node->md; @@ -2021,7 +2025,7 @@ getinfo_helper_dir(control_connection_t *control_conn, } else if (!strcmpstart(question, "desc-annotations/id/")) { const routerinfo_t *ri = NULL; const node_t *node = - node_get_by_hex_id(question+strlen("desc-annotations/id/")); + node_get_by_hex_id(question+strlen("desc-annotations/id/"), 0); if (node) ri = node->ri; if (ri) { @@ -3244,7 +3248,7 @@ handle_control_getinfo(control_connection_t *conn, uint32_t len, size_t esc_len; esc_len = write_escaped_data(v, strlen(v), &esc); connection_printf_to_buf(conn, "250+%s=\r\n", k); - connection_write_to_buf(esc, esc_len, TO_CONN(conn)); + connection_buf_add(esc, esc_len, TO_CONN(conn)); tor_free(esc); } } @@ -3402,7 +3406,7 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, nodes = smartlist_new(); SMARTLIST_FOREACH_BEGIN(router_nicknames, const char *, n) { - const node_t *node = node_get_by_nickname(n, 1); + const node_t *node = node_get_by_nickname(n, 0); if (!node) { connection_printf_to_buf(conn, "552 No such router \"%s\"\r\n", n); goto done; @@ -3629,12 +3633,15 @@ handle_control_postdescriptor(control_connection_t *conn, uint32_t len, int cache = 0; /* eventually, we may switch this to 1 */ const char *cp = memchr(body, '\n', len); - smartlist_t *args = smartlist_new(); - tor_assert(cp); + + if (cp == NULL) { + connection_printf_to_buf(conn, "251 Empty body\r\n"); + return 0; + } ++cp; char *cmdline = tor_memdup_nulterm(body, cp-body); - + smartlist_t *args = smartlist_new(); smartlist_split_string(args, cmdline, " ", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); SMARTLIST_FOREACH_BEGIN(args, char *, option) { @@ -4166,7 +4173,7 @@ handle_control_hsfetch(control_connection_t *conn, uint32_t len, const char *server; server = arg + strlen(opt_server); - node = node_get_by_hex_id(server); + node = node_get_by_hex_id(server, 0); if (!node) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", server); @@ -4227,14 +4234,19 @@ handle_control_hspost(control_connection_t *conn, const char *body) { static const char *opt_server = "SERVER="; - smartlist_t *args = smartlist_new(); smartlist_t *hs_dirs = NULL; const char *encoded_desc = body; size_t encoded_desc_len = len; char *cp = memchr(body, '\n', len); + if (cp == NULL) { + connection_printf_to_buf(conn, "251 Empty body\r\n"); + return 0; + } char *argline = tor_strndup(body, cp-body); + smartlist_t *args = smartlist_new(); + /* If any SERVER= options were specified, try parse the options line */ if (!strcasecmpstart(argline, opt_server)) { /* encoded_desc begins after a newline character */ @@ -4247,7 +4259,7 @@ handle_control_hspost(control_connection_t *conn, SMARTLIST_FOREACH_BEGIN(args, const char *, arg) { if (!strcasecmpstart(arg, opt_server)) { const char *server = arg + strlen(opt_server); - const node_t *node = node_get_by_hex_id(server); + const node_t *node = node_get_by_hex_id(server, 0); if (!node || !node->rs) { connection_printf_to_buf(conn, "552 Server \"%s\" not found\r\n", @@ -4985,7 +4997,7 @@ connection_control_process_inbuf(control_connection_t *conn) sizeof(buf)-6); body_len = 2+strlen(buf+6)+2; /* code, msg, nul. */ set_uint16(buf+0, htons(body_len)); - connection_write_to_buf(buf, 4+body_len, TO_CONN(conn)); + connection_buf_add(buf, 4+body_len, TO_CONN(conn)); connection_mark_and_flush(TO_CONN(conn)); return 0; @@ -5007,7 +5019,7 @@ connection_control_process_inbuf(control_connection_t *conn) /* First, fetch a line. */ do { data_len = conn->incoming_cmd_len - conn->incoming_cmd_cur_len; - r = connection_fetch_from_buf_line(TO_CONN(conn), + r = connection_buf_get_line(TO_CONN(conn), conn->incoming_cmd+conn->incoming_cmd_cur_len, &data_len); if (r == 0) @@ -6053,47 +6065,6 @@ control_event_address_mapped(const char *from, const char *to, time_t expires, return 0; } -/** The authoritative dirserver has received a new descriptor that - * has passed basic syntax checks and is properly self-signed. - * - * Notify any interested party of the new descriptor and what has - * been done with it, and also optionally give an explanation/reason. */ -int -control_event_or_authdir_new_descriptor(const char *action, - const char *desc, size_t desclen, - const char *msg) -{ - char firstline[1024]; - char *buf; - size_t totallen; - char *esc = NULL; - size_t esclen; - - if (!EVENT_IS_INTERESTING(EVENT_AUTHDIR_NEWDESCS)) - return 0; - - tor_snprintf(firstline, sizeof(firstline), - "650+AUTHDIR_NEWDESC=\r\n%s\r\n%s\r\n", - action, - msg ? msg : ""); - - /* Escape the server descriptor properly */ - esclen = write_escaped_data(desc, desclen, &esc); - - totallen = strlen(firstline) + esclen + 1; - buf = tor_malloc(totallen); - strlcpy(buf, firstline, totallen); - strlcpy(buf+strlen(firstline), esc, totallen); - send_control_event_string(EVENT_AUTHDIR_NEWDESCS, - buf); - send_control_event_string(EVENT_AUTHDIR_NEWDESCS, - "650 OK\r\n"); - tor_free(esc); - tor_free(buf); - - return 0; -} - /** Cached liveness for network liveness events and GETINFO */ diff --git a/src/or/control.h b/src/or/control.h index 41a194bfcb..5f6dcc352b 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -33,7 +33,6 @@ void connection_control_closed(control_connection_t *conn); int connection_control_process_inbuf(control_connection_t *conn); -#define EVENT_AUTHDIR_NEWDESCS 0x000D #define EVENT_NS 0x000F int control_event_is_interesting(int event); @@ -64,10 +63,6 @@ int control_event_descriptors_changed(smartlist_t *routers); int control_event_address_mapped(const char *from, const char *to, time_t expires, const char *error, const int cached); -int control_event_or_authdir_new_descriptor(const char *action, - const char *desc, - size_t desclen, - const char *msg); int control_event_my_descriptor_changed(void); int control_event_network_liveness_update(int liveness); int control_event_networkstatus_changed(smartlist_t *statuses); @@ -169,8 +164,8 @@ void control_free_all(void); #define EVENT_WARN_MSG 0x000A #define EVENT_ERR_MSG 0x000B #define EVENT_ADDRMAP 0x000C -/* Exposed above */ -// #define EVENT_AUTHDIR_NEWDESCS 0x000D +/* There was an AUTHDIR_NEWDESCS event, but it no longer exists. We + can reclaim 0x000D. */ #define EVENT_DESCCHANGED 0x000E /* Exposed above */ // #define EVENT_NS 0x000F diff --git a/src/or/directory.c b/src/or/directory.c index 9ee6fae4dc..fed4277dec 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -104,7 +104,6 @@ static void directory_send_command(dir_connection_t *conn, int direct, const directory_request_t *request); static int body_is_plausible(const char *body, size_t body_len, int purpose); -static char *http_get_header(const char *headers, const char *which); static void http_set_address_origin(const char *headers, connection_t *conn); static void connection_dir_download_routerdesc_failed(dir_connection_t *conn); static void connection_dir_bridge_routerdesc_failed(dir_connection_t *conn); @@ -1912,11 +1911,11 @@ directory_send_command(dir_connection_t *conn, request_len = strlen(request); total_request_len += request_len; - connection_write_to_buf(request, request_len, TO_CONN(conn)); + connection_buf_add(request, request_len, TO_CONN(conn)); url_len = strlen(url); total_request_len += url_len; - connection_write_to_buf(url, url_len, TO_CONN(conn)); + connection_buf_add(url, url_len, TO_CONN(conn)); tor_free(url); if (!strcmp(httpcommand, "POST") || payload) { @@ -1933,11 +1932,11 @@ directory_send_command(dir_connection_t *conn, request_len = strlen(request); total_request_len += request_len; - connection_write_to_buf(request, request_len, TO_CONN(conn)); + connection_buf_add(request, request_len, TO_CONN(conn)); if (payload) { /* then send the payload afterwards too */ - connection_write_to_buf(payload, payload_len, TO_CONN(conn)); + connection_buf_add(payload, payload_len, TO_CONN(conn)); total_request_len += payload_len; } @@ -1966,15 +1965,41 @@ directory_send_command(dir_connection_t *conn, STATIC int parse_http_url(const char *headers, char **url) { + char *command = NULL; + if (parse_http_command(headers, &command, url) < 0) { + return -1; + } + if (strcmpstart(*url, "/tor/")) { + char *new_url = NULL; + tor_asprintf(&new_url, "/tor%s%s", + *url[0] == '/' ? "" : "/", + *url); + tor_free(*url); + *url = new_url; + } + tor_free(command); + return 0; +} + +/** Parse an HTTP request line at the start of a headers string. On failure, + * return -1. On success, set *<b>command_out</b> to a copy of the HTTP + * command ("get", "post", etc), set *<b>url_out</b> to a copy of the URL, and + * return 0. */ +int +parse_http_command(const char *headers, char **command_out, char **url_out) +{ + const char *command, *end_of_command; char *s, *start, *tmp; s = (char *)eat_whitespace_no_nl(headers); if (!*s) return -1; + command = s; s = (char *)find_whitespace(s); /* get past GET/POST */ if (!*s) return -1; + end_of_command = s; s = (char *)eat_whitespace_no_nl(s); if (!*s) return -1; - start = s; /* this is it, assuming it's valid */ + start = s; /* this is the URL, assuming it's valid */ s = (char *)find_whitespace(start); if (!*s) return -1; @@ -2005,13 +2030,8 @@ parse_http_url(const char *headers, char **url) return -1; } - if (s-start < 5 || strcmpstart(start,"/tor/")) { /* need to rewrite it */ - *url = tor_malloc(s - start + 5); - strlcpy(*url,"/tor", s-start+5); - strlcat((*url)+4, start, s-start+1); - } else { - *url = tor_strndup(start, s-start); - } + *url_out = tor_memdup_nulterm(start, s-start); + *command_out = tor_memdup_nulterm(command, end_of_command - command); return 0; } @@ -2019,7 +2039,7 @@ parse_http_url(const char *headers, char **url) * <b>which</b>. The key should be given with a terminating colon and space; * this function copies everything after, up to but not including the * following \\r\\n. */ -static char * +char * http_get_header(const char *headers, const char *which) { const char *cp = headers; @@ -3466,7 +3486,7 @@ write_http_status_line(dir_connection_t *conn, int status, return; } log_debug(LD_DIRSERV,"Wrote status 'HTTP/1.0 %d %s'", status, reason_phrase); - connection_write_to_buf(buf, strlen(buf), TO_CONN(conn)); + connection_buf_add(buf, strlen(buf), TO_CONN(conn)); } /** Write the header for an HTTP/1.0 response onto <b>conn</b>-\>outbuf, @@ -3539,7 +3559,7 @@ write_http_response_header_impl(dir_connection_t *conn, ssize_t length, memcpy(cp, "\r\n", 3); else tor_assert(0); - connection_write_to_buf(tmp, strlen(tmp), TO_CONN(conn)); + connection_buf_add(tmp, strlen(tmp), TO_CONN(conn)); } /** As write_http_response_header_impl, but sets encoding and content-typed @@ -3902,7 +3922,7 @@ handle_get_frontpage(dir_connection_t *conn, const get_handler_args_t *args) * this page no matter what.] */ write_http_response_header_impl(conn, dlen, "text/html", "identity", NULL, DIRPORTFRONTPAGE_CACHE_LIFETIME); - connection_write_to_buf(frontpage, dlen, TO_CONN(conn)); + connection_buf_add(frontpage, dlen, TO_CONN(conn)); } else { write_http_status_line(conn, 404, "Not found"); } @@ -4539,15 +4559,15 @@ handle_get_status_vote(dir_connection_t *conn, const get_handler_args_t *args) conn->compress_state = tor_compress_new(1, compress_method, choose_compression_level(estimated_len)); SMARTLIST_FOREACH(items, const char *, c, - connection_write_to_buf_compress(c, strlen(c), conn, 0)); - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress(c, strlen(c), conn, 0)); + connection_buf_add_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(items, const char *, c, - connection_write_to_buf(c, strlen(c), TO_CONN(conn))); + connection_buf_add(c, strlen(c), TO_CONN(conn))); } } else { SMARTLIST_FOREACH(dir_items, cached_dir_t *, d, - connection_write_to_buf(compress_method != NO_METHOD ? + connection_buf_add(compress_method != NO_METHOD ? d->dir_compressed : d->dir, compress_method != NO_METHOD ? d->dir_compressed_len : d->dir_len, @@ -4795,14 +4815,14 @@ handle_get_keys(dir_connection_t *conn, const get_handler_args_t *args) conn->compress_state = tor_compress_new(1, compress_method, choose_compression_level(len)); SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_write_to_buf_compress( + connection_buf_add_compress( c->cache_info.signed_descriptor_body, c->cache_info.signed_descriptor_len, conn, 0)); - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress("", 0, conn, 1); } else { SMARTLIST_FOREACH(certs, authority_cert_t *, c, - connection_write_to_buf(c->cache_info.signed_descriptor_body, + connection_buf_add(c->cache_info.signed_descriptor_body, c->cache_info.signed_descriptor_len, TO_CONN(conn))); } @@ -4831,7 +4851,7 @@ handle_get_hs_descriptor_v2(dir_connection_t *conn, switch (rend_cache_lookup_v2_desc_as_dir(query, &descp)) { case 1: /* valid */ write_http_response_header(conn, strlen(descp), NO_METHOD, 0); - connection_write_to_buf(descp, strlen(descp), TO_CONN(conn)); + connection_buf_add(descp, strlen(descp), TO_CONN(conn)); break; case 0: /* well-formed but not present */ write_http_status_line(conn, 404, "Not found"); @@ -4883,7 +4903,7 @@ handle_get_hs_descriptor_v3(dir_connection_t *conn, /* Found requested descriptor! Pass it to this nice client. */ write_http_response_header(conn, strlen(desc_str), NO_METHOD, 0); - connection_write_to_buf(desc_str, strlen(desc_str), TO_CONN(conn)); + connection_buf_add(desc_str, strlen(desc_str), TO_CONN(conn)); done: return 0; @@ -4922,7 +4942,7 @@ handle_get_networkstatus_bridges(dir_connection_t *conn, status = networkstatus_getinfo_by_purpose("bridge", time(NULL)); size_t dlen = strlen(status); write_http_response_header(conn, dlen, NO_METHOD, 0); - connection_write_to_buf(status, dlen, TO_CONN(conn)); + connection_buf_add(status, dlen, TO_CONN(conn)); tor_free(status); goto done; } @@ -4939,7 +4959,7 @@ handle_get_robots(dir_connection_t *conn, const get_handler_args_t *args) const char robots[] = "User-agent: *\r\nDisallow: /\r\n"; size_t len = strlen(robots); write_http_response_header(conn, len, NO_METHOD, ROBOTS_CACHE_LIFETIME); - connection_write_to_buf(robots, len, TO_CONN(conn)); + connection_buf_add(robots, len, TO_CONN(conn)); } return 0; } diff --git a/src/or/directory.h b/src/or/directory.h index fc71bf800b..1200728411 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -87,6 +87,9 @@ MOCK_DECL(void, directory_initiate_request, (directory_request_t *request)); int parse_http_response(const char *headers, int *code, time_t *date, compress_method_t *compression, char **response); +int parse_http_command(const char *headers, + char **command_out, char **url_out); +char *http_get_header(const char *headers, const char *which); int connection_dir_is_encrypted(const dir_connection_t *conn); int connection_dir_reached_eof(dir_connection_t *conn); @@ -188,7 +191,6 @@ STATIC char *accept_encoding_header(void); STATIC int allowed_anonymous_connection_compression_method(compress_method_t); STATIC void warn_disallowed_anonymous_compression_method(compress_method_t); -typedef struct response_handler_args_t response_handler_args_t; STATIC int handle_response_fetch_hsdesc_v3(dir_connection_t *conn, const response_handler_args_t *args); diff --git a/src/or/dirserv.c b/src/or/dirserv.c index e5654e3b90..06ac15d587 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -673,9 +673,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) ri->nickname, source, (int)ri->cache_info.signed_descriptor_len, MAX_DESCRIPTOR_UPLOAD_SIZE); *msg = "Router descriptor was too large."; - control_event_or_authdir_new_descriptor("REJECTED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); r = ROUTER_AUTHDIR_REJECTS; goto fail; } @@ -694,9 +691,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) router_describe(ri), source); *msg = "Not replacing router descriptor; no information has changed since " "the last one with this identity."; - control_event_or_authdir_new_descriptor("DROPPED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); r = ROUTER_IS_ALREADY_KNOWN; goto fail; } @@ -709,9 +703,6 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) if (ed25519_validate_pubkey(pkey) < 0) { log_warn(LD_DIRSERV, "Received bad key from %s (source %s)", router_describe(ri), source); - control_event_or_authdir_new_descriptor("REJECTED", - ri->cache_info.signed_descriptor_body, - desclen, *msg); routerinfo_free(ri); return ROUTER_AUTHDIR_REJECTS; } @@ -732,7 +723,12 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) log_info(LD_DIRSERV, "Dropping descriptor from %s (source: %s) because " "its key did not match an older RSA/Ed25519 keypair", router_describe(ri), source); - *msg = "Looks like your keypair does not match its older value."; + *msg = "Looks like your keypair has changed? This authority previously " + "recorded a different RSA identity for this Ed25519 identity (or vice " + "versa.) Did you replace or copy some of your key files, but not " + "the others? You should either restore the expected keypair, or " + "delete your keys and restart Tor to start your relay with a new " + "identity."; r = ROUTER_AUTHDIR_REJECTS; goto fail; } @@ -749,14 +745,11 @@ dirserv_add_descriptor(routerinfo_t *ri, const char **msg, const char *source) r = router_add_to_routerlist(ri, msg, 0, 0); if (!WRA_WAS_ADDED(r)) { /* unless the routerinfo was fine, just out-of-date */ - if (WRA_WAS_REJECTED(r)) - control_event_or_authdir_new_descriptor("REJECTED", desc, desclen, *msg); log_info(LD_DIRSERV, "Did not add descriptor from '%s' (source: %s): %s.", nickname, source, *msg ? *msg : "(no message)"); } else { smartlist_t *changed; - control_event_or_authdir_new_descriptor("ACCEPTED", desc, desclen, *msg); changed = smartlist_new(); smartlist_add(changed, ri); @@ -2700,7 +2693,7 @@ measured_bw_line_parse(measured_bw_line_t *out, const char *orig_line) } cp+=strlen("bw="); - out->bw_kb = tor_parse_long(cp, 0, 0, LONG_MAX, &parse_ok, &endptr); + out->bw_kb = tor_parse_long(cp, 10, 0, LONG_MAX, &parse_ok, &endptr); if (!parse_ok || (*endptr && !TOR_ISSPACE(*endptr))) { log_warn(LD_DIRSERV, "Invalid bandwidth in bandwidth file line: %s", escaped(orig_line)); @@ -3612,9 +3605,9 @@ spooled_resource_flush_some(spooled_resource_t *spooled, return SRFS_DONE; } if (conn->compress_state) { - connection_write_to_buf_compress((const char*)body, bodylen, conn, 0); + connection_buf_add_compress((const char*)body, bodylen, conn, 0); } else { - connection_write_to_buf((const char*)body, bodylen, TO_CONN(conn)); + connection_buf_add((const char*)body, bodylen, TO_CONN(conn)); } return SRFS_DONE; } else { @@ -3651,11 +3644,11 @@ spooled_resource_flush_some(spooled_resource_t *spooled, return SRFS_ERR; ssize_t bytes = (ssize_t) MIN(DIRSERV_CACHED_DIR_CHUNK_SIZE, remaining); if (conn->compress_state) { - connection_write_to_buf_compress( + connection_buf_add_compress( ptr + spooled->cached_dir_offset, bytes, conn, 0); } else { - connection_write_to_buf(ptr + spooled->cached_dir_offset, + connection_buf_add(ptr + spooled->cached_dir_offset, bytes, TO_CONN(conn)); } spooled->cached_dir_offset += bytes; @@ -3920,7 +3913,7 @@ connection_dirserv_flushed_some(dir_connection_t *conn) if (conn->compress_state) { /* Flush the compression state: there could be more bytes pending in there, * and we don't want to omit bytes. */ - connection_write_to_buf_compress("", 0, conn, 1); + connection_buf_add_compress("", 0, conn, 1); tor_compress_free(conn->compress_state); conn->compress_state = NULL; } diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c index 01dc06ce13..c22a2f13d8 100644 --- a/src/or/ext_orport.c +++ b/src/or/ext_orport.c @@ -23,8 +23,9 @@ #include "ext_orport.h" #include "control.h" #include "config.h" -#include "util.h" #include "main.h" +#include "proto_ext_or.h" +#include "util.h" /** Allocate and return a structure capable of holding an Extended * ORPort message of body length <b>len</b>. */ @@ -69,10 +70,10 @@ connection_write_ext_or_command(connection_t *conn, return -1; set_uint16(header, htons(command)); set_uint16(header+2, htons(bodylen)); - connection_write_to_buf(header, 4, conn); + connection_buf_add(header, 4, conn); if (bodylen) { tor_assert(body); - connection_write_to_buf(body, bodylen, conn); + connection_buf_add(body, bodylen, conn); } return 0; } @@ -170,7 +171,7 @@ connection_ext_or_auth_neg_auth_type(connection_t *conn) if (connection_get_inbuf_len(conn) < 1) return 0; - if (connection_fetch_from_buf(authtype, 1, conn) < 0) + if (connection_buf_get_bytes(authtype, 1, conn) < 0) return -1; log_debug(LD_GENERAL, "Client wants us to use %d auth type", authtype[0]); @@ -310,7 +311,7 @@ connection_ext_or_auth_handle_client_nonce(connection_t *conn) if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_NONCE_LEN) return 0; - if (connection_fetch_from_buf(client_nonce, + if (connection_buf_get_bytes(client_nonce, EXT_OR_PORT_AUTH_NONCE_LEN, conn) < 0) return -1; @@ -325,7 +326,7 @@ connection_ext_or_auth_handle_client_nonce(connection_t *conn) &reply, &reply_len) < 0) return -1; - connection_write_to_buf(reply, reply_len, conn); + connection_buf_add(reply, reply_len, conn); memwipe(reply, 0, reply_len); tor_free(reply); @@ -347,9 +348,9 @@ static void connection_ext_or_auth_send_result(connection_t *conn, int success) { if (success) - connection_write_to_buf("\x01", 1, conn); + connection_buf_add("\x01", 1, conn); else - connection_write_to_buf("\x00", 1, conn); + connection_buf_add("\x00", 1, conn); } /** Receive the client's hash from <b>conn</b>, validate that it's @@ -367,7 +368,7 @@ connection_ext_or_auth_handle_client_hash(connection_t *conn) if (connection_get_inbuf_len(conn) < EXT_OR_PORT_AUTH_HASH_LEN) return 0; - if (connection_fetch_from_buf(provided_client_hash, + if (connection_buf_get_bytes(provided_client_hash, EXT_OR_PORT_AUTH_HASH_LEN, conn) < 0) return -1; @@ -637,7 +638,7 @@ connection_ext_or_start_auth(or_connection_t *or_conn) log_debug(LD_GENERAL, "ExtORPort authentication: Sending supported authentication types"); - connection_write_to_buf((const char *)authtypes, sizeof(authtypes), conn); + connection_buf_add((const char *)authtypes, sizeof(authtypes), conn); conn->state = EXT_OR_CONN_STATE_AUTH_WAIT_AUTH_TYPE; return 0; diff --git a/src/or/geoip.c b/src/or/geoip.c index 65d00b8659..3944b2cf69 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -1665,6 +1665,15 @@ getinfo_helper_geoip(control_connection_t *control_conn, sa_family_t family; tor_addr_t addr; question += strlen("ip-to-country/"); + + if (!strcmp(question, "ipv4-available") || + !strcmp(question, "ipv6-available")) { + family = !strcmp(question, "ipv4-available") ? AF_INET : AF_INET6; + const int available = geoip_is_loaded(family); + tor_asprintf(answer, "%d", !! available); + return 0; + } + family = tor_addr_parse(&addr, question); if (family != AF_INET && family != AF_INET6) { *errmsg = "Invalid address family"; diff --git a/src/or/hibernate.c b/src/or/hibernate.c index f54109fc90..8c48a6f47d 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -587,7 +587,10 @@ accounting_set_wakeup_time(void) char buf[ISO_TIME_LEN+1]; format_iso_time(buf, interval_start_time); - crypto_pk_get_digest(get_server_identity_key(), digest); + if (crypto_pk_get_digest(get_server_identity_key(), digest) < 0) { + log_err(LD_BUG, "Error getting our key's digest."); + tor_assert(0); + } d_env = crypto_digest_new(); crypto_digest_add_bytes(d_env, buf, ISO_TIME_LEN); diff --git a/src/or/hs_cache.c b/src/or/hs_cache.c index 6962c5ce44..78e2adacf6 100644 --- a/src/or/hs_cache.c +++ b/src/or/hs_cache.c @@ -726,6 +726,23 @@ hs_cache_clean_as_client(time_t now) cache_clean_v3_as_client(now); } +/* Purge the client descriptor cache. */ +void +hs_cache_purge_as_client(void) +{ + DIGEST256MAP_FOREACH_MODIFY(hs_cache_v3_client, key, + hs_cache_client_descriptor_t *, entry) { + size_t entry_size = cache_get_client_entry_size(entry); + MAP_DEL_CURRENT(key); + cache_client_desc_free(entry); + /* Update our OOM. We didn't use the remove() function because we are in + * a loop so we have to explicitely decrement. */ + rend_cache_decrement_allocation(entry_size); + } DIGEST256MAP_FOREACH_END; + + log_info(LD_REND, "Hidden service client descriptor cache purged."); +} + /* For a given service identity public key and an introduction authentication * key, note the given failure in the client intro state cache. */ void @@ -779,6 +796,20 @@ hs_cache_client_intro_state_clean(time_t now) } DIGEST256MAP_FOREACH_END; } +/* Purge the client introduction state cache. */ +void +hs_cache_client_intro_state_purge(void) +{ + DIGEST256MAP_FOREACH_MODIFY(hs_cache_client_intro_state, key, + hs_cache_client_intro_state_t *, cache) { + MAP_DEL_CURRENT(key); + cache_client_intro_state_free(cache); + } DIGEST256MAP_FOREACH_END; + + log_info(LD_REND, "Hidden service client introduction point state " + "cache purged."); +} + /**************** Generics *********************************/ /* Do a round of OOM cleanup on all directory caches. Return the amount of diff --git a/src/or/hs_cache.h b/src/or/hs_cache.h index 2a4d2dbb2f..8dbc842b95 100644 --- a/src/or/hs_cache.h +++ b/src/or/hs_cache.h @@ -84,6 +84,7 @@ hs_cache_lookup_as_client(const ed25519_public_key_t *key); int hs_cache_store_as_client(const char *desc_str, const ed25519_public_key_t *identity_pk); void hs_cache_clean_as_client(time_t now); +void hs_cache_purge_as_client(void); /* Client failure cache. */ void hs_cache_client_intro_state_note(const ed25519_public_key_t *service_pk, @@ -93,6 +94,7 @@ const hs_cache_intro_state_t *hs_cache_client_intro_state_find( const ed25519_public_key_t *service_pk, const ed25519_public_key_t *auth_key); void hs_cache_client_intro_state_clean(time_t now); +void hs_cache_client_intro_state_purge(void); #ifdef HS_CACHE_PRIVATE diff --git a/src/or/hs_circuitmap.c b/src/or/hs_circuitmap.c index 09704d796c..63d5c1ba2e 100644 --- a/src/or/hs_circuitmap.c +++ b/src/or/hs_circuitmap.c @@ -407,9 +407,20 @@ hs_circuitmap_get_rend_circ_service_side(const uint8_t *cookie) } /* Public function: Return client-side rendezvous circuit with rendezvous - * <b>cookie</b>. It will first lookup for the CIRCUIT_PURPOSE_C_REND_READY - * purpose and then try for CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED and then - * finally tries for CIRCUIT_PURPOSE_C_ESTABLISH_REND. + * <b>cookie</b>. It will look for circuits with the following purposes: + + * a) CIRCUIT_PURPOSE_C_REND_READY: Established rend circuit (received + * RENDEZVOUS_ESTABLISHED). Waiting for RENDEZVOUS2 from service, and for + * INTRODUCE_ACK from intro point. + * + * b) CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED: Established rend circuit and + * introduce circuit acked. Waiting for RENDEZVOUS2 from service. + * + * c) CIRCUIT_PURPOSE_C_REND_JOINED: Established rend circuit and received + * RENDEZVOUS2 from service. + * + * d) CIRCUIT_PURPOSE_C_ESTABLISH_REND: Rend circuit open but not yet + * established. * * Return NULL if no such circuit is found in the circuitmap. */ origin_circuit_t * @@ -433,6 +444,13 @@ hs_circuitmap_get_rend_circ_client_side(const uint8_t *cookie) circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, REND_TOKEN_LEN, cookie, + CIRCUIT_PURPOSE_C_REND_JOINED); + if (circ) { + return circ; + } + + circ = hs_circuitmap_get_origin_circuit(HS_TOKEN_REND_CLIENT_SIDE, + REND_TOKEN_LEN, cookie, CIRCUIT_PURPOSE_C_ESTABLISH_REND); return circ; } diff --git a/src/or/hs_client.c b/src/or/hs_client.c index c0e24ac85c..cde0231d9d 100644 --- a/src/or/hs_client.c +++ b/src/or/hs_client.c @@ -6,6 +6,8 @@ * \brief Implement next generation hidden service client functionality **/ +#define HS_CLIENT_PRIVATE + #include "or.h" #include "hs_circuit.h" #include "hs_ident.h" @@ -24,10 +26,36 @@ #include "circuitlist.h" #include "circuituse.h" #include "connection.h" +#include "nodelist.h" #include "circpathbias.h" #include "connection.h" #include "hs_ntor.h" #include "circuitbuild.h" +#include "networkstatus.h" + +/* Cancel all descriptor fetches currently in progress. */ +static void +cancel_descriptor_fetches(void) +{ + smartlist_t *conns = + connection_list_by_type_state(CONN_TYPE_DIR, DIR_PURPOSE_FETCH_HSDESC); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { + const hs_ident_dir_conn_t *ident = TO_DIR_CONN(conn)->hs_ident; + if (BUG(ident == NULL)) { + /* A directory connection fetching a service descriptor can't have an + * empty hidden service identifier. */ + continue; + } + log_debug(LD_REND, "Marking for close a directory connection fetching " + "a hidden service descriptor for service %s.", + safe_str_client(ed25519_fmt(&ident->identity_pk))); + connection_mark_for_close(conn); + } SMARTLIST_FOREACH_END(conn); + + /* No ownership of the objects in this list. */ + smartlist_free(conns); + log_info(LD_REND, "Hidden service client descriptor fetches cancelled."); +} /* Get all connections that are waiting on a circuit and flag them back to * waiting for a hidden service descriptor for the given service key @@ -57,15 +85,49 @@ flag_all_conn_wait_desc(const ed25519_public_key_t *service_identity_pk) smartlist_free(conns); } +/* Remove tracked HSDir requests from our history for this hidden service + * identity public key. */ +static void +purge_hid_serv_request(const ed25519_public_key_t *identity_pk) +{ + char base64_blinded_pk[ED25519_BASE64_LEN + 1]; + ed25519_public_key_t blinded_pk; + + tor_assert(identity_pk); + + /* Get blinded pubkey of hidden service. It is possible that we just moved + * to a new time period meaning that we won't be able to purge the request + * from the previous time period. That is fine because they will expire at + * some point and we don't care about those anymore. */ + hs_build_blinded_pubkey(identity_pk, NULL, 0, + hs_get_time_period_num(approx_time()), &blinded_pk); + if (BUG(ed25519_public_to_base64(base64_blinded_pk, &blinded_pk) < 0)) { + return; + } + /* Purge last hidden service request from cache for this blinded key. */ + hs_purge_hid_serv_from_last_hid_serv_requests(base64_blinded_pk); +} + /* A v3 HS circuit successfully connected to the hidden service. Update the * stream state at <b>hs_conn_ident</b> appropriately. */ static void note_connection_attempt_succeeded(const hs_ident_edge_conn_t *hs_conn_ident) { - (void) hs_conn_ident; - - /* TODO: When implementing client side */ - return; + tor_assert(hs_conn_ident); + + /* Remove from the hid serv cache all requests for that service so we can + * query the HSDir again later on for various reasons. */ + purge_hid_serv_request(&hs_conn_ident->identity_pk); + + /* The v2 subsystem cleans up the intro point time out flag at this stage. + * We don't try to do it here because we still need to keep intact the intro + * point state for future connections. Even though we are able to connect to + * the service, doesn't mean we should reset the timed out intro points. + * + * It is not possible to have successfully connected to an intro point + * present in our cache that was on error or timed out. Every entry in that + * cache have a 2 minutes lifetime so ultimately the intro point(s) state + * will be reset and thus possible to be retried. */ } /* Given the pubkey of a hidden service in <b>onion_identity_pk</b>, fetch its @@ -75,7 +137,7 @@ static int directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, const routerstatus_t *hsdir) { - uint64_t current_time_period = hs_get_time_period_num(approx_time()); + uint64_t current_time_period = hs_get_time_period_num(0); ed25519_public_key_t blinded_pubkey; char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; hs_ident_dir_conn_t hs_conn_dir_ident; @@ -122,12 +184,12 @@ directory_launch_v3_desc_fetch(const ed25519_public_key_t *onion_identity_pk, /** Return the HSDir we should use to fetch the descriptor of the hidden * service with identity key <b>onion_identity_pk</b>. */ -static routerstatus_t * +STATIC routerstatus_t * pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) { int retval; char base64_blinded_pubkey[ED25519_BASE64_LEN + 1]; - uint64_t current_time_period = hs_get_time_period_num(approx_time()); + uint64_t current_time_period = hs_get_time_period_num(0); smartlist_t *responsible_hsdirs; ed25519_public_key_t blinded_pubkey; routerstatus_t *hsdir_rs = NULL; @@ -146,8 +208,8 @@ pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk) } /* Get responsible hsdirs of service for this time period */ - hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period, 0, 1, - responsible_hsdirs); + hs_get_responsible_hsdirs(&blinded_pubkey, current_time_period, + 0, 1, responsible_hsdirs); log_debug(LD_REND, "Found %d responsible HSDirs and about to pick one.", smartlist_len(responsible_hsdirs)); @@ -427,9 +489,21 @@ client_rendezvous_circ_has_opened(origin_circuit_t *circ) tor_assert(circ); tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND); + const extend_info_t *rp_ei = circ->build_state->chosen_exit; + + /* Check that we didn't accidentally choose a node that does not understand + * the v3 rendezvous protocol */ + if (rp_ei) { + const node_t *rp_node = node_get_by_id(rp_ei->identity_digest); + if (rp_node) { + if (BUG(!node_supports_v3_rendezvous_point(rp_node))) { + return; + } + } + } + log_info(LD_REND, "Rendezvous circuit has opened to %s.", - safe_str_client( - extend_info_describe(circ->build_state->chosen_exit))); + safe_str_client(extend_info_describe(rp_ei))); /* Ignore returned value, nothing we can really do. On failure, the circuit * will be marked for close. */ @@ -672,6 +746,14 @@ handle_introduce_ack_success(origin_circuit_t *intro_circ) } assert_circ_anonymity_ok(rend_circ, get_options()); + + /* It is possible to get a RENDEZVOUS2 cell before the INTRODUCE_ACK which + * means that the circuit will be joined and already transmitting data. In + * that case, simply skip the purpose change and close the intro circuit + * like it should be. */ + if (TO_CIRCUIT(rend_circ)->purpose == CIRCUIT_PURPOSE_C_REND_JOINED) { + goto end; + } circuit_change_purpose(TO_CIRCUIT(rend_circ), CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED); /* Set timestamp_dirty, because circuit_expire_building expects it to @@ -859,7 +941,7 @@ hs_client_decode_descriptor(const char *desc_str, /* Create subcredential for this HS so that we can decrypt */ { - uint64_t current_time_period = hs_get_time_period_num(approx_time()); + uint64_t current_time_period = hs_get_time_period_num(0); hs_build_blinded_pubkey(service_identity_pk, NULL, 0, current_time_period, &blinded_pubkey); hs_get_subcredential(service_identity_pk, &blinded_pubkey, subcredential); @@ -869,7 +951,10 @@ hs_client_decode_descriptor(const char *desc_str, ret = hs_desc_decode_descriptor(desc_str, subcredential, desc); memwipe(subcredential, 0, sizeof(subcredential)); if (ret < 0) { - log_warn(LD_GENERAL, "Could not parse received descriptor as client"); + log_warn(LD_GENERAL, "Could not parse received descriptor as client."); + if (get_options()->SafeLogging_ == SAFELOG_SCRUB_NONE) { + log_warn(LD_GENERAL, "%s", escaped(desc_str)); + } goto err; } @@ -932,8 +1017,8 @@ hs_client_refetch_hsdesc(const ed25519_public_key_t *identity_pk) cached_desc = hs_cache_lookup_as_client(identity_pk); if (cached_desc && hs_client_any_intro_points_usable(identity_pk, cached_desc)) { - log_warn(LD_GENERAL, "We would fetch a v3 hidden service descriptor " - "but we already have a useable descriprot."); + log_info(LD_GENERAL, "We would fetch a v3 hidden service descriptor " + "but we already have a usable descriptor."); return 0; } } @@ -1069,7 +1154,9 @@ hs_client_desc_has_arrived(const hs_ident_dir_conn_t *ident) "Closing streams."); connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_RESOLVEFAILED); - /* XXX: Note the connection attempt. */ + /* We are unable to use the descriptor so remove the directory request + * from the cache so the next connection can try again. */ + note_connection_attempt_succeeded(edge_conn->hs_ident); goto end; } @@ -1215,3 +1302,32 @@ hs_client_reextend_intro_circuit(origin_circuit_t *circ) return ret; } +/* Release all the storage held by the client subsystem. */ +void +hs_client_free_all(void) +{ + /* Purge the hidden service request cache. */ + hs_purge_last_hid_serv_requests(); +} + +/* Purge all potentially remotely-detectable state held in the hidden + * service client code. Called on SIGNAL NEWNYM. */ +void +hs_client_purge_state(void) +{ + /* v2 subsystem. */ + rend_client_purge_state(); + + /* Cancel all descriptor fetches. Do this first so once done we are sure + * that our descriptor cache won't modified. */ + cancel_descriptor_fetches(); + /* Purge the introduction point state cache. */ + hs_cache_client_intro_state_purge(); + /* Purge the descriptor cache. */ + hs_cache_purge_as_client(); + /* Purge the last hidden service request cache. */ + hs_purge_last_hid_serv_requests(); + + log_info(LD_REND, "Hidden service client state has been purged."); +} + diff --git a/src/or/hs_client.h b/src/or/hs_client.h index 8ed0501c91..5227704506 100644 --- a/src/or/hs_client.h +++ b/src/or/hs_client.h @@ -46,5 +46,16 @@ extend_info_t *hs_client_get_random_intro_from_edge( int hs_client_reextend_intro_circuit(origin_circuit_t *circ); +void hs_client_purge_state(void); + +void hs_client_free_all(void); + +#ifdef HS_CLIENT_PRIVATE + +STATIC routerstatus_t * +pick_hsdir_v3(const ed25519_public_key_t *onion_identity_pk); + +#endif + #endif /* TOR_HS_CLIENT_H */ diff --git a/src/or/hs_common.c b/src/or/hs_common.c index 03dd07f6ca..291d8ae8da 100644 --- a/src/or/hs_common.c +++ b/src/or/hs_common.c @@ -19,6 +19,7 @@ #include "nodelist.h" #include "hs_cache.h" #include "hs_common.h" +#include "hs_client.h" #include "hs_ident.h" #include "hs_service.h" #include "policies.h" @@ -98,42 +99,65 @@ add_unix_port(smartlist_t *ports, rend_service_port_config_t *p) /* Helper function: The key is a digest that we compare to a node_t object * current hsdir_index. */ static int -compare_digest_to_current_hsdir_index(const void *_key, const void **_member) +compare_digest_to_fetch_hsdir_index(const void *_key, const void **_member) { const char *key = _key; const node_t *node = *_member; - return tor_memcmp(key, node->hsdir_index->current, DIGEST256_LEN); + return tor_memcmp(key, node->hsdir_index->fetch, DIGEST256_LEN); } /* Helper function: The key is a digest that we compare to a node_t object * next hsdir_index. */ static int -compare_digest_to_next_hsdir_index(const void *_key, const void **_member) +compare_digest_to_store_first_hsdir_index(const void *_key, + const void **_member) { const char *key = _key; const node_t *node = *_member; - return tor_memcmp(key, node->hsdir_index->next, DIGEST256_LEN); + return tor_memcmp(key, node->hsdir_index->store_first, DIGEST256_LEN); +} + +/* Helper function: The key is a digest that we compare to a node_t object + * next hsdir_index. */ +static int +compare_digest_to_store_second_hsdir_index(const void *_key, + const void **_member) +{ + const char *key = _key; + const node_t *node = *_member; + return tor_memcmp(key, node->hsdir_index->store_second, DIGEST256_LEN); } /* Helper function: Compare two node_t objects current hsdir_index. */ static int -compare_node_current_hsdir_index(const void **a, const void **b) +compare_node_fetch_hsdir_index(const void **a, const void **b) { const node_t *node1= *a; const node_t *node2 = *b; - return tor_memcmp(node1->hsdir_index->current, - node2->hsdir_index->current, + return tor_memcmp(node1->hsdir_index->fetch, + node2->hsdir_index->fetch, DIGEST256_LEN); } /* Helper function: Compare two node_t objects next hsdir_index. */ static int -compare_node_next_hsdir_index(const void **a, const void **b) +compare_node_store_first_hsdir_index(const void **a, const void **b) { const node_t *node1= *a; const node_t *node2 = *b; - return tor_memcmp(node1->hsdir_index->next, - node2->hsdir_index->next, + return tor_memcmp(node1->hsdir_index->store_first, + node2->hsdir_index->store_first, + DIGEST256_LEN); +} + +/* Helper function: Compare two node_t objects next hsdir_index. */ +static int +compare_node_store_second_hsdir_index(const void **a, const void **b) +{ + const node_t *node1= *a; + const node_t *node2 = *b; + return tor_memcmp(node1->hsdir_index->store_second, + node2->hsdir_index->store_second, DIGEST256_LEN); } @@ -210,15 +234,26 @@ get_time_period_length(void) return (uint64_t) time_period_length; } -/** Get the HS time period number at time <b>now</b> */ +/** Get the HS time period number at time <b>now</b>. If <b>now</b> is not set, + * we try to get the time ourselves. */ uint64_t hs_get_time_period_num(time_t now) { uint64_t time_period_num; + time_t current_time; + + /* If no time is specified, set current time based on consensus time, and + * only fall back to system time if that fails. */ + if (now != 0) { + current_time = now; + } else { + networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); + current_time = ns ? ns->valid_after : approx_time(); + } /* Start by calculating minutes since the epoch */ uint64_t time_period_length = get_time_period_length(); - uint64_t minutes_since_epoch = now / 60; + uint64_t minutes_since_epoch = current_time / 60; /* Apply the rotation offset as specified by prop224 (section * [TIME-PERIODS]), so that new time periods synchronize nicely with SRV @@ -241,6 +276,14 @@ hs_get_next_time_period_num(time_t now) return hs_get_time_period_num(now) + 1; } +/* Get the number of the _previous_ HS time period, given that the current + * time is <b>now</b>. */ +uint64_t +hs_get_previous_time_period_num(time_t now) +{ + return hs_get_time_period_num(now) - 1; +} + /* Return the start time of the upcoming time period based on <b>now</b>. */ time_t hs_get_start_time_of_next_time_period(time_t now) @@ -546,7 +589,7 @@ compute_disaster_srv(uint64_t time_period_num, uint8_t *srv_out) * would have to do it thousands of times in a row, we always cache the * computer disaster SRV (and its corresponding time period num) in case we * want to reuse it soon after. We need to cache two SRVs, one for each active - * time period (in case of overlap mode). + * time period. */ static uint8_t cached_disaster_srv[2][DIGEST256_LEN]; static uint64_t cached_time_period_nums[2] = {0}; @@ -991,10 +1034,22 @@ hs_build_blinded_keypair(const ed25519_keypair_t *kp, memwipe(param, 0, sizeof(param)); } -/* Return true if overlap mode is active given the date in consensus. If - * consensus is NULL, then we use the latest live consensus we can find. */ +/* Return true if we are currently in the time segment between a new time + * period and a new SRV (in the real network that happens between 12:00 and + * 00:00 UTC). Here is a diagram showing exactly when this returns true: + * + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^^^^^^^^^^^^ ^^^^^^^^^^^^ | + * | | + * +------------------------------------------------------------------+ + */ MOCK_IMPL(int, -hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now)) +hs_in_period_between_tp_and_srv,(const networkstatus_t *consensus, time_t now)) { time_t valid_after; time_t srv_start_time, tp_start_time; @@ -1006,19 +1061,18 @@ hs_overlap_mode_is_active, (const networkstatus_t *consensus, time_t now)) } } - /* We consider to be in overlap mode when we are in the period of time - * between a fresh SRV and the beginning of the new time period (in the - * normal network this is between 00:00 (inclusive) and 12:00 UTC - * (exclusive)) */ + /* Get start time of next TP and of current SRV protocol run, and check if we + * are between them. */ valid_after = consensus->valid_after; - srv_start_time =sr_state_get_start_time_of_current_protocol_run(valid_after); + srv_start_time = + sr_state_get_start_time_of_current_protocol_run(valid_after); tp_start_time = hs_get_start_time_of_next_time_period(srv_start_time); if (valid_after >= srv_start_time && valid_after < tp_start_time) { - return 1; + return 0; } - return 0; + return 1; } /* Return 1 if any virtual port in ports needs a circuit with good uptime. @@ -1188,10 +1242,9 @@ hs_get_hsdir_spread_store(void) } /** <b>node</b> is an HSDir so make sure that we have assigned an hsdir index. - * If <b>is_for_next_period</b> is set, also check the next HSDir index field. * Return 0 if everything is as expected, else return -1. */ static int -node_has_hsdir_index(const node_t *node, int is_for_next_period) +node_has_hsdir_index(const node_t *node) { tor_assert(node_supports_v3_hsdir(node)); @@ -1203,14 +1256,18 @@ node_has_hsdir_index(const node_t *node, int is_for_next_period) /* At this point, since the node has a desc, this node must also have an * hsdir index. If not, something went wrong, so BUG out. */ - if (BUG(node->hsdir_index == NULL) || - BUG(tor_mem_is_zero((const char*)node->hsdir_index->current, + if (BUG(node->hsdir_index == NULL)) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->fetch, DIGEST256_LEN))) { return 0; } - - if (is_for_next_period && - BUG(tor_mem_is_zero((const char*)node->hsdir_index->next, + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->store_first, + DIGEST256_LEN))) { + return 0; + } + if (BUG(tor_mem_is_zero((const char*)node->hsdir_index->store_second, DIGEST256_LEN))) { return 0; } @@ -1220,19 +1277,19 @@ node_has_hsdir_index(const node_t *node, int is_for_next_period) /* For a given blinded key and time period number, get the responsible HSDir * and put their routerstatus_t object in the responsible_dirs list. If - * is_next_period is true, the next hsdir_index of the node_t is used. If - * is_client is true, the spread fetch consensus parameter is used else the - * spread store is used which is only for upload. This function can't fail but - * it is possible that the responsible_dirs list contains fewer nodes than - * expected. + * 'use_second_hsdir_index' is true, use the second hsdir_index of the node_t + * is used. If 'for_fetching' is true, the spread fetch consensus parameter is + * used else the spread store is used which is only for upload. This function + * can't fail but it is possible that the responsible_dirs list contains fewer + * nodes than expected. * * This function goes over the latest consensus routerstatus list and sorts it * by their node_t hsdir_index then does a binary search to find the closest * node. All of this makes it a bit CPU intensive so use it wisely. */ void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, - uint64_t time_period_num, int is_next_period, - int is_client, smartlist_t *responsible_dirs) + uint64_t time_period_num, int use_second_hsdir_index, + int for_fetching, smartlist_t *responsible_dirs) { smartlist_t *sorted_nodes; /* The compare function used for the smartlist bsearch. We have two @@ -1259,7 +1316,7 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, node_t *n = node_get_mutable_by_id(rs->identity_digest); tor_assert(n); if (node_supports_v3_hsdir(n) && rs->is_hs_dir) { - if (!node_has_hsdir_index(n, is_next_period)) { + if (!node_has_hsdir_index(n)) { log_info(LD_GENERAL, "Node %s was found without hsdir index.", node_describe(n)); continue; @@ -1276,12 +1333,15 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, /* First thing we have to do is sort all node_t by hsdir_index. The * is_next_period tells us if we want the current or the next one. Set the * bsearch compare function also while we are at it. */ - if (is_next_period) { - smartlist_sort(sorted_nodes, compare_node_next_hsdir_index); - cmp_fct = compare_digest_to_next_hsdir_index; + if (for_fetching) { + smartlist_sort(sorted_nodes, compare_node_fetch_hsdir_index); + cmp_fct = compare_digest_to_fetch_hsdir_index; + } else if (use_second_hsdir_index) { + smartlist_sort(sorted_nodes, compare_node_store_second_hsdir_index); + cmp_fct = compare_digest_to_store_second_hsdir_index; } else { - smartlist_sort(sorted_nodes, compare_node_current_hsdir_index); - cmp_fct = compare_digest_to_current_hsdir_index; + smartlist_sort(sorted_nodes, compare_node_store_first_hsdir_index); + cmp_fct = compare_digest_to_store_first_hsdir_index; } /* For all replicas, we'll select a set of HSDirs using the consensus @@ -1292,8 +1352,8 @@ hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, uint8_t hs_index[DIGEST256_LEN] = {0}; /* Number of node to add to the responsible dirs list depends on if we are * trying to fetch or store. A client always fetches. */ - int n_to_add = (is_client) ? hs_get_hsdir_spread_fetch() : - hs_get_hsdir_spread_store(); + int n_to_add = (for_fetching) ? hs_get_hsdir_spread_fetch() : + hs_get_hsdir_spread_store(); /* Get the index that we should use to select the node. */ hs_build_hs_index(replica, blinded_pk, time_period_num, hs_index); @@ -1357,7 +1417,7 @@ hs_hsdir_requery_period(const or_options_t *options) * * where 'hsdir_identity' is the identity digest of the HSDir node, and * 'hs_identity' is the descriptor ID of the HS in the v2 case, or the ed25519 - * identity public key of the HS in the v3 case. */ + * blinded public key of the HS in the v3 case. */ static strmap_t *last_hid_serv_requests_ = NULL; /** Returns last_hid_serv_requests_, initializing it to a new strmap if @@ -1453,14 +1513,13 @@ hs_purge_hid_serv_from_last_hid_serv_requests(const char *req_key_str) /* XXX: The use of REND_DESC_ID_V2_LEN_BASE32 is very wrong in terms of * semantic, see #23305. */ - /* Length check on the strings we are about to compare. The "key" contains - * both the base32 HSDir identity digest and the requested key at the - * directory. The "req_key_str" can either be a base32 descriptor ID or a - * base64 blinded key which should be the second part of "key". BUG on - * this check because both strings are internally controlled so this - * should never happen. */ - if (BUG((strlen(req_key_str) + REND_DESC_ID_V2_LEN_BASE32) < - strlen(key))) { + /* This strmap contains variable-sized elements so this is a basic length + * check on the strings we are about to compare. The key is variable sized + * since it's composed as follows: + * key = base32(hsdir_identity) + base32(req_key_str) + * where 'req_key_str' is the descriptor ID of the HS in the v2 case, or + * the ed25519 blinded public key of the HS in the v3 case. */ + if (strlen(key) < REND_DESC_ID_V2_LEN_BASE32 + strlen(req_key_str)) { iter = strmap_iter_next(last_hid_serv_requests, iter); continue; } @@ -1701,6 +1760,7 @@ hs_free_all(void) hs_circuitmap_free_all(); hs_service_free_all(); hs_cache_free_all(); + hs_client_free_all(); } /* For the given origin circuit circ, decrement the number of rendezvous diff --git a/src/or/hs_common.h b/src/or/hs_common.h index 79d92d915f..5851578fd6 100644 --- a/src/or/hs_common.h +++ b/src/or/hs_common.h @@ -142,10 +142,14 @@ typedef struct rend_service_port_config_t { /* Hidden service directory index used in a node_t which is set once we set * the consensus. */ typedef struct hsdir_index_t { - /* The hsdir index for the current time period. */ - uint8_t current[DIGEST256_LEN]; - /* The hsdir index for the next time period. */ - uint8_t next[DIGEST256_LEN]; + /* HSDir index to use when fetching a descriptor. */ + uint8_t fetch[DIGEST256_LEN]; + + /* HSDir index used by services to store their first and second + * descriptor. The first descriptor is chronologically older than the second + * one and uses older TP and SRV values. */ + uint8_t store_first[DIGEST256_LEN]; + uint8_t store_second[DIGEST256_LEN]; } hsdir_index_t; void hs_init(void); @@ -193,13 +197,14 @@ void hs_get_subcredential(const ed25519_public_key_t *identity_pk, const ed25519_public_key_t *blinded_pk, uint8_t *subcred_out); +uint64_t hs_get_previous_time_period_num(time_t now); uint64_t hs_get_time_period_num(time_t now); uint64_t hs_get_next_time_period_num(time_t now); time_t hs_get_start_time_of_next_time_period(time_t now); link_specifier_t *hs_link_specifier_dup(const link_specifier_t *lspec); -MOCK_DECL(int, hs_overlap_mode_is_active, +MOCK_DECL(int, hs_in_period_between_tp_and_srv, (const networkstatus_t *consensus, time_t now)); uint8_t *hs_get_current_srv(uint64_t time_period_num, @@ -219,8 +224,9 @@ int32_t hs_get_hsdir_spread_fetch(void); int32_t hs_get_hsdir_spread_store(void); void hs_get_responsible_hsdirs(const ed25519_public_key_t *blinded_pk, - uint64_t time_period_num, int is_next_period, - int is_client, smartlist_t *responsible_dirs); + uint64_t time_period_num, + int use_second_hsdir_index, + int for_fetching, smartlist_t *responsible_dirs); routerstatus_t *hs_pick_hsdir(smartlist_t *responsible_dirs, const char *req_key_str); diff --git a/src/or/hs_descriptor.h b/src/or/hs_descriptor.h index 3e82746c35..61e7a28c2e 100644 --- a/src/or/hs_descriptor.h +++ b/src/or/hs_descriptor.h @@ -33,8 +33,11 @@ struct link_specifier_t; * which is 720 minutes or 43200 seconds. */ #define HS_DESC_MAX_LIFETIME (12 * 60 * 60) /* Lifetime of certificate in the descriptor. This defines the lifetime of the - * descriptor signing key and the cross certification cert of that key. */ -#define HS_DESC_CERT_LIFETIME (36 * 60 * 60) + * descriptor signing key and the cross certification cert of that key. It is + * set to 54 hours because a descriptor can be around for 48 hours and because + * consensuses are used after the hour, add an extra 6 hours to give some time + * for the service to stop using it. */ +#define HS_DESC_CERT_LIFETIME (54 * 60 * 60) /* Length of the salt needed for the encrypted section of a descriptor. */ #define HS_DESC_ENCRYPTED_SALT_LEN 16 /* Length of the secret input needed for the KDF construction which derives diff --git a/src/or/hs_service.c b/src/or/hs_service.c index 0d0db1cd6b..5759aa8127 100644 --- a/src/or/hs_service.c +++ b/src/or/hs_service.c @@ -23,6 +23,7 @@ #include "router.h" #include "routerkeys.h" #include "routerlist.h" +#include "shared_random_state.h" #include "statefile.h" #include "hs_circuit.h" @@ -72,7 +73,13 @@ static const char address_tld[] = "onion"; * loading keys requires that we are an actual running tor process. */ static smartlist_t *hs_service_staging_list; +/** True if the list of available router descriptors might have changed which + * might result in an altered hash ring. Check if the hash ring changed and + * reupload if needed */ +static int consider_republishing_hs_descriptors = 0; + static void set_descriptor_revision_counter(hs_descriptor_t *hs_desc); +static void move_descriptors(hs_service_t *src, hs_service_t *dst); /* Helper: Function to compare two objects in the service map. Return 1 if the * two service have the same master public identity key. */ @@ -709,37 +716,6 @@ close_service_circuits(hs_service_t *service) close_service_rp_circuits(service); } -/* Move introduction points from the src descriptor to the dst descriptor. The - * destination service intropoints are wiped out if any before moving. */ -static void -move_descriptor_intro_points(hs_service_descriptor_t *src, - hs_service_descriptor_t *dst) -{ - tor_assert(src); - tor_assert(dst); - - digest256map_free(dst->intro_points.map, service_intro_point_free_); - dst->intro_points.map = src->intro_points.map; - /* Nullify the source. */ - src->intro_points.map = NULL; -} - -/* Move introduction points from the src service to the dst service. The - * destination service intropoints are wiped out if any before moving. */ -static void -move_intro_points(hs_service_t *src, hs_service_t *dst) -{ - tor_assert(src); - tor_assert(dst); - - if (src->desc_current && dst->desc_current) { - move_descriptor_intro_points(src->desc_current, dst->desc_current); - } - if (src->desc_next && dst->desc_next) { - move_descriptor_intro_points(src->desc_next, dst->desc_next); - } -} - /* Move every ephemeral services from the src service map to the dst service * map. It is possible that a service can't be register to the dst map which * won't stop the process of moving them all but will trigger a log warn. */ @@ -780,6 +756,25 @@ service_escaped_dir(const hs_service_t *s) escaped(s->config.directory_path); } +/** Move the hidden service state from <b>src</b> to <b>dst</b>. We do this + * when we receive a SIGHUP: <b>dst</b> is the post-HUP service */ +static void +move_hs_state(hs_service_t *src_service, hs_service_t *dst_service) +{ + tor_assert(src_service); + tor_assert(dst_service); + + hs_service_state_t *src = &src_service->state; + hs_service_state_t *dst = &dst_service->state; + + /* Let's do a shallow copy */ + dst->intro_circ_retry_started_time = src->intro_circ_retry_started_time; + dst->num_intro_circ_launched = src->num_intro_circ_launched; + dst->replay_cache_rend_cookie = src->replay_cache_rend_cookie; + + src->replay_cache_rend_cookie = NULL; /* steal pointer reference */ +} + /* Register services that are in the staging list. Once this function returns, * the global service map will be set with the right content and all non * surviving services will be cleaned up. */ @@ -812,13 +807,15 @@ register_all_services(void) * transfer the intro points to it. */ s = find_service(hs_service_map, &snew->keys.identity_pk); if (s) { - /* Pass ownership of intro points from s (the current service) to snew - * (the newly configured one). */ - move_intro_points(s, snew); + /* Pass ownership of the descriptors from s (the current service) to + * snew (the newly configured one). */ + move_descriptors(s, snew); + move_hs_state(s, snew); /* Remove the service from the global map because after this, we need to * go over the remaining service in that map that aren't surviving the * reload to close their circuits. */ remove_service(hs_service_map, s); + hs_service_free(s); } /* Great, this service is now ready to be added to our new map. */ if (BUG(register_service(new_service_map, snew) < 0)) { @@ -967,8 +964,6 @@ service_descriptor_free(hs_service_descriptor_t *desc) hs_descriptor_free(desc->desc); memwipe(&desc->signing_kp, 0, sizeof(desc->signing_kp)); memwipe(&desc->blinded_kp, 0, sizeof(desc->blinded_kp)); - SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id)); - smartlist_free(desc->hsdir_missing_info); /* Cleanup all intro points. */ digest256map_free(desc->intro_points.map, service_intro_point_free_); digestmap_free(desc->intro_points.failed_id, tor_free_); @@ -988,11 +983,37 @@ service_descriptor_new(void) /* Initialize the intro points map. */ sdesc->intro_points.map = digest256map_new(); sdesc->intro_points.failed_id = digestmap_new(); - sdesc->hsdir_missing_info = smartlist_new(); sdesc->previous_hsdirs = smartlist_new(); return sdesc; } +/* Move descriptor(s) from the src service to the dst service. We do this + * during SIGHUP when we re-create our hidden services. */ +static void +move_descriptors(hs_service_t *src, hs_service_t *dst) +{ + tor_assert(src); + tor_assert(dst); + + if (src->desc_current) { + /* Nothing should be there, but clean it up just in case */ + if (BUG(dst->desc_current)) { + service_descriptor_free(dst->desc_current); + } + dst->desc_current = src->desc_current; + src->desc_current = NULL; + } + + if (src->desc_next) { + /* Nothing should be there, but clean it up just in case */ + if (BUG(dst->desc_next)) { + service_descriptor_free(dst->desc_next); + } + dst->desc_next = src->desc_next; + src->desc_next = NULL; + } +} + /* From the given service, remove all expired failing intro points for each * descriptor. */ static void @@ -1345,28 +1366,81 @@ build_service_descriptor(hs_service_t *service, time_t now, service_descriptor_free(desc); } +/* Build both descriptors for the given service that has just booted up. + * Because it's a special case, it deserves its special function ;). */ +static void +build_descriptors_for_new_service(hs_service_t *service, time_t now) +{ + uint64_t current_desc_tp, next_desc_tp; + + tor_assert(service); + /* These are the conditions for a new service. */ + tor_assert(!service->desc_current); + tor_assert(!service->desc_next); + + /* + * +------------------------------------------------------------------+ + * | | + * | 00:00 12:00 00:00 12:00 00:00 12:00 | + * | SRV#1 TP#1 SRV#2 TP#2 SRV#3 TP#3 | + * | | + * | $==========|-----------$===========|-----------$===========| | + * | ^ ^ | + * | A B | + * +------------------------------------------------------------------+ + * + * Case A: The service boots up before a new time period, the current time + * period is thus TP#1 and the next is TP#2 which for both we have access to + * their SRVs. + * + * Case B: The service boots up inside TP#2, we can't use the TP#3 for the + * next descriptor because we don't have the SRV#3 so the current should be + * TP#1 and next TP#2. + */ + + if (hs_in_period_between_tp_and_srv(NULL, now)) { + /* Case B from the above, inside of the new time period. */ + current_desc_tp = hs_get_previous_time_period_num(0); /* TP#1 */ + next_desc_tp = hs_get_time_period_num(0); /* TP#2 */ + } else { + /* Case A from the above, outside of the new time period. */ + current_desc_tp = hs_get_time_period_num(0); /* TP#1 */ + next_desc_tp = hs_get_next_time_period_num(0); /* TP#2 */ + } + + /* Build descriptors. */ + build_service_descriptor(service, now, current_desc_tp, + &service->desc_current); + build_service_descriptor(service, now, next_desc_tp, + &service->desc_next); + log_info(LD_REND, "Hidden service %s has just started. Both descriptors " + "built. Now scheduled for upload.", + safe_str_client(service->onion_address)); +} + /* Build descriptors for each service if needed. There are conditions to build * a descriptor which are details in the function. */ STATIC void build_all_descriptors(time_t now) { FOR_EACH_SERVICE_BEGIN(service) { - if (service->desc_current == NULL) { - /* This means we just booted up because else this descriptor will never - * be NULL as it should always point to the descriptor that was in - * desc_next after rotation. */ - build_service_descriptor(service, now, hs_get_time_period_num(now), - &service->desc_current); - - log_info(LD_REND, "Hidden service %s current descriptor successfully " - "built. Now scheduled for upload.", - safe_str_client(service->onion_address)); + + /* A service booting up will have both descriptors to NULL. No other cases + * makes both descriptor non existent. */ + if (service->desc_current == NULL && service->desc_next == NULL) { + build_descriptors_for_new_service(service, now); + continue; } - /* A next descriptor to NULL indicate that we need to build a fresh one if - * we are in the overlap period for the _next_ time period since it means - * we either just booted or we just rotated our descriptors. */ - if (hs_overlap_mode_is_active(NULL, now) && service->desc_next == NULL) { - build_service_descriptor(service, now, hs_get_next_time_period_num(now), + + /* Reaching this point means we are pass bootup so at runtime. We should + * *never* have an empty current descriptor. If the next descriptor is + * empty, we'll try to build it for the next time period. This only + * happens when we rotate meaning that we are guaranteed to have a new SRV + * at that point for the next time period. */ + tor_assert(service->desc_current); + + if (service->desc_next == NULL) { + build_service_descriptor(service, now, hs_get_next_time_period_num(0), &service->desc_next); log_info(LD_REND, "Hidden service %s next descriptor successfully " "built. Now scheduled for upload.", @@ -1541,7 +1615,6 @@ service_desc_note_upload(hs_service_descriptor_t *desc, const node_t *hsdir) if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { smartlist_add_strdup(desc->previous_hsdirs, b64_digest); - smartlist_sort_strings(desc->previous_hsdirs); } } @@ -1696,10 +1769,62 @@ cleanup_intro_points(hs_service_t *service, time_t now) } FOR_EACH_DESCRIPTOR_END; } -/** We just entered overlap period and we need to rotate our <b>service</b> - * descriptors */ +/* Set the next rotation time of the descriptors for the given service for the + * time now. */ +static void +set_rotation_time(hs_service_t *service, time_t now) +{ + time_t valid_after; + const networkstatus_t *ns = networkstatus_get_live_consensus(now); + if (ns) { + valid_after = ns->valid_after; + } else { + valid_after = now; + } + + tor_assert(service); + service->state.next_rotation_time = + sr_state_get_start_time_of_current_protocol_run(valid_after) + + sr_state_get_protocol_run_duration(); + + { + char fmt_time[ISO_TIME_LEN + 1]; + format_local_iso_time(fmt_time, service->state.next_rotation_time); + log_info(LD_REND, "Next descriptor rotation time set to %s for %s", + fmt_time, safe_str_client(service->onion_address)); + } +} + +/* Return true iff the service should rotate its descriptor. The time now is + * only used to fetch the live consensus and if none can be found, this + * returns false. */ +static unsigned int +should_rotate_descriptors(hs_service_t *service, time_t now) +{ + const networkstatus_t *ns; + + tor_assert(service); + + ns = networkstatus_get_live_consensus(now); + if (ns == NULL) { + goto no_rotation; + } + + if (ns->valid_after >= service->state.next_rotation_time) { + goto rotation; + } + + no_rotation: + return 0; + rotation: + return 1; +} + +/* Rotate the service descriptors of the given service. The current descriptor + * will be freed, the next one put in as the current and finally the next + * descriptor pointer is NULLified. */ static void -rotate_service_descriptors(hs_service_t *service) +rotate_service_descriptors(hs_service_t *service, time_t now) { if (service->desc_current) { /* Close all IP circuits for the descriptor. */ @@ -1712,11 +1837,12 @@ rotate_service_descriptors(hs_service_t *service) * a descriptor creation for it. */ service->desc_current = service->desc_next; service->desc_next = NULL; + + /* We've just rotated, set the next time for the rotation. */ + set_rotation_time(service, now); } -/* Rotate descriptors for each service if needed. If we are just entering - * the overlap period, rotate them that is point the previous descriptor to - * the current and cleanup the previous one. A non existing current +/* Rotate descriptors for each service if needed. A non existing current * descriptor will trigger a descriptor build for the next time period. */ STATIC void rotate_all_descriptors(time_t now) @@ -1726,34 +1852,25 @@ rotate_all_descriptors(time_t now) * those descriptors are on the same tor instance */ FOR_EACH_SERVICE_BEGIN(service) { - /* We are _not_ in the overlap period so skip rotation. */ - if (!hs_overlap_mode_is_active(NULL, now)) { - service->state.in_overlap_period = 0; - continue; - } - /* We've entered the overlap period already so skip rotation. */ - if (service->state.in_overlap_period) { + + /* Note for a service booting up: Both descriptors are NULL in that case + * so this function might return true if we are in the timeframe for a + * rotation leading to basically swapping two NULL pointers which is + * harmless. However, the side effect is that triggering a rotation will + * update the service state and avoid doing anymore rotations after the + * two descriptors have been built. */ + if (!should_rotate_descriptors(service, now)) { continue; } - /* It's the first time the service encounters the overlap period so flag - * it in order to make sure we don't rotate at next check. */ - service->state.in_overlap_period = 1; - - /* We just entered overlap period: recompute all HSDir indices. We need to - * do this otherwise nodes can get stuck with old HSDir indices until we - * fetch a new consensus, and we might need to reupload our desc before - * that. */ - /* XXX find a better place than rotate_all_descriptors() to do this */ - nodelist_recompute_all_hsdir_indices(); - - /* If we have a next descriptor lined up, rotate the descriptors so that it - * becomes current. */ - if (service->desc_next) { - rotate_service_descriptors(service); - } - log_info(LD_REND, "We've just entered the overlap period. Service %s " - "descriptors have been rotated!", + + tor_assert(service->desc_current); + tor_assert(service->desc_next); + + log_info(LD_REND, "Time to rotate our descriptors (%p / %p) for %s", + service->desc_current, service->desc_next, safe_str_client(service->onion_address)); + + rotate_service_descriptors(service, now); } FOR_EACH_SERVICE_END; } @@ -1767,6 +1884,17 @@ run_housekeeping_event(time_t now) * simply moving things around or removing uneeded elements. */ FOR_EACH_SERVICE_BEGIN(service) { + + /* If the service is starting off, set the rotation time. We can't do that + * at configure time because the get_options() needs to be set for setting + * that time that uses the voting interval. */ + if (service->state.next_rotation_time == 0) { + /* Set the next rotation time of the descriptors. If it's Oct 25th + * 23:47:00, the next rotation time is when the next SRV is computed + * which is at Oct 26th 00:00:00 that is in 13 minutes. */ + set_rotation_time(service, now); + } + /* Cleanup invalid intro points from the service descriptor. */ cleanup_intro_points(service, now); @@ -2036,8 +2164,8 @@ upload_descriptor_to_hsdir(const hs_service_t *service, /* Logging so we know where it was sent. */ { int is_next_desc = (service->desc_next == desc); - const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->next : - hsdir->hsdir_index->current; + const uint8_t *index = (is_next_desc) ? hsdir->hsdir_index->store_second: + hsdir->hsdir_index->store_first; log_info(LD_REND, "Service %s %s descriptor of revision %" PRIu64 " initiated upload request to %s with index %s", safe_str_client(service->onion_address), @@ -2252,7 +2380,7 @@ set_descriptor_revision_counter(hs_descriptor_t *hs_desc) * if PublishHidServDescriptors is false. */ STATIC void upload_descriptor_to_all(const hs_service_t *service, - hs_service_descriptor_t *desc, int for_next_period) + hs_service_descriptor_t *desc) { smartlist_t *responsible_dirs = NULL; @@ -2264,7 +2392,11 @@ upload_descriptor_to_all(const hs_service_t *service, /* The parameter 0 means that we aren't a client so tell the function to use * the spread store consensus paremeter. */ hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, - for_next_period, 0, responsible_dirs); + service->desc_next == desc, 0, responsible_dirs); + + /** Clear list of previous hsdirs since we are about to upload to a new + * list. Let's keep it up to date. */ + service_desc_clear_previous_hsdirs(desc); /* For each responsible HSDir we have, initiate an upload command. */ SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, @@ -2273,18 +2405,6 @@ upload_descriptor_to_all(const hs_service_t *service, /* Getting responsible hsdir implies that the node_t object exists for the * routerstatus_t found in the consensus else we have a problem. */ tor_assert(hsdir_node); - /* Do not upload to an HSDir we don't have a descriptor for. */ - if (!node_has_descriptor(hsdir_node)) { - log_info(LD_REND, "Missing descriptor for HSDir %s. Not uploading " - "descriptor. We'll try later once we have it.", - safe_str_client(node_describe(hsdir_node))); - /* Once we get new directory information, this HSDir will be retried if - * we ever get the descriptor. */ - smartlist_add(desc->hsdir_missing_info, - tor_memdup(hsdir_rs->identity_digest, DIGEST_LEN)); - continue; - } - /* Upload this descriptor to the chosen directory. */ upload_descriptor_to_hsdir(service, desc, hsdir_node); } SMARTLIST_FOREACH_END(hsdir_rs); @@ -2309,6 +2429,44 @@ upload_descriptor_to_all(const hs_service_t *service, return; } +/** The set of HSDirs have changed: check if the change affects our descriptor + * HSDir placement, and if it does, reupload the desc. */ +STATIC int +service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc) +{ + int should_reupload = 0; + smartlist_t *responsible_dirs = smartlist_new(); + + /* No desc upload has happened yet: it will happen eventually */ + if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) { + goto done; + } + + /* Get list of responsible hsdirs */ + hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, + service->desc_next == desc, 0, responsible_dirs); + + /* Check if any new hsdirs have been added to the responsible hsdirs set: + * Iterate over the list of new hsdirs, and reupload if any of them is not + * present in the list of previous hsdirs. + */ + SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) { + char b64_digest[BASE64_DIGEST_LEN+1] = {0}; + digest_to_base64(b64_digest, hsdir_rs->identity_digest); + + if (!smartlist_contains_string(desc->previous_hsdirs, b64_digest)) { + should_reupload = 1; + break; + } + } SMARTLIST_FOREACH_END(hsdir_rs); + + done: + smartlist_free(responsible_dirs); + + return should_reupload; +} + /* Return 1 if the given descriptor from the given service can be uploaded * else return 0 if it can not. */ static int @@ -2381,9 +2539,14 @@ run_upload_descriptor_event(time_t now) /* Run v3+ check. */ FOR_EACH_SERVICE_BEGIN(service) { FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { - int for_next_period = 0; + /* If we were asked to re-examine the hash ring, and it changed, then + schedule an upload */ + if (consider_republishing_hs_descriptors && + service_desc_hsdirs_changed(service, desc)) { + service_desc_schedule_upload(desc, now, 0); + } - /* Can this descriptor be uploaed? */ + /* Can this descriptor be uploaded? */ if (!should_service_upload_descriptor(service, desc, now)) { continue; } @@ -2401,15 +2564,12 @@ run_upload_descriptor_event(time_t now) * accurate because all circuits have been established. */ build_desc_intro_points(service, desc, now); - /* If the service is in the overlap period and this descriptor is the - * next one, it has to be uploaded for the next time period meaning - * we'll use the next node_t hsdir_index to pick the HSDirs. */ - if (desc == service->desc_next) { - for_next_period = 1; - } - upload_descriptor_to_all(service, desc, for_next_period); + upload_descriptor_to_all(service, desc); } FOR_EACH_DESCRIPTOR_END; } FOR_EACH_SERVICE_END; + + /* We are done considering whether to republish rend descriptors */ + consider_republishing_hs_descriptors = 0; } /* Called when the introduction point circuit is done building and ready to be @@ -2482,9 +2642,10 @@ service_rendezvous_circ_has_opened(origin_circuit_t *circ) tor_assert(circ->hs_ident); tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_CONNECT_REND); - /* Declare the circuit dirty to avoid reuse, and for path-bias */ - if (!TO_CIRCUIT(circ)->timestamp_dirty) - TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); + /* Declare the circuit dirty to avoid reuse, and for path-bias. We set the + * timestamp regardless of its content because that circuit could have been + * cannibalized so in any cases, we are about to use that circuit more. */ + TO_CIRCUIT(circ)->timestamp_dirty = time(NULL); pathbias_count_use_attempt(circ); /* Get the corresponding service and intro point. */ @@ -2616,58 +2777,6 @@ service_handle_introduce2(origin_circuit_t *circ, const uint8_t *payload, return -1; } -/* For a given service and a descriptor of that service, consider retrying to - * upload the descriptor to any directories from which we had missing - * information when originally tried to be uploaded. This is called when our - * directory information has changed. */ -static void -consider_hsdir_upload_retry(const hs_service_t *service, - hs_service_descriptor_t *desc) -{ - smartlist_t *responsible_dirs = NULL; - smartlist_t *still_missing_dirs = NULL; - - tor_assert(service); - tor_assert(desc); - - responsible_dirs = smartlist_new(); - still_missing_dirs = smartlist_new(); - - /* We first need to get responsible directories from the latest consensus so - * we can then make sure that the node that we were missing information for - * is still responsible for this descriptor. */ - hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, - service->desc_next == desc, 0, responsible_dirs); - - SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, rs) { - const node_t *node; - const char *id = rs->identity_digest; - if (!smartlist_contains_digest(desc->hsdir_missing_info, id)) { - continue; - } - /* We do need a node_t object and descriptor to perform an upload. If - * found, we remove the id from the missing dir list else we add it to the - * still missing dir list to keep track of id that are still missing. */ - node = node_get_by_id(id); - if (node && node_has_descriptor(node)) { - upload_descriptor_to_hsdir(service, desc, node); - smartlist_remove(desc->hsdir_missing_info, id); - } else { - smartlist_add(still_missing_dirs, tor_memdup(id, DIGEST_LEN)); - } - } SMARTLIST_FOREACH_END(rs); - - /* Switch the still missing dir list with the current missing dir list in - * the descriptor. It is possible that the list ends up empty which is what - * we want if we have no more missing dir. */ - SMARTLIST_FOREACH(desc->hsdir_missing_info, char *, id, tor_free(id)); - smartlist_free(desc->hsdir_missing_info); - desc->hsdir_missing_info = still_missing_dirs; - - /* No ownership of the routerstatus_t object in this list. */ - smartlist_free(responsible_dirs); -} - /* Add to list every filename used by service. This is used by the sandbox * subsystem. */ static void @@ -2690,88 +2799,10 @@ service_add_fnames_to_list(const hs_service_t *service, smartlist_t *list) smartlist_add(list, hs_path_from_filename(s_dir, fname)); } -/** The set of HSDirs have changed: check if the change affects our descriptor - * HSDir placement, and if it does, reupload the desc. */ -static int -service_desc_hsdirs_changed(const hs_service_t *service, - const hs_service_descriptor_t *desc) -{ - int retval = 0; - smartlist_t *responsible_dirs = smartlist_new(); - smartlist_t *b64_responsible_dirs = smartlist_new(); - - /* No desc upload has happened yet: it will happen eventually */ - if (!desc->previous_hsdirs || !smartlist_len(desc->previous_hsdirs)) { - goto done; - } - - /* Get list of responsible hsdirs */ - hs_get_responsible_hsdirs(&desc->blinded_kp.pubkey, desc->time_period_num, - service->desc_next == desc, 0, responsible_dirs); - - /* Make a second list with their b64ed identity digests, so that we can - * compare it with out previous list of hsdirs */ - SMARTLIST_FOREACH_BEGIN(responsible_dirs, const routerstatus_t *, hsdir_rs) { - char b64_digest[BASE64_DIGEST_LEN+1] = {0}; - digest_to_base64(b64_digest, hsdir_rs->identity_digest); - smartlist_add_strdup(b64_responsible_dirs, b64_digest); - } SMARTLIST_FOREACH_END(hsdir_rs); - - /* Sort this new smartlist so that we can compare it with the other one */ - smartlist_sort_strings(b64_responsible_dirs); - - /* Check whether the set of HSDirs changed */ - if (!smartlist_strings_eq(b64_responsible_dirs, desc->previous_hsdirs)) { - log_info(LD_GENERAL, "Received new dirinfo and set of hsdirs changed!"); - retval = 1; - } else { - log_debug(LD_GENERAL, "No change in hsdir set!"); - } - - done: - smartlist_free(responsible_dirs); - - SMARTLIST_FOREACH(b64_responsible_dirs, char*, s, tor_free(s)); - smartlist_free(b64_responsible_dirs); - - return retval; -} - /* ========== */ /* Public API */ /* ========== */ -/* We just received a new batch of descriptors which might affect the shape of - * the HSDir hash ring. Signal that we should re-upload our HS descriptors. */ -void -hs_hsdir_set_changed_consider_reupload(void) -{ - time_t now = approx_time(); - - /* Check if HS subsystem is initialized */ - if (!hs_service_map) { - return; - } - - /* Basic test: If we have not bootstrapped 100% yet, no point in even trying - to upload descriptor. */ - if (!router_have_minimum_dir_info()) { - return; - } - - log_info(LD_GENERAL, "Received new dirinfo: Checking hash ring for changes"); - - /* Go over all descriptors and check if the set of HSDirs changed for any of - * them. Schedule reupload if so. */ - FOR_EACH_SERVICE_BEGIN(service) { - FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { - if (service_desc_hsdirs_changed(service, desc)) { - service_desc_schedule_upload(desc, now, 0); - } - } FOR_EACH_DESCRIPTOR_END; - } FOR_EACH_SERVICE_END; -} - /* Return the number of service we have configured and usable. */ unsigned int hs_service_get_num_services(void) @@ -2941,22 +2972,14 @@ hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, } /* Called when our internal view of the directory has changed. We might have - * new descriptors for hidden service directories that we didn't have before - * so try them if it's the case. */ + * received a new batch of descriptors which might affect the shape of the + * HSDir hash ring. Signal that we should reexamine the hash ring and + * re-upload our HS descriptors if needed. */ void hs_service_dir_info_changed(void) { - /* For each service we have, check every descriptor and consider retrying to - * upload it to directories that we might have had missing information - * previously that is missing a router descriptor. */ - FOR_EACH_SERVICE_BEGIN(service) { - FOR_EACH_DESCRIPTOR_BEGIN(service, desc) { - /* This cleans up the descriptor missing hsdir information list if a - * successful upload is made or if any of the directory aren't - * responsible anymore for the service descriptor. */ - consider_hsdir_upload_retry(service, desc); - } FOR_EACH_DESCRIPTOR_END; - } FOR_EACH_SERVICE_END; + log_info(LD_REND, "New dirinfo arrived: consider reuploading descriptor"); + consider_republishing_hs_descriptors = 1; } /* Called when we get an INTRODUCE2 cell on the circ. Respond to the cell and @@ -3122,6 +3145,7 @@ hs_service_new(const or_options_t *options) /* Allocate the CLIENT_PK replay cache in service state. */ service->state.replay_cache_rend_cookie = replaycache_new(REND_REPLAY_TIME_INTERVAL, REND_REPLAY_TIME_INTERVAL); + return service; } diff --git a/src/or/hs_service.h b/src/or/hs_service.h index 5bd19931fc..248df27e10 100644 --- a/src/or/hs_service.h +++ b/src/or/hs_service.h @@ -123,17 +123,10 @@ typedef struct hs_service_descriptor_t { * couldn't pick any nodes. */ unsigned int missing_intro_points : 1; - /* List of identity digests for hidden service directories to which we - * couldn't upload this descriptor because we didn't have its router - * descriptor at the time. If this list is non-empty, only the relays in this - * list are re-tried to upload this descriptor when our directory information - * have been updated. */ - smartlist_t *hsdir_missing_info; - /** List of the responsible HSDirs (their b64ed identity digest) last time we * uploaded this descriptor. If the set of responsible HSDirs is different * from this list, this means we received new dirinfo and we need to - * reupload our descriptor. This list is always sorted lexicographically. */ + * reupload our descriptor. */ smartlist_t *previous_hsdirs; } hs_service_descriptor_t; @@ -203,16 +196,16 @@ typedef struct hs_service_state_t { * should never go over MAX_INTRO_CIRCS_PER_PERIOD. */ unsigned int num_intro_circ_launched; - /* Indicate that the service has entered the overlap period. We use this - * flag to check for descriptor rotation. */ - unsigned int in_overlap_period : 1; - /* Replay cache tracking the REND_COOKIE found in INTRODUCE2 cell to detect * repeats. Clients may send INTRODUCE1 cells for the same rendezvous point * through two or more different introduction points; when they do, this * keeps us from launching multiple simultaneous attempts to connect to the * same rend point. */ replaycache_t *replay_cache_rend_cookie; + + /* When is the next time we should rotate our descriptors. This is has to be + * done at the start time of the next SRV protocol run. */ + time_t next_rotation_time; } hs_service_state_t; /* Representation of a service running on this tor instance. */ @@ -236,8 +229,7 @@ typedef struct hs_service_t { /* Current descriptor. */ hs_service_descriptor_t *desc_current; - /* Next descriptor that we need for the overlap period for which we have to - * keep two sets of opened introduction point circuits. */ + /* Next descriptor. */ hs_service_descriptor_t *desc_next; /* XXX: Credential (client auth.) #20700. */ @@ -266,7 +258,6 @@ void hs_service_lists_fnames_for_sandbox(smartlist_t *file_list, smartlist_t *dir_list); int hs_service_set_conn_addr_port(const origin_circuit_t *circ, edge_connection_t *conn); -void hs_hsdir_set_changed_consider_reupload(void); void hs_service_dir_info_changed(void); void hs_service_run_scheduled_events(time_t now); @@ -346,13 +337,15 @@ STATIC int write_address_to_file(const hs_service_t *service, const char *fname_); STATIC void upload_descriptor_to_all(const hs_service_t *service, - hs_service_descriptor_t *desc, - int for_next_period); + hs_service_descriptor_t *desc); STATIC void service_desc_schedule_upload(hs_service_descriptor_t *desc, time_t now, int descriptor_changed); +STATIC int service_desc_hsdirs_changed(const hs_service_t *service, + const hs_service_descriptor_t *desc); + #endif /* TOR_UNIT_TESTS */ #endif /* HS_SERVICE_PRIVATE */ diff --git a/src/or/include.am b/src/or/include.am index 69b505fcd4..021f5f9d5d 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -20,7 +20,6 @@ EXTRA_DIST+= src/or/ntmain.c src/or/Makefile.nmake LIBTOR_A_SOURCES = \ src/or/addressmap.c \ src/or/bridges.c \ - src/or/buffers.c \ src/or/channel.c \ src/or/channelpadding.c \ src/or/channeltls.c \ @@ -79,6 +78,11 @@ LIBTOR_A_SOURCES = \ src/or/parsecommon.c \ src/or/periodic.c \ src/or/protover.c \ + src/or/proto_cell.c \ + src/or/proto_control0.c \ + src/or/proto_ext_or.c \ + src/or/proto_http.c \ + src/or/proto_socks.c \ src/or/policies.c \ src/or/reasons.c \ src/or/relay.c \ @@ -150,7 +154,6 @@ endif ORHEADERS = \ src/or/addressmap.h \ src/or/bridges.h \ - src/or/buffers.h \ src/or/channel.h \ src/or/channelpadding.h \ src/or/channeltls.h \ @@ -215,6 +218,11 @@ ORHEADERS = \ src/or/periodic.h \ src/or/policies.h \ src/or/protover.h \ + src/or/proto_cell.h \ + src/or/proto_control0.h \ + src/or/proto_ext_or.h \ + src/or/proto_http.h \ + src/or/proto_socks.h \ src/or/reasons.h \ src/or/relay.h \ src/or/rendcache.h \ diff --git a/src/or/main.c b/src/or/main.c index 38ebe3f257..c987ddc61b 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -52,6 +52,7 @@ #include "backtrace.h" #include "bridges.h" #include "buffers.h" +#include "buffers_tls.h" #include "channel.h" #include "channeltls.h" #include "channelpadding.h" @@ -80,6 +81,7 @@ #include "hibernate.h" #include "hs_cache.h" #include "hs_circuitmap.h" +#include "hs_client.h" #include "keypin.h" #include "main.h" #include "microdesc.h" @@ -835,7 +837,7 @@ conn_close_if_marked(int i) (int)conn->outbuf_flushlen, conn->marked_for_close_file, conn->marked_for_close); if (conn->linked_conn) { - retval = move_buf_to_buf(conn->linked_conn->inbuf, conn->outbuf, + retval = buf_move_to_buf(conn->linked_conn->inbuf, conn->outbuf, &conn->outbuf_flushlen); if (retval >= 0) { /* The linked conn will notice that it has data when it notices that @@ -849,12 +851,13 @@ conn_close_if_marked(int i) connection_wants_to_flush(conn)); } else if (connection_speaks_cells(conn)) { if (conn->state == OR_CONN_STATE_OPEN) { - retval = flush_buf_tls(TO_OR_CONN(conn)->tls, conn->outbuf, sz, + retval = buf_flush_to_tls(conn->outbuf, TO_OR_CONN(conn)->tls, sz, &conn->outbuf_flushlen); } else retval = -1; /* never flush non-open broken tls connections */ } else { - retval = flush_buf(conn->s, conn->outbuf, sz, &conn->outbuf_flushlen); + retval = buf_flush_to_socket(conn->outbuf, conn->s, sz, + &conn->outbuf_flushlen); } if (retval >= 0 && /* Technically, we could survive things like TLS_WANT_WRITE here. But don't bother for now. */ @@ -1142,7 +1145,7 @@ signewnym_impl(time_t now) circuit_mark_all_dirty_circs_as_unusable(); addressmap_clear_transient(); - rend_client_purge_state(); + hs_client_purge_state(); time_of_last_signewnym = now; signewnym_is_pending = 0; @@ -1483,7 +1486,7 @@ run_scheduled_events(time_t now) /* 12. launch diff computations. (This is free if there are none to * launch.) */ - if (server_mode(options)) { + if (dir_server_mode(options)) { consdiffmgr_rescan(); } } @@ -2110,7 +2113,8 @@ hs_service_callback(time_t now, const or_options_t *options) /* We need to at least be able to build circuits and that we actually have * a working network. */ - if (!have_completed_a_circuit() || net_is_disabled()) { + if (!have_completed_a_circuit() || net_is_disabled() || + networkstatus_get_live_consensus(now) == NULL) { goto end; } @@ -3583,7 +3587,6 @@ sandbox_init_filter(void) /* steals references */ sandbox_cfg_allow_open_filename(&cfg, file_name); sandbox_cfg_allow_open_filename(&cfg, tmp_name); - tor_free(file_name); }); SMARTLIST_FOREACH(dirs, char *, dir, { /* steals reference */ diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 69bff55cff..8b07a88853 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -66,12 +66,6 @@ #include "torcert.h" #include "channelpadding.h" -/** Map from lowercase nickname to identity digest of named server, if any. */ -static strmap_t *named_server_map = NULL; -/** Map from lowercase nickname to (void*)1 for all names that are listed - * as unnamed for some server in the consensus. */ -static strmap_t *unnamed_server_map = NULL; - /** Most recently received and validated v3 "ns"-flavored consensus network * status. */ STATIC networkstatus_t *current_ns_consensus = NULL; @@ -142,7 +136,6 @@ static int have_warned_about_old_version = 0; * listed by the authorities. */ static int have_warned_about_new_version = 0; -static void routerstatus_list_update_named_server_map(void); static void update_consensus_bootstrap_multiple_downloads( time_t now, const or_options_t *options); @@ -250,13 +243,6 @@ router_reload_consensus_networkstatus(void) } } - if (!networkstatus_get_latest_consensus()) { - if (!named_server_map) - named_server_map = strmap_new(); - if (!unnamed_server_map) - unnamed_server_map = strmap_new(); - } - update_certificate_downloads(time(NULL)); routers_update_all_from_networkstatus(time(NULL), 3); @@ -794,41 +780,6 @@ router_get_consensus_status_by_id(const char *digest) return router_get_mutable_consensus_status_by_id(digest); } -/** Given a nickname (possibly verbose, possibly a hexadecimal digest), return - * the corresponding routerstatus_t, or NULL if none exists. Warn the - * user if <b>warn_if_unnamed</b> is set, and they have specified a router by - * nickname, but the Named flag isn't set for that router. */ -const routerstatus_t * -router_get_consensus_status_by_nickname(const char *nickname, - int warn_if_unnamed) -{ - const node_t *node = node_get_by_nickname(nickname, warn_if_unnamed); - if (node) - return node->rs; - else - return NULL; -} - -/** Return the identity digest that's mapped to officially by - * <b>nickname</b>. */ -const char * -networkstatus_get_router_digest_by_nickname(const char *nickname) -{ - if (!named_server_map) - return NULL; - return strmap_get_lc(named_server_map, nickname); -} - -/** Return true iff <b>nickname</b> is disallowed from being the nickname - * of any server. */ -int -networkstatus_nickname_is_unnamed(const char *nickname) -{ - if (!unnamed_server_map) - return 0; - return strmap_get_lc(unnamed_server_map, nickname) != NULL; -} - /** How frequently do directory authorities re-download fresh networkstatus * documents? */ #define AUTHORITY_NS_CACHE_INTERVAL (10*60) @@ -1683,7 +1634,8 @@ any_client_port_set(const or_options_t *options) options->TransPort_set || options->NATDPort_set || options->ControlPort_set || - options->DNSPort_set); + options->DNSPort_set || + options->HTTPTunnelPort_set); } /** @@ -1984,7 +1936,6 @@ networkstatus_set_current_consensus(const char *consensus, update_consensus_networkstatus_fetch_time(now); dirvote_recalculate_timing(options, now); - routerstatus_list_update_named_server_map(); /* Update ewma and adjust policy if needed; first cache the old value */ old_ewma_enabled = cell_ewma_enabled(); @@ -2021,7 +1972,7 @@ networkstatus_set_current_consensus(const char *consensus, &c->digests, c->digest_sha3_as_signed, c->valid_after); - if (server_mode(get_options())) { + if (dir_server_mode(get_options())) { consdiffmgr_add_consensus(consensus, c); } } @@ -2153,31 +2104,6 @@ routers_update_all_from_networkstatus(time_t now, int dir_version) } } -/** Update our view of the list of named servers from the most recently - * retrieved networkstatus consensus. */ -static void -routerstatus_list_update_named_server_map(void) -{ - networkstatus_t *ns = networkstatus_get_latest_consensus(); - if (!ns) - return; - - strmap_free(named_server_map, tor_free_); - named_server_map = strmap_new(); - strmap_free(unnamed_server_map, NULL); - unnamed_server_map = strmap_new(); - smartlist_t *rslist = ns->routerstatus_list; - SMARTLIST_FOREACH_BEGIN(rslist, const routerstatus_t *, rs) { - if (rs->is_named) { - strmap_set_lc(named_server_map, rs->nickname, - tor_memdup(rs->identity_digest, DIGEST_LEN)); - } - if (rs->is_unnamed) { - strmap_set_lc(unnamed_server_map, rs->nickname, (void*)1); - } - } SMARTLIST_FOREACH_END(rs); -} - /** Given a list <b>routers</b> of routerinfo_t *, update each status field * according to our current consensus networkstatus. May re-order * <b>routers</b>. */ @@ -2555,7 +2481,8 @@ getinfo_helper_networkstatus(control_connection_t *conn, } status = router_get_consensus_status_by_id(d); } else if (!strcmpstart(question, "ns/name/")) { - status = router_get_consensus_status_by_nickname(question+8, 0); + const node_t *n = node_get_by_nickname(question+8, 0); + status = n ? n->rs : NULL; } else if (!strcmpstart(question, "ns/purpose/")) { *answer = networkstatus_getinfo_by_purpose(question+11, time(NULL)); return *answer ? 0 : -1; @@ -2662,8 +2589,5 @@ networkstatus_free_all(void) } tor_free(waiting->body); } - - strmap_free(named_server_map, tor_free_); - strmap_free(unnamed_server_map, NULL); } diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index f9320747d2..adaa108904 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -62,11 +62,6 @@ const routerstatus_t *router_get_consensus_status_by_descriptor_digest( MOCK_DECL(routerstatus_t *, router_get_mutable_consensus_status_by_descriptor_digest, (networkstatus_t *consensus, const char *digest)); -const routerstatus_t *router_get_consensus_status_by_nickname( - const char *nickname, - int warn_if_unnamed); -const char *networkstatus_get_router_digest_by_nickname(const char *nickname); -int networkstatus_nickname_is_unnamed(const char *nickname); int we_want_to_fetch_flavor(const or_options_t *options, int flavor); int we_want_to_fetch_unknown_auth_certs(const or_options_t *options); void networkstatus_consensus_download_failed(int status_code, diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 6acc87f967..01a0e9e856 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -38,6 +38,8 @@ * used for authorities and fallback directories.) */ +#define NODELIST_PRIVATE + #include "or.h" #include "address.h" #include "config.h" @@ -92,7 +94,14 @@ typedef struct nodelist_t { smartlist_t *nodes; /* Hash table to map from node ID digest to node. */ HT_HEAD(nodelist_map, node_t) nodes_by_id; - + /* Hash table to map from node Ed25519 ID to node. + * + * Whenever a node's routerinfo or microdescriptor is about to change, + * you should remove it from this map with node_remove_from_ed25519_map(). + * Whenever a node's routerinfo or microdescriptor has just chaned, + * you should add it to this map with node_add_to_ed25519_map(). + */ + HT_HEAD(nodelist_ed_map, node_t) nodes_by_ed_id; } nodelist_t; static inline unsigned int @@ -111,6 +120,23 @@ HT_PROTOTYPE(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq) HT_GENERATE2(nodelist_map, node_t, ht_ent, node_id_hash, node_id_eq, 0.6, tor_reallocarray_, tor_free_) +static inline unsigned int +node_ed_id_hash(const node_t *node) +{ + return (unsigned) siphash24g(node->ed25519_id.pubkey, ED25519_PUBKEY_LEN); +} + +static inline unsigned int +node_ed_id_eq(const node_t *node1, const node_t *node2) +{ + return ed25519_pubkey_eq(&node1->ed25519_id, &node2->ed25519_id); +} + +HT_PROTOTYPE(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, + node_ed_id_eq) +HT_GENERATE2(nodelist_ed_map, node_t, ed_ht_ent, node_ed_id_hash, + node_ed_id_eq, 0.6, tor_reallocarray_, tor_free_) + /** The global nodelist. */ static nodelist_t *the_nodelist=NULL; @@ -121,6 +147,7 @@ init_nodelist(void) if (PREDICT_UNLIKELY(the_nodelist == NULL)) { the_nodelist = tor_malloc_zero(sizeof(nodelist_t)); HT_INIT(nodelist_map, &the_nodelist->nodes_by_id); + HT_INIT(nodelist_ed_map, &the_nodelist->nodes_by_ed_id); the_nodelist->nodes = smartlist_new(); } } @@ -138,6 +165,21 @@ node_get_mutable_by_id(const char *identity_digest) return node; } +/** As node_get_by_ed25519_id, but returns a non-const pointer */ +node_t * +node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id) +{ + node_t search, *node; + if (PREDICT_UNLIKELY(the_nodelist == NULL)) + return NULL; + if (BUG(ed_id == NULL) || BUG(ed25519_public_key_is_zero(ed_id))) + return NULL; + + memcpy(&search.ed25519_id, ed_id, sizeof(search.ed25519_id)); + node = HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, &search); + return node; +} + /** Return the node_t whose identity is <b>identity_digest</b>, or NULL * if no such node exists. */ MOCK_IMPL(const node_t *, @@ -146,6 +188,14 @@ node_get_by_id,(const char *identity_digest)) return node_get_mutable_by_id(identity_digest); } +/** Return the node_t whose ed25519 identity is <b>ed_id</b>, or NULL + * if no such node exists. */ +MOCK_IMPL(const node_t *, +node_get_by_ed25519_id,(const ed25519_public_key_t *ed_id)) +{ + return node_get_mutable_by_ed25519_id(ed_id); +} + /** Internal: return the node_t whose identity_digest is * <b>identity_digest</b>. If none exists, create a new one, add it to the * nodelist, and return it. @@ -173,16 +223,78 @@ node_get_or_create(const char *identity_digest) return node; } +/** Remove <b>node</b> from the ed25519 map (if it present), and + * set its ed25519_id field to zero. */ +static int +node_remove_from_ed25519_map(node_t *node) +{ + tor_assert(the_nodelist); + tor_assert(node); + + if (ed25519_public_key_is_zero(&node->ed25519_id)) { + return 0; + } + + int rv = 0; + node_t *search = + HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + if (BUG(search != node)) { + goto clear_and_return; + } + + search = HT_REMOVE(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + tor_assert(search == node); + rv = 1; + + clear_and_return: + memset(&node->ed25519_id, 0, sizeof(node->ed25519_id)); + return rv; +} + +/** If <b>node</b> has an ed25519 id, and it is not already in the ed25519 id + * map, set its ed25519_id field, and add it to the ed25519 map. + */ +static int +node_add_to_ed25519_map(node_t *node) +{ + tor_assert(the_nodelist); + tor_assert(node); + + if (! ed25519_public_key_is_zero(&node->ed25519_id)) { + return 0; + } + + const ed25519_public_key_t *key = node_get_ed25519_id(node); + if (!key) { + return 0; + } + + node_t *old; + memcpy(&node->ed25519_id, key, sizeof(node->ed25519_id)); + old = HT_FIND(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + if (BUG(old)) { + /* XXXX order matters here, and this may mean that authorities aren't + * pinning. */ + if (old != node) + memset(&node->ed25519_id, 0, sizeof(node->ed25519_id)); + return 0; + } + + HT_INSERT(nodelist_ed_map, &the_nodelist->nodes_by_ed_id, node); + return 1; +} + /* For a given <b>node</b> for the consensus <b>ns</b>, set the hsdir index * for the node, both current and next if possible. This can only fails if the * node_t ed25519 identity key can't be found which would be a bug. */ -static void +STATIC void node_set_hsdir_index(node_t *node, const networkstatus_t *ns) { time_t now = approx_time(); const ed25519_public_key_t *node_identity_pk; - uint8_t *next_hsdir_index_srv = NULL, *current_hsdir_index_srv = NULL; + uint8_t *fetch_srv = NULL, *store_first_srv = NULL, *store_second_srv = NULL; uint64_t next_time_period_num, current_time_period_num; + uint64_t fetch_tp, store_first_tp, store_second_tp; tor_assert(node); tor_assert(ns); @@ -200,41 +312,59 @@ node_set_hsdir_index(node_t *node, const networkstatus_t *ns) goto done; } - /* Get the current and next time period number, we might use them both. */ - current_time_period_num = hs_get_time_period_num(now); - next_time_period_num = hs_get_next_time_period_num(now); - - if (hs_overlap_mode_is_active(ns, now)) { - /* We are in overlap mode, this means that our consensus has just cycled - * from current SRV to previous SRV so for the _next_ upcoming time - * period, we have to use the current SRV and use the previous SRV for the - * current time period. If the current or previous SRV can't be found, the - * disaster one is returned. */ - next_hsdir_index_srv = hs_get_current_srv(next_time_period_num, ns); - /* The following can be confusing so again, in overlap mode, we use our - * previous SRV for our _current_ hsdir index. */ - current_hsdir_index_srv = hs_get_previous_srv(current_time_period_num, ns); + /* Get the current and next time period number. */ + current_time_period_num = hs_get_time_period_num(0); + next_time_period_num = hs_get_next_time_period_num(0); + + /* We always use the current time period for fetching descs */ + fetch_tp = current_time_period_num; + + /* Now extract the needed SRVs and time periods for building hsdir indices */ + if (hs_in_period_between_tp_and_srv(ns, now)) { + fetch_srv = hs_get_current_srv(fetch_tp, ns); + + store_first_tp = hs_get_previous_time_period_num(0); + store_second_tp = current_time_period_num; } else { - /* If NOT in overlap mode, we only need to compute the current hsdir index - * for the ongoing time period and thus the current SRV. If it can't be - * found, the disaster one is returned. */ - current_hsdir_index_srv = hs_get_current_srv(current_time_period_num, ns); - } - - /* Build the current hsdir index. */ - hs_build_hsdir_index(node_identity_pk, current_hsdir_index_srv, - current_time_period_num, node->hsdir_index->current); - if (next_hsdir_index_srv) { - /* Build the next hsdir index if we have a next SRV that we can use. */ - hs_build_hsdir_index(node_identity_pk, next_hsdir_index_srv, - next_time_period_num, node->hsdir_index->next); + fetch_srv = hs_get_previous_srv(fetch_tp, ns); + + store_first_tp = current_time_period_num; + store_second_tp = next_time_period_num; + } + + /* We always use the old SRV for storing the first descriptor and the latest + * SRV for storing the second descriptor */ + store_first_srv = hs_get_previous_srv(store_first_tp, ns); + store_second_srv = hs_get_current_srv(store_second_tp, ns); + + /* Build the fetch index. */ + hs_build_hsdir_index(node_identity_pk, fetch_srv, fetch_tp, + node->hsdir_index->fetch); + + /* If we are in the time segment between SRV#N and TP#N, the fetch index is + the same as the first store index */ + if (!hs_in_period_between_tp_and_srv(ns, now)) { + memcpy(node->hsdir_index->store_first, node->hsdir_index->fetch, + sizeof(node->hsdir_index->store_first)); + } else { + hs_build_hsdir_index(node_identity_pk, store_first_srv, store_first_tp, + node->hsdir_index->store_first); + } + + /* If we are in the time segment between TP#N and SRV#N+1, the fetch index is + the same as the second store index */ + if (hs_in_period_between_tp_and_srv(ns, now)) { + memcpy(node->hsdir_index->store_second, node->hsdir_index->fetch, + sizeof(node->hsdir_index->store_second)); } else { - memset(node->hsdir_index->next, 0, sizeof(node->hsdir_index->next)); + hs_build_hsdir_index(node_identity_pk, store_second_srv, store_second_tp, + node->hsdir_index->store_second); } done: - tor_free(current_hsdir_index_srv); - tor_free(next_hsdir_index_srv); + tor_free(fetch_srv); + tor_free(store_first_srv); + tor_free(store_second_srv); return; } @@ -283,6 +413,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) id_digest = ri->cache_info.identity_digest; node = node_get_or_create(id_digest); + node_remove_from_ed25519_map(node); + if (node->ri) { if (!routers_have_same_or_addrs(node->ri, ri)) { node_addrs_changed(node); @@ -296,6 +428,8 @@ nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out) } node->ri = ri; + node_add_to_ed25519_map(node); + if (node->country == -1) node_set_country(node); @@ -339,8 +473,10 @@ nodelist_add_microdesc(microdesc_t *md) return NULL; node = node_get_mutable_by_id(rs->identity_digest); if (node) { + node_remove_from_ed25519_map(node); if (node->md) node->md->held_by_nodes--; + node->md = md; md->held_by_nodes++; /* Setting the HSDir index requires the ed25519 identity key which can @@ -349,7 +485,9 @@ nodelist_add_microdesc(microdesc_t *md) if (rs->supports_v3_hsdir) { node_set_hsdir_index(node, ns); } + node_add_to_ed25519_map(node); } + return node; } @@ -377,12 +515,14 @@ nodelist_set_consensus(networkstatus_t *ns) if (ns->flavor == FLAV_MICRODESC) { if (node->md == NULL || tor_memneq(node->md->digest,rs->descriptor_digest,DIGEST256_LEN)) { + node_remove_from_ed25519_map(node); if (node->md) node->md->held_by_nodes--; node->md = microdesc_cache_lookup_by_digest256(NULL, rs->descriptor_digest); if (node->md) node->md->held_by_nodes++; + node_add_to_ed25519_map(node); } } @@ -447,6 +587,9 @@ nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md) if (node && node->md == md) { node->md = NULL; md->held_by_nodes--; + if (! node_get_ed25519_id(node)) { + node_remove_from_ed25519_map(node); + } } } @@ -475,6 +618,7 @@ nodelist_drop_node(node_t *node, int remove_from_ht) tmp = HT_REMOVE(nodelist_map, &the_nodelist->nodes_by_id, node); tor_assert(tmp == node); } + node_remove_from_ed25519_map(node); idx = node->nodelist_idx; tor_assert(idx >= 0); @@ -558,6 +702,7 @@ nodelist_free_all(void) return; HT_CLEAR(nodelist_map, &the_nodelist->nodes_by_id); + HT_CLEAR(nodelist_ed_map, &the_nodelist->nodes_by_ed_id); SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { node->nodelist_idx = -1; node_free(node); @@ -622,9 +767,27 @@ nodelist_assert_ok(void) tor_assert(node_sl_idx == node->nodelist_idx); } SMARTLIST_FOREACH_END(node); + /* Every node listed with an ed25519 identity should be listed by that + * identity. + */ + SMARTLIST_FOREACH_BEGIN(the_nodelist->nodes, node_t *, node) { + if (!ed25519_public_key_is_zero(&node->ed25519_id)) { + tor_assert(node == node_get_by_ed25519_id(&node->ed25519_id)); + } + } SMARTLIST_FOREACH_END(node); + + node_t **idx; + HT_FOREACH(idx, nodelist_ed_map, &the_nodelist->nodes_by_ed_id) { + node_t *node = *idx; + tor_assert(node == node_get_by_ed25519_id(&node->ed25519_id)); + } + tor_assert((long)smartlist_len(the_nodelist->nodes) == (long)HT_SIZE(&the_nodelist->nodes_by_id)); + tor_assert((long)smartlist_len(the_nodelist->nodes) >= + (long)HT_SIZE(&the_nodelist->nodes_by_ed_id)); + digestmap_free(dm, NULL); } @@ -641,28 +804,23 @@ nodelist_get_list,(void)) /** Given a hex-encoded nickname of the format DIGEST, $DIGEST, $DIGEST=name, * or $DIGEST~name, return the node with the matching identity digest and * nickname (if any). Return NULL if no such node exists, or if <b>hex_id</b> - * is not well-formed. */ + * is not well-formed. DOCDOC flags */ const node_t * -node_get_by_hex_id(const char *hex_id) +node_get_by_hex_id(const char *hex_id, unsigned flags) { char digest_buf[DIGEST_LEN]; char nn_buf[MAX_NICKNAME_LEN+1]; char nn_char='\0'; + (void) flags; // XXXX + if (hex_digest_nickname_decode(hex_id, digest_buf, &nn_char, nn_buf)==0) { const node_t *node = node_get_by_id(digest_buf); if (!node) return NULL; - if (nn_char) { - const char *real_name = node_get_nickname(node); - if (!real_name || strcasecmp(real_name, nn_buf)) - return NULL; - if (nn_char == '=') { - const char *named_id = - networkstatus_get_router_digest_by_nickname(nn_buf); - if (!named_id || tor_memneq(named_id, digest_buf, DIGEST_LEN)) - return NULL; - } + if (nn_char == '=') { + /* "=" indicates a Named relay, but there aren't any of those now. */ + return NULL; } return node; } @@ -671,42 +829,27 @@ node_get_by_hex_id(const char *hex_id) } /** Given a nickname (possibly verbose, possibly a hexadecimal digest), return - * the corresponding node_t, or NULL if none exists. Warn the user if - * <b>warn_if_unnamed</b> is set, and they have specified a router by - * nickname, but the Named flag isn't set for that router. */ + * the corresponding node_t, or NULL if none exists. Warn the user if they + * have specified a router by nickname, unless the NNF_NO_WARN_UNNAMED bit is + * set in <b>flags</b>. */ MOCK_IMPL(const node_t *, -node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) +node_get_by_nickname,(const char *nickname, unsigned flags)) { + const int warn_if_unnamed = !(flags & NNF_NO_WARN_UNNAMED); + if (!the_nodelist) return NULL; /* Handle these cases: DIGEST, $DIGEST, $DIGEST=name, $DIGEST~name. */ { const node_t *node; - if ((node = node_get_by_hex_id(nickname)) != NULL) + if ((node = node_get_by_hex_id(nickname, flags)) != NULL) return node; } if (!strcasecmp(nickname, UNNAMED_ROUTER_NICKNAME)) return NULL; - /* Okay, so if we get here, the nickname is just a nickname. Is there - * a binding for it in the consensus? */ - { - const char *named_id = - networkstatus_get_router_digest_by_nickname(nickname); - if (named_id) - return node_get_by_id(named_id); - } - - /* Is it marked as owned-by-someone-else? */ - if (networkstatus_nickname_is_unnamed(nickname)) { - log_info(LD_GENERAL, "The name %s is listed as Unnamed: there is some " - "router that holds it, but not one listed in the current " - "consensus.", escaped(nickname)); - return NULL; - } - /* Okay, so the name is not canonical for anybody. */ { smartlist_t *matches = smartlist_new(); @@ -758,22 +901,34 @@ node_get_by_nickname,(const char *nickname, int warn_if_unnamed)) const ed25519_public_key_t * node_get_ed25519_id(const node_t *node) { + const ed25519_public_key_t *ri_pk = NULL; + const ed25519_public_key_t *md_pk = NULL; if (node->ri) { if (node->ri->cache_info.signing_key_cert) { - const ed25519_public_key_t *pk = - &node->ri->cache_info.signing_key_cert->signing_key; - if (BUG(ed25519_public_key_is_zero(pk))) - goto try_the_md; - return pk; + ri_pk = &node->ri->cache_info.signing_key_cert->signing_key; + if (BUG(ed25519_public_key_is_zero(ri_pk))) + ri_pk = NULL; } } - try_the_md: + if (node->md) { if (node->md->ed25519_identity_pkey) { - return node->md->ed25519_identity_pkey; + md_pk = node->md->ed25519_identity_pkey; } } - return NULL; + + if (ri_pk && md_pk) { + if (ed25519_pubkey_eq(ri_pk, md_pk)) { + return ri_pk; + } else { + log_warn(LD_GENERAL, "Inconsistent ed25519 identities in the nodelist"); + return NULL; + } + } else if (ri_pk) { + return ri_pk; + } else { + return md_pk; + } } /** Return true iff this node's Ed25519 identity matches <b>id</b>. @@ -863,6 +1018,28 @@ node_supports_ed25519_hs_intro(const node_t *node) return 0; } +/** Return true iff <b>node</b> supports to be a rendezvous point for hidden + * service version 3 (HSRend=2). */ +int +node_supports_v3_rendezvous_point(const node_t *node) +{ + tor_assert(node); + + if (node->rs) { + return node->rs->supports_v3_rendezvous_point; + } + if (node->ri) { + if (node->ri->protocol_list == NULL) { + return 0; + } + return protocol_list_supports_protocol(node->ri->protocol_list, + PRT_HSREND, + PROTOVER_HS_RENDEZVOUS_POINT_V3); + } + tor_assert_nonfatal_unreached_once(); + return 0; +} + /** Return the RSA ID key's SHA1 digest for the provided node. */ const uint8_t * node_get_rsa_id_digest(const node_t *node) @@ -884,21 +1061,6 @@ node_get_nickname(const node_t *node) return NULL; } -/** Return true iff the nickname of <b>node</b> is canonical, based on the - * latest consensus. */ -int -node_is_named(const node_t *node) -{ - const char *named_id; - const char *nickname = node_get_nickname(node); - if (!nickname) - return 0; - named_id = networkstatus_get_router_digest_by_nickname(nickname); - if (!named_id) - return 0; - return tor_memeq(named_id, node->identity, DIGEST_LEN); -} - /** Return true iff <b>node</b> appears to be a directory authority or * directory cache */ int @@ -946,13 +1108,12 @@ node_get_verbose_nickname(const node_t *node, char *verbose_name_out) { const char *nickname = node_get_nickname(node); - int is_named = node_is_named(node); verbose_name_out[0] = '$'; base16_encode(verbose_name_out+1, HEX_DIGEST_LEN+1, node->identity, DIGEST_LEN); if (!nickname) return; - verbose_name_out[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + verbose_name_out[1+HEX_DIGEST_LEN] = '~'; strlcpy(verbose_name_out+1+HEX_DIGEST_LEN+1, nickname, MAX_NICKNAME_LEN+1); } @@ -1507,8 +1668,7 @@ node_nickname_matches(const node_t *node, const char *nickname) return 1; return hex_digest_nickname_matches(nickname, node->identity, - n, - node_is_named(node)); + n); } /** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ @@ -1610,7 +1770,7 @@ nodelist_add_node_and_family(smartlist_t *sl, const node_t *node) SMARTLIST_FOREACH_BEGIN(declared_family, const char *, name) { const node_t *node2; const smartlist_t *family2; - if (!(node2 = node_get_by_nickname(name, 0))) + if (!(node2 = node_get_by_nickname(name, NNF_NO_WARN_UNNAMED))) continue; if (!(family2 = node_get_declared_family(node2))) continue; @@ -1810,7 +1970,7 @@ router_dir_info_changed(void) { need_to_update_have_min_dir_info = 1; rend_hsdir_routers_changed(); - hs_hsdir_set_changed_consider_reupload(); + hs_service_dir_info_changed(); } /** Return a string describing what we're missing before we have enough diff --git a/src/or/nodelist.h b/src/or/nodelist.h index d16cf0ecf7..427e449ad9 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -18,7 +18,14 @@ node_t *node_get_mutable_by_id(const char *identity_digest); MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest)); -const node_t *node_get_by_hex_id(const char *identity_digest); +node_t *node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id); +MOCK_DECL(const node_t *, node_get_by_ed25519_id, + (const ed25519_public_key_t *ed_id)); + +#define NNF_NO_WARN_UNNAMED (1u<<0) + +const node_t *node_get_by_hex_id(const char *identity_digest, + unsigned flags); node_t *nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out); node_t *nodelist_add_microdesc(microdesc_t *md); void nodelist_set_consensus(networkstatus_t *ns); @@ -34,12 +41,11 @@ void nodelist_free_all(void); void nodelist_assert_ok(void); MOCK_DECL(const node_t *, node_get_by_nickname, - (const char *nickname, int warn_if_unnamed)); + (const char *nickname, unsigned flags)); void node_get_verbose_nickname(const node_t *node, char *verbose_name_out); void node_get_verbose_nickname_by_id(const char *id_digest, char *verbose_name_out); -int node_is_named(const node_t *node); int node_is_dir(const node_t *node); int node_has_descriptor(const node_t *node); int node_get_purpose(const node_t *node); @@ -62,6 +68,7 @@ int node_ed25519_id_matches(const node_t *node, int node_supports_ed25519_link_authentication(const node_t *node); int node_supports_v3_hsdir(const node_t *node); int node_supports_ed25519_hs_intro(const node_t *node); +int node_supports_v3_rendezvous_point(const node_t *node); const uint8_t *node_get_rsa_id_digest(const node_t *node); int node_has_ipv6_addr(const node_t *node); @@ -135,5 +142,16 @@ void router_dir_info_changed(void); const char *get_dir_info_status_string(void); int count_loading_descriptors_progress(void); +#ifdef NODELIST_PRIVATE + +#ifdef TOR_UNIT_TESTS + +STATIC void +node_set_hsdir_index(node_t *node, const networkstatus_t *ns); + +#endif /* TOR_UNIT_TESTS */ + +#endif /* NODELIST_PRIVATE */ + #endif diff --git a/src/or/or.h b/src/or/or.h index 78b658a793..de03a99035 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -226,8 +226,10 @@ typedef enum { #define CONN_TYPE_EXT_OR 16 /** Type for sockets listening for Extended ORPort connections. */ #define CONN_TYPE_EXT_OR_LISTENER 17 +/** Type for sockets listening for HTTP CONNECT tunnel connections. */ +#define CONN_TYPE_AP_HTTP_CONNECT_LISTENER 18 -#define CONN_TYPE_MAX_ 17 +#define CONN_TYPE_MAX_ 19 /* !!!! If _CONN_TYPE_MAX is ever over 31, we must grow the type field in * connection_t. */ @@ -348,7 +350,9 @@ typedef enum { /** State for a transparent natd connection: waiting for original * destination. */ #define AP_CONN_STATE_NATD_WAIT 12 -#define AP_CONN_STATE_MAX_ 12 +/** State for an HTTP tunnel: waiting for an HTTP CONNECT command. */ +#define AP_CONN_STATE_HTTP_CONNECT_WAIT 13 +#define AP_CONN_STATE_MAX_ 13 /** True iff the AP_CONN_STATE_* value <b>s</b> means that the corresponding * edge connection is not attached to any circuit. */ @@ -648,6 +652,10 @@ typedef enum { /** The target address is in a private network (like 127.0.0.1 or 10.0.0.1); * you don't want to do that over a randomly chosen exit */ #define END_STREAM_REASON_PRIVATE_ADDR 262 +/** This is an HTTP tunnel connection and the client used or misused HTTP in a + * way we can't handle. + */ +#define END_STREAM_REASON_HTTPPROTOCOL 263 /** Bitwise-and this value with endreason to mask out all flags. */ #define END_STREAM_REASON_MASK 511 @@ -1179,11 +1187,8 @@ typedef struct { uint16_t length; /**< How long is the payload body? */ } relay_header_t; -typedef struct buf_t buf_t; typedef struct socks_request_t socks_request_t; -#define buf_t buf_t - typedef struct entry_port_cfg_t { /* Client port types (socks, dns, trans, natd) only: */ uint8_t isolation_flags; /**< Zero or more isolation flags */ @@ -1243,6 +1248,8 @@ typedef struct server_port_cfg_t { #define CONTROL_CONNECTION_MAGIC 0x8abc765du #define LISTENER_CONNECTION_MAGIC 0x1a1ac741u +struct buf_t; + /** Description of a connection to another host or process, and associated * data. * @@ -1314,8 +1321,9 @@ typedef struct connection_t { struct event *read_event; /**< Libevent event structure. */ struct event *write_event; /**< Libevent event structure. */ - buf_t *inbuf; /**< Buffer holding data read over this connection. */ - buf_t *outbuf; /**< Buffer holding data to write over this connection. */ + struct buf_t *inbuf; /**< Buffer holding data read over this connection. */ + struct buf_t *outbuf; /**< Buffer holding data to write over this + * connection. */ size_t outbuf_flushlen; /**< How much data should we try to flush from the * outbuf? */ time_t timestamp_lastread; /**< When was the last time libevent said we could @@ -1722,11 +1730,11 @@ typedef struct entry_connection_t { /** For AP connections only: buffer for data that we have sent * optimistically, which we might need to re-send if we have to * retry this connection. */ - buf_t *pending_optimistic_data; + struct buf_t *pending_optimistic_data; /* For AP connections only: buffer for data that we previously sent * optimistically which we are currently re-sending as we retry this * connection. */ - buf_t *sending_optimistic_data; + struct buf_t *sending_optimistic_data; /** If this is a DNSPort connection, this field holds the pending DNS * request that we're going to try to answer. */ @@ -2321,6 +2329,11 @@ typedef struct routerstatus_t { * requires HSDir=2. */ unsigned int supports_v3_hsdir : 1; + /** True iff this router has a protocol list that allows it to be an hidden + * service rendezvous point supporting version 3 as seen in proposal 224. + * This requires HSRend=2. */ + unsigned int supports_v3_rendezvous_point: 1; + unsigned int has_bandwidth:1; /**< The vote/consensus had bw info */ unsigned int has_exitsummary:1; /**< The vote/consensus had exit summaries */ unsigned int bw_is_unmeasured:1; /**< This is a consensus entry, with @@ -2446,6 +2459,8 @@ typedef struct node_t { /** Used to look up the node_t by its identity digest. */ HT_ENTRY(node_t) ht_ent; + /** Used to look up the node_t by its ed25519 identity digest. */ + HT_ENTRY(node_t) ed_ht_ent; /** Position of the node within the list of nodes */ int nodelist_idx; @@ -2453,6 +2468,13 @@ typedef struct node_t { * identity may exist at a time. */ char identity[DIGEST_LEN]; + /** The ed25519 identity of this node_t. This field is nonzero iff we + * currently have an ed25519 identity for this node in either md or ri, + * _and_ this node has been inserted to the ed25519-to-node map in the + * nodelist. + */ + ed25519_public_key_t ed25519_id; + microdesc_t *md; routerinfo_t *ri; routerstatus_t *rs; @@ -3696,6 +3718,8 @@ typedef struct { } TransProxyType_parsed; config_line_t *NATDPort_lines; /**< Ports to listen on for transparent natd * connections. */ + /** Ports to listen on for HTTP Tunnel connections. */ + config_line_t *HTTPTunnelPort_lines; config_line_t *ControlPort_lines; /**< Ports to listen on for control * connections. */ config_line_t *ControlSocket; /**< List of Unix Domain Sockets to listen on @@ -3722,7 +3746,8 @@ typedef struct { * configured in one of the _lines options above. * For client ports, also true if there is a unix socket configured. * If you are checking for client ports, you may want to use: - * SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set + * SocksPort_set || TransPort_set || NATDPort_set || DNSPort_set || + * HTTPTunnelPort_set * rather than SocksPort_set. * * @{ @@ -3735,6 +3760,7 @@ typedef struct { unsigned int DirPort_set : 1; unsigned int DNSPort_set : 1; unsigned int ExtORPort_set : 1; + unsigned int HTTPTunnelPort_set : 1; /**@}*/ int AssumeReachable; /**< Whether to publish our descriptor regardless. */ @@ -5385,7 +5411,10 @@ typedef enum { CRN_PREF_ADDR = 1<<7, /* On clients, only provide nodes that we can connect to directly, based on * our firewall rules */ - CRN_DIRECT_CONN = 1<<8 + CRN_DIRECT_CONN = 1<<8, + /* On clients, only provide nodes with HSRend >= 2 protocol version which + * is required for hidden service version >= 3. */ + CRN_RENDEZVOUS_V3 = 1<<9, } router_crn_flags_t; /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */ diff --git a/src/or/proto_cell.c b/src/or/proto_cell.c new file mode 100644 index 0000000000..4485ab4e9e --- /dev/null +++ b/src/or/proto_cell.c @@ -0,0 +1,83 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "proto_cell.h" + +#include "connection_or.h" + +/** True iff the cell command <b>command</b> is one that implies a + * variable-length cell in Tor link protocol <b>linkproto</b>. */ +static inline int +cell_command_is_var_length(uint8_t command, int linkproto) +{ + /* If linkproto is v2 (2), CELL_VERSIONS is the only variable-length cells + * work as implemented here. If it's 1, there are no variable-length cells. + * Tor does not support other versions right now, and so can't negotiate + * them. + */ + switch (linkproto) { + case 1: + /* Link protocol version 1 has no variable-length cells. */ + return 0; + case 2: + /* In link protocol version 2, VERSIONS is the only variable-length cell */ + return command == CELL_VERSIONS; + case 0: + case 3: + default: + /* In link protocol version 3 and later, and in version "unknown", + * commands 128 and higher indicate variable-length. VERSIONS is + * grandfathered in. */ + return command == CELL_VERSIONS || command >= 128; + } +} + +/** Check <b>buf</b> for a variable-length cell according to the rules of link + * protocol version <b>linkproto</b>. If one is found, pull it off the buffer + * and assign a newly allocated var_cell_t to *<b>out</b>, and return 1. + * Return 0 if whatever is on the start of buf_t is not a variable-length + * cell. Return 1 and set *<b>out</b> to NULL if there seems to be the start + * of a variable-length cell on <b>buf</b>, but the whole thing isn't there + * yet. */ +int +fetch_var_cell_from_buf(buf_t *buf, var_cell_t **out, int linkproto) +{ + char hdr[VAR_CELL_MAX_HEADER_SIZE]; + var_cell_t *result; + uint8_t command; + uint16_t length; + const int wide_circ_ids = linkproto >= MIN_LINK_PROTO_FOR_WIDE_CIRC_IDS; + const int circ_id_len = get_circ_id_size(wide_circ_ids); + const unsigned header_len = get_var_cell_header_size(wide_circ_ids); + *out = NULL; + if (buf_datalen(buf) < header_len) + return 0; + buf_peek(buf, hdr, header_len); + + command = get_uint8(hdr + circ_id_len); + if (!(cell_command_is_var_length(command, linkproto))) + return 0; + + length = ntohs(get_uint16(hdr + circ_id_len + 1)); + if (buf_datalen(buf) < (size_t)(header_len+length)) + return 1; + result = var_cell_new(length); + result->command = command; + if (wide_circ_ids) + result->circ_id = ntohl(get_uint32(hdr)); + else + result->circ_id = ntohs(get_uint16(hdr)); + + buf_drain(buf, header_len); + buf_peek(buf, (char*) result->payload, length); + buf_drain(buf, length); + + *out = result; + return 1; +} + diff --git a/src/or/proto_cell.h b/src/or/proto_cell.h new file mode 100644 index 0000000000..91729a391e --- /dev/null +++ b/src/or/proto_cell.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_CELL_H +#define TOR_PROTO_CELL_H + +struct buf_t; +struct var_cell_t; + +int fetch_var_cell_from_buf(struct buf_t *buf, struct var_cell_t **out, + int linkproto); + +#endif + diff --git a/src/or/proto_control0.c b/src/or/proto_control0.c new file mode 100644 index 0000000000..c17ba34948 --- /dev/null +++ b/src/or/proto_control0.c @@ -0,0 +1,26 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "proto_control0.h" + +/** Return 1 iff buf looks more like it has an (obsolete) v0 controller + * command on it than any valid v1 controller command. */ +int +peek_buf_has_control0_command(buf_t *buf) +{ + if (buf_datalen(buf) >= 4) { + char header[4]; + uint16_t cmd; + buf_peek(buf, header, sizeof(header)); + cmd = ntohs(get_uint16(header+2)); + if (cmd <= 0x14) + return 1; /* This is definitely not a v1 control command. */ + } + return 0; +} + diff --git a/src/or/proto_control0.h b/src/or/proto_control0.h new file mode 100644 index 0000000000..6df6bebaa5 --- /dev/null +++ b/src/or/proto_control0.h @@ -0,0 +1,14 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_CONTROL0_H +#define TOR_PROTO_CONTROL0_H + +struct buf_t; +int peek_buf_has_control0_command(struct buf_t *buf); + +#endif + diff --git a/src/or/proto_ext_or.c b/src/or/proto_ext_or.c new file mode 100644 index 0000000000..057cf109ec --- /dev/null +++ b/src/or/proto_ext_or.c @@ -0,0 +1,40 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "buffers.h" +#include "ext_orport.h" +#include "proto_ext_or.h" + +/** The size of the header of an Extended ORPort message: 2 bytes for + * COMMAND, 2 bytes for BODYLEN */ +#define EXT_OR_CMD_HEADER_SIZE 4 + +/** Read <b>buf</b>, which should contain an Extended ORPort message + * from a transport proxy. If well-formed, create and populate + * <b>out</b> with the Extended ORport message. Return 0 if the + * buffer was incomplete, 1 if it was well-formed and -1 if we + * encountered an error while parsing it. */ +int +fetch_ext_or_command_from_buf(buf_t *buf, ext_or_cmd_t **out) +{ + char hdr[EXT_OR_CMD_HEADER_SIZE]; + uint16_t len; + + if (buf_datalen(buf) < EXT_OR_CMD_HEADER_SIZE) + return 0; + buf_peek(buf, hdr, sizeof(hdr)); + len = ntohs(get_uint16(hdr+2)); + if (buf_datalen(buf) < (unsigned)len + EXT_OR_CMD_HEADER_SIZE) + return 0; + *out = ext_or_cmd_new(len); + (*out)->cmd = ntohs(get_uint16(hdr)); + (*out)->len = len; + buf_drain(buf, EXT_OR_CMD_HEADER_SIZE); + buf_get_bytes(buf, (*out)->body, len); + return 1; +} + diff --git a/src/or/proto_ext_or.h b/src/or/proto_ext_or.h new file mode 100644 index 0000000000..2ef185356c --- /dev/null +++ b/src/or/proto_ext_or.h @@ -0,0 +1,17 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_EXT_OR_H +#define TOR_PROTO_EXT_OR_H + +struct buf_t; +struct ext_or_cmt_t; + +int fetch_ext_or_command_from_buf(struct buf_t *buf, + struct ext_or_cmd_t **out); + +#endif + diff --git a/src/or/proto_http.c b/src/or/proto_http.c new file mode 100644 index 0000000000..3762429e1e --- /dev/null +++ b/src/or/proto_http.c @@ -0,0 +1,171 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define PROTO_HTTP_PRIVATE +#include "or.h" +#include "buffers.h" +#include "proto_http.h" + +/** Return true if <b>cmd</b> looks like a HTTP (proxy) request. */ +int +peek_buf_has_http_command(const buf_t *buf) +{ + if (buf_peek_startswith(buf, "CONNECT ") || + buf_peek_startswith(buf, "DELETE ") || + buf_peek_startswith(buf, "GET ") || + buf_peek_startswith(buf, "POST ") || + buf_peek_startswith(buf, "PUT " )) + return 1; + return 0; +} + +/** There is a (possibly incomplete) http statement on <b>buf</b>, of the + * form "\%s\\r\\n\\r\\n\%s", headers, body. (body may contain NULs.) + * If a) the headers include a Content-Length field and all bytes in + * the body are present, or b) there's no Content-Length field and + * all headers are present, then: + * + * - strdup headers into <b>*headers_out</b>, and NUL-terminate it. + * - memdup body into <b>*body_out</b>, and NUL-terminate it. + * - Then remove them from <b>buf</b>, and return 1. + * + * - If headers or body is NULL, discard that part of the buf. + * - If a headers or body doesn't fit in the arg, return -1. + * (We ensure that the headers or body don't exceed max len, + * _even if_ we're planning to discard them.) + * - If force_complete is true, then succeed even if not all of the + * content has arrived. + * + * Else, change nothing and return 0. + */ +int +fetch_from_buf_http(buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete) +{ + const char *headers; + size_t headerlen, bodylen, contentlen=0; + int crlf_offset; + int r; + + if (buf_datalen(buf) == 0) + return 0; + + crlf_offset = buf_find_string_offset(buf, "\r\n\r\n", 4); + if (crlf_offset > (int)max_headerlen || + (crlf_offset < 0 && buf_datalen(buf) > max_headerlen)) { + log_debug(LD_HTTP,"headers too long."); + return -1; + } else if (crlf_offset < 0) { + log_debug(LD_HTTP,"headers not all here yet."); + return 0; + } + /* Okay, we have a full header. Make sure it all appears in the first + * chunk. */ + headerlen = crlf_offset + 4; + size_t headers_in_chunk = 0; + buf_pullup(buf, headerlen, &headers, &headers_in_chunk); + + bodylen = buf_datalen(buf) - headerlen; + log_debug(LD_HTTP,"headerlen %d, bodylen %d.", (int)headerlen, (int)bodylen); + + if (max_headerlen <= headerlen) { + log_warn(LD_HTTP,"headerlen %d larger than %d. Failing.", + (int)headerlen, (int)max_headerlen-1); + return -1; + } + if (max_bodylen <= bodylen) { + log_warn(LD_HTTP,"bodylen %d larger than %d. Failing.", + (int)bodylen, (int)max_bodylen-1); + return -1; + } + + r = buf_http_find_content_length(headers, headerlen, &contentlen); + if (r == -1) { + log_warn(LD_PROTOCOL, "Content-Length is bogus; maybe " + "someone is trying to crash us."); + return -1; + } else if (r == 1) { + /* if content-length is malformed, then our body length is 0. fine. */ + log_debug(LD_HTTP,"Got a contentlen of %d.",(int)contentlen); + if (bodylen < contentlen) { + if (!force_complete) { + log_debug(LD_HTTP,"body not all here yet."); + return 0; /* not all there yet */ + } + } + if (bodylen > contentlen) { + bodylen = contentlen; + log_debug(LD_HTTP,"bodylen reduced to %d.",(int)bodylen); + } + } else { + tor_assert(r == 0); + /* Leave bodylen alone */ + } + + /* all happy. copy into the appropriate places, and return 1 */ + if (headers_out) { + *headers_out = tor_malloc(headerlen+1); + buf_get_bytes(buf, *headers_out, headerlen); + (*headers_out)[headerlen] = 0; /* NUL terminate it */ + } + if (body_out) { + tor_assert(body_used); + *body_used = bodylen; + *body_out = tor_malloc(bodylen+1); + buf_get_bytes(buf, *body_out, bodylen); + (*body_out)[bodylen] = 0; /* NUL terminate it */ + } + return 1; +} + +/** + * Scan the HTTP headers in the <b>headerlen</b>-byte memory range at + * <b>headers</b>, looking for a "Content-Length" header. Try to set + * *<b>result_out</b> to the numeric value of that header if possible. + * Return -1 if the header was malformed, 0 if it was missing, and 1 if + * it was present and well-formed. + */ +STATIC int +buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out) +{ + const char *p, *newline; + char *len_str, *eos=NULL; + size_t remaining, result; + int ok; + *result_out = 0; /* The caller shouldn't look at this unless the + * return value is 1, but let's prevent confusion */ + +#define CONTENT_LENGTH "\r\nContent-Length: " + p = (char*) tor_memstr(headers, headerlen, CONTENT_LENGTH); + if (p == NULL) + return 0; + + tor_assert(p >= headers && p < headers+headerlen); + remaining = (headers+headerlen)-p; + p += strlen(CONTENT_LENGTH); + remaining -= strlen(CONTENT_LENGTH); + + newline = memchr(p, '\n', remaining); + if (newline == NULL) + return -1; + + len_str = tor_memdup_nulterm(p, newline-p); + /* We limit the size to INT_MAX because other parts of the buffer.c + * code don't like buffers to be any bigger than that. */ + result = (size_t) tor_parse_uint64(len_str, 10, 0, INT_MAX, &ok, &eos); + if (eos && !tor_strisspace(eos)) { + ok = 0; + } else { + *result_out = result; + } + tor_free(len_str); + + return ok ? 1 : -1; +} + diff --git a/src/or/proto_http.h b/src/or/proto_http.h new file mode 100644 index 0000000000..dbff823cce --- /dev/null +++ b/src/or/proto_http.h @@ -0,0 +1,24 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_HTTP_H +#define TOR_PROTO_HTTP_H + +struct buf_t; + +int fetch_from_buf_http(struct buf_t *buf, + char **headers_out, size_t max_headerlen, + char **body_out, size_t *body_used, size_t max_bodylen, + int force_complete); +int peek_buf_has_http_command(const struct buf_t *buf); + +#ifdef PROTO_HTTP_PRIVATE +STATIC int buf_http_find_content_length(const char *headers, size_t headerlen, + size_t *result_out); +#endif + +#endif + diff --git a/src/or/proto_socks.c b/src/or/proto_socks.c new file mode 100644 index 0000000000..f92d614918 --- /dev/null +++ b/src/or/proto_socks.c @@ -0,0 +1,698 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "addressmap.h" +#include "buffers.h" +#include "control.h" +#include "config.h" +#include "ext_orport.h" +#include "proto_socks.h" +#include "reasons.h" + +static void socks_request_set_socks5_error(socks_request_t *req, + socks5_reply_status_t reason); + +static int parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out); +static int parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out); +/** + * Wait this many seconds before warning the user about using SOCKS unsafely + * again. */ +#define SOCKS_WARN_INTERVAL 5 + +/** Warn that the user application has made an unsafe socks request using + * protocol <b>socks_protocol</b> on port <b>port</b>. Don't warn more than + * once per SOCKS_WARN_INTERVAL, unless <b>safe_socks</b> is set. */ +static void +log_unsafe_socks_warning(int socks_protocol, const char *address, + uint16_t port, int safe_socks) +{ + static ratelim_t socks_ratelim = RATELIM_INIT(SOCKS_WARN_INTERVAL); + + if (safe_socks) { + log_fn_ratelim(&socks_ratelim, LOG_WARN, LD_APP, + "Your application (using socks%d to port %d) is giving " + "Tor only an IP address. Applications that do DNS resolves " + "themselves may leak information. Consider using Socks4A " + "(e.g. via privoxy or socat) instead. For more information, " + "please see https://wiki.torproject.org/TheOnionRouter/" + "TorFAQ#SOCKSAndDNS.%s", + socks_protocol, + (int)port, + safe_socks ? " Rejecting." : ""); + } + control_event_client_status(LOG_WARN, + "DANGEROUS_SOCKS PROTOCOL=SOCKS%d ADDRESS=%s:%d", + socks_protocol, address, (int)port); +} + +/** Do not attempt to parse socks messages longer than this. This value is + * actually significantly higher than the longest possible socks message. */ +#define MAX_SOCKS_MESSAGE_LEN 512 + +/** Return a new socks_request_t. */ +socks_request_t * +socks_request_new(void) +{ + return tor_malloc_zero(sizeof(socks_request_t)); +} + +/** Free all storage held in the socks_request_t <b>req</b>. */ +void +socks_request_free(socks_request_t *req) +{ + if (!req) + return; + if (req->username) { + memwipe(req->username, 0x10, req->usernamelen); + tor_free(req->username); + } + if (req->password) { + memwipe(req->password, 0x04, req->passwordlen); + tor_free(req->password); + } + memwipe(req, 0xCC, sizeof(socks_request_t)); + tor_free(req); +} + +/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one + * of the forms + * - socks4: "socksheader username\\0" + * - socks4a: "socksheader username\\0 destaddr\\0" + * - socks5 phase one: "version #methods methods" + * - socks5 phase two: "version command 0 addresstype..." + * If it's a complete and valid handshake, and destaddr fits in + * MAX_SOCKS_ADDR_LEN bytes, then pull the handshake off the buf, + * assign to <b>req</b>, and return 1. + * + * If it's invalid or too big, return -1. + * + * Else it's not all there yet, leave buf alone and return 0. + * + * If you want to specify the socks reply, write it into <b>req->reply</b> + * and set <b>req->replylen</b>, else leave <b>req->replylen</b> alone. + * + * If <b>log_sockstype</b> is non-zero, then do a notice-level log of whether + * the connection is possibly leaking DNS requests locally or not. + * + * If <b>safe_socks</b> is true, then reject unsafe socks protocols. + * + * If returning 0 or -1, <b>req->address</b> and <b>req->port</b> are + * undefined. + */ +int +fetch_from_buf_socks(buf_t *buf, socks_request_t *req, + int log_sockstype, int safe_socks) +{ + int res; + ssize_t n_drain; + size_t want_length = 128; + const char *head = NULL; + size_t datalen = 0; + + if (buf_datalen(buf) < 2) /* version and another byte */ + return 0; + + do { + n_drain = 0; + buf_pullup(buf, want_length, &head, &datalen); + tor_assert(head && datalen >= 2); + want_length = 0; + + res = parse_socks(head, datalen, req, log_sockstype, + safe_socks, &n_drain, &want_length); + + if (n_drain < 0) + buf_clear(buf); + else if (n_drain > 0) + buf_drain(buf, n_drain); + + } while (res == 0 && head && want_length < buf_datalen(buf) && + buf_datalen(buf) >= 2); + + return res; +} + +/** Create a SOCKS5 reply message with <b>reason</b> in its REP field and + * have Tor send it as error response to <b>req</b>. + */ +static void +socks_request_set_socks5_error(socks_request_t *req, + socks5_reply_status_t reason) +{ + req->replylen = 10; + memset(req->reply,0,10); + + req->reply[0] = 0x05; // VER field. + req->reply[1] = reason; // REP field. + req->reply[3] = 0x01; // ATYP field. +} + +static const char SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG[] = + "HTTP/1.0 501 Tor is not an HTTP Proxy\r\n" + "Content-Type: text/html; charset=iso-8859-1\r\n\r\n" + "<html>\n" + "<head>\n" + "<title>Tor is not an HTTP Proxy</title>\n" + "</head>\n" + "<body>\n" + "<h1>Tor is not an HTTP Proxy</h1>\n" + "<p>\n" + "It appears you have configured your web browser to use Tor as " + "an HTTP proxy.\n\n" + "This is not correct: Tor is a SOCKS proxy, not an HTTP proxy.\n" + "Please configure your client accordingly.\n" + "</p>\n" + "<p>\n" + "See <a href=\"https://www.torproject.org/documentation.html\">" + "https://www.torproject.org/documentation.html</a> for more " + "information.\n" + "<!-- Plus this comment, to make the body response more than 512 bytes, so " + " IE will be willing to display it. Comment comment comment comment " + " comment comment comment comment comment comment comment comment.-->\n" + "</p>\n" + "</body>\n" + "</html>\n"; + +/** Implementation helper to implement fetch_from_*_socks. Instead of looking + * at a buffer's contents, we look at the <b>datalen</b> bytes of data in + * <b>data</b>. Instead of removing data from the buffer, we set + * <b>drain_out</b> to the amount of data that should be removed (or -1 if the + * buffer should be cleared). Instead of pulling more data into the first + * chunk of the buffer, we set *<b>want_length_out</b> to the number of bytes + * we'd like to see in the input buffer, if they're available. */ +static int +parse_socks(const char *data, size_t datalen, socks_request_t *req, + int log_sockstype, int safe_socks, ssize_t *drain_out, + size_t *want_length_out) +{ + unsigned int len; + char tmpbuf[TOR_ADDR_BUF_LEN+1]; + tor_addr_t destaddr; + uint32_t destip; + uint8_t socksver; + char *next, *startaddr; + unsigned char usernamelen, passlen; + struct in_addr in; + + if (datalen < 2) { + /* We always need at least 2 bytes. */ + *want_length_out = 2; + return 0; + } + + if (req->socks_version == 5 && !req->got_auth) { + /* See if we have received authentication. Strictly speaking, we should + also check whether we actually negotiated username/password + authentication. But some broken clients will send us authentication + even if we negotiated SOCKS_NO_AUTH. */ + if (*data == 1) { /* username/pass version 1 */ + /* Format is: authversion [1 byte] == 1 + usernamelen [1 byte] + username [usernamelen bytes] + passlen [1 byte] + password [passlen bytes] */ + usernamelen = (unsigned char)*(data + 1); + if (datalen < 2u + usernamelen + 1u) { + *want_length_out = 2u + usernamelen + 1u; + return 0; + } + passlen = (unsigned char)*(data + 2u + usernamelen); + if (datalen < 2u + usernamelen + 1u + passlen) { + *want_length_out = 2u + usernamelen + 1u + passlen; + return 0; + } + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 1; /* authversion == 1 */ + req->reply[1] = 0; /* authentication successful */ + log_debug(LD_APP, + "socks5: Accepted username/password without checking."); + if (usernamelen) { + req->username = tor_memdup(data+2u, usernamelen); + req->usernamelen = usernamelen; + } + if (passlen) { + req->password = tor_memdup(data+3u+usernamelen, passlen); + req->passwordlen = passlen; + } + *drain_out = 2u + usernamelen + 1u + passlen; + req->got_auth = 1; + *want_length_out = 7; /* Minimal socks5 command. */ + return 0; + } else if (req->auth_type == SOCKS_USER_PASS) { + /* unknown version byte */ + log_warn(LD_APP, "Socks5 username/password version %d not recognized; " + "rejecting.", (int)*data); + return -1; + } + } + + socksver = *data; + + switch (socksver) { /* which version of socks? */ + case 5: /* socks5 */ + + if (req->socks_version != 5) { /* we need to negotiate a method */ + unsigned char nummethods = (unsigned char)*(data+1); + int have_user_pass, have_no_auth; + int r=0; + tor_assert(!req->socks_version); + if (datalen < 2u+nummethods) { + *want_length_out = 2u+nummethods; + return 0; + } + if (!nummethods) + return -1; + req->replylen = 2; /* 2 bytes of response */ + req->reply[0] = 5; /* socks5 reply */ + have_user_pass = (memchr(data+2, SOCKS_USER_PASS, nummethods) !=NULL); + have_no_auth = (memchr(data+2, SOCKS_NO_AUTH, nummethods) !=NULL); + if (have_user_pass && !(have_no_auth && req->socks_prefer_no_auth)) { + req->auth_type = SOCKS_USER_PASS; + req->reply[1] = SOCKS_USER_PASS; /* tell client to use "user/pass" + auth method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 2 (username/password)"); + r=0; + } else if (have_no_auth) { + req->reply[1] = SOCKS_NO_AUTH; /* tell client to use "none" auth + method */ + req->socks_version = 5; /* remember we've already negotiated auth */ + log_debug(LD_APP,"socks5: accepted method 0 (no authentication)"); + r=0; + } else { + log_warn(LD_APP, + "socks5: offered methods don't include 'no auth' or " + "username/password. Rejecting."); + req->reply[1] = '\xFF'; /* reject all methods */ + r=-1; + } + /* Remove packet from buf. Some SOCKS clients will have sent extra + * junk at this point; let's hope it's an authentication message. */ + *drain_out = 2u + nummethods; + + return r; + } + if (req->auth_type != SOCKS_NO_AUTH && !req->got_auth) { + log_warn(LD_APP, + "socks5: negotiated authentication, but none provided"); + return -1; + } + /* we know the method; read in the request */ + log_debug(LD_APP,"socks5: checking request"); + if (datalen < 7) {/* basic info plus >=1 for addr plus 2 for port */ + *want_length_out = 7; + return 0; /* not yet */ + } + req->command = (unsigned char) *(data+1); + if (req->command != SOCKS_COMMAND_CONNECT && + req->command != SOCKS_COMMAND_RESOLVE && + req->command != SOCKS_COMMAND_RESOLVE_PTR) { + /* not a connect or resolve or a resolve_ptr? we don't support it. */ + socks_request_set_socks5_error(req,SOCKS5_COMMAND_NOT_SUPPORTED); + + log_warn(LD_APP,"socks5: command %d not recognized. Rejecting.", + req->command); + return -1; + } + switch (*(data+3)) { /* address type */ + case 1: /* IPv4 address */ + case 4: /* IPv6 address */ { + const int is_v6 = *(data+3) == 4; + const unsigned addrlen = is_v6 ? 16 : 4; + log_debug(LD_APP,"socks5: ipv4 address type"); + if (datalen < 6+addrlen) {/* ip/port there? */ + *want_length_out = 6+addrlen; + return 0; /* not yet */ + } + + if (is_v6) + tor_addr_from_ipv6_bytes(&destaddr, data+4); + else + tor_addr_from_ipv4n(&destaddr, get_uint32(data+4)); + + tor_addr_to_str(tmpbuf, &destaddr, sizeof(tmpbuf), 1); + + if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + log_warn(LD_APP, + "socks5 IP takes %d bytes, which doesn't fit in %d. " + "Rejecting.", + (int)strlen(tmpbuf)+1,(int)MAX_SOCKS_ADDR_LEN); + return -1; + } + strlcpy(req->address,tmpbuf,sizeof(req->address)); + req->port = ntohs(get_uint16(data+4+addrlen)); + *drain_out = 6+addrlen; + if (req->command != SOCKS_COMMAND_RESOLVE_PTR && + !addressmap_have_mapping(req->address,0)) { + log_unsafe_socks_warning(5, req->address, req->port, safe_socks); + if (safe_socks) { + socks_request_set_socks5_error(req, SOCKS5_NOT_ALLOWED); + return -1; + } + } + return 1; + } + case 3: /* fqdn */ + log_debug(LD_APP,"socks5: fqdn address type"); + if (req->command == SOCKS_COMMAND_RESOLVE_PTR) { + socks_request_set_socks5_error(req, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); + log_warn(LD_APP, "socks5 received RESOLVE_PTR command with " + "hostname type. Rejecting."); + return -1; + } + len = (unsigned char)*(data+4); + if (datalen < 7+len) { /* addr/port there? */ + *want_length_out = 7+len; + return 0; /* not yet */ + } + if (len+1 > MAX_SOCKS_ADDR_LEN) { + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + log_warn(LD_APP, + "socks5 hostname is %d bytes, which doesn't fit in " + "%d. Rejecting.", len+1,MAX_SOCKS_ADDR_LEN); + return -1; + } + memcpy(req->address,data+5,len); + req->address[len] = 0; + req->port = ntohs(get_uint16(data+5+len)); + *drain_out = 5+len+2; + + if (!string_is_valid_hostname(req->address)) { + socks_request_set_socks5_error(req, SOCKS5_GENERAL_ERROR); + + log_warn(LD_PROTOCOL, + "Your application (using socks5 to port %d) gave Tor " + "a malformed hostname: %s. Rejecting the connection.", + req->port, escaped_safe_str_client(req->address)); + return -1; + } + if (log_sockstype) + log_notice(LD_APP, + "Your application (using socks5 to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); + return 1; + default: /* unsupported */ + socks_request_set_socks5_error(req, + SOCKS5_ADDRESS_TYPE_NOT_SUPPORTED); + log_warn(LD_APP,"socks5: unsupported address type %d. Rejecting.", + (int) *(data+3)); + return -1; + } + tor_assert(0); + break; + case 4: { /* socks4 */ + enum {socks4, socks4a} socks4_prot = socks4a; + const char *authstart, *authend; + /* http://ss5.sourceforge.net/socks4.protocol.txt */ + /* http://ss5.sourceforge.net/socks4A.protocol.txt */ + + req->socks_version = 4; + if (datalen < SOCKS4_NETWORK_LEN) {/* basic info available? */ + *want_length_out = SOCKS4_NETWORK_LEN; + return 0; /* not yet */ + } + // buf_pullup(buf, 1280); + req->command = (unsigned char) *(data+1); + if (req->command != SOCKS_COMMAND_CONNECT && + req->command != SOCKS_COMMAND_RESOLVE) { + /* not a connect or resolve? we don't support it. (No resolve_ptr with + * socks4.) */ + log_warn(LD_APP,"socks4: command %d not recognized. Rejecting.", + req->command); + return -1; + } + + req->port = ntohs(get_uint16(data+2)); + destip = ntohl(get_uint32(data+4)); + if ((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) { + log_warn(LD_APP,"socks4: Port or DestIP is zero. Rejecting."); + return -1; + } + if (destip >> 8) { + log_debug(LD_APP,"socks4: destip not in form 0.0.0.x."); + in.s_addr = htonl(destip); + tor_inet_ntoa(&in,tmpbuf,sizeof(tmpbuf)); + if (strlen(tmpbuf)+1 > MAX_SOCKS_ADDR_LEN) { + log_debug(LD_APP,"socks4 addr (%d bytes) too long. Rejecting.", + (int)strlen(tmpbuf)); + return -1; + } + log_debug(LD_APP, + "socks4: successfully read destip (%s)", + safe_str_client(tmpbuf)); + socks4_prot = socks4; + } + + authstart = data + SOCKS4_NETWORK_LEN; + next = memchr(authstart, 0, + datalen-SOCKS4_NETWORK_LEN); + if (!next) { + if (datalen >= 1024) { + log_debug(LD_APP, "Socks4 user name too long; rejecting."); + return -1; + } + log_debug(LD_APP,"socks4: Username not here yet."); + *want_length_out = datalen+1024; /* More than we need, but safe */ + return 0; + } + authend = next; + tor_assert(next < data+datalen); + + startaddr = NULL; + if (socks4_prot != socks4a && + !addressmap_have_mapping(tmpbuf,0)) { + log_unsafe_socks_warning(4, tmpbuf, req->port, safe_socks); + + if (safe_socks) + return -1; + } + if (socks4_prot == socks4a) { + if (next+1 == data+datalen) { + log_debug(LD_APP,"socks4: No part of destaddr here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ + return 0; + } + startaddr = next+1; + next = memchr(startaddr, 0, data + datalen - startaddr); + if (!next) { + if (datalen >= 1024) { + log_debug(LD_APP,"socks4: Destaddr too long."); + return -1; + } + log_debug(LD_APP,"socks4: Destaddr not all here yet."); + *want_length_out = datalen + 1024; /* More than we need, but safe */ + return 0; + } + if (MAX_SOCKS_ADDR_LEN <= next-startaddr) { + log_warn(LD_APP,"socks4: Destaddr too long. Rejecting."); + return -1; + } + // tor_assert(next < buf->cur+buf_datalen(buf)); + + if (log_sockstype) + log_notice(LD_APP, + "Your application (using socks4a to port %d) instructed " + "Tor to take care of the DNS resolution itself if " + "necessary. This is good.", req->port); + } + log_debug(LD_APP,"socks4: Everything is here. Success."); + strlcpy(req->address, startaddr ? startaddr : tmpbuf, + sizeof(req->address)); + if (!string_is_valid_hostname(req->address)) { + log_warn(LD_PROTOCOL, + "Your application (using socks4 to port %d) gave Tor " + "a malformed hostname: %s. Rejecting the connection.", + req->port, escaped_safe_str_client(req->address)); + return -1; + } + if (authend != authstart) { + req->got_auth = 1; + req->usernamelen = authend - authstart; + req->username = tor_memdup(authstart, authend - authstart); + } + /* next points to the final \0 on inbuf */ + *drain_out = next - data + 1; + return 1; + } + case 'G': /* get */ + case 'H': /* head */ + case 'P': /* put/post */ + case 'C': /* connect */ + strlcpy((char*)req->reply, SOCKS_PROXY_IS_NOT_AN_HTTP_PROXY_MSG, + MAX_SOCKS_REPLY_LEN); + req->replylen = strlen((char*)req->reply)+1; + /* fall through */ + default: /* version is not socks4 or socks5 */ + log_warn(LD_APP, + "Socks version %d not recognized. (Tor is not an http proxy.)", + *(data)); + { + /* Tell the controller the first 8 bytes. */ + char *tmp = tor_strndup(data, datalen < 8 ? datalen : 8); + control_event_client_status(LOG_WARN, + "SOCKS_UNKNOWN_PROTOCOL DATA=\"%s\"", + escaped(tmp)); + tor_free(tmp); + } + return -1; + } +} + +/** Inspect a reply from SOCKS server stored in <b>buf</b> according + * to <b>state</b>, removing the protocol data upon success. Return 0 on + * incomplete response, 1 on success and -1 on error, in which case + * <b>reason</b> is set to a descriptive message (free() when finished + * with it). + * + * As a special case, 2 is returned when user/pass is required + * during SOCKS5 handshake and user/pass is configured. + */ +int +fetch_from_buf_socks_client(buf_t *buf, int state, char **reason) +{ + ssize_t drain = 0; + int r; + const char *head = NULL; + size_t datalen = 0; + + if (buf_datalen(buf) < 2) + return 0; + + buf_pullup(buf, MAX_SOCKS_MESSAGE_LEN, &head, &datalen); + tor_assert(head && datalen >= 2); + + r = parse_socks_client((uint8_t*)head, datalen, + state, reason, &drain); + if (drain > 0) + buf_drain(buf, drain); + else if (drain < 0) + buf_clear(buf); + + return r; +} + +/** Implementation logic for fetch_from_*_socks_client. */ +static int +parse_socks_client(const uint8_t *data, size_t datalen, + int state, char **reason, + ssize_t *drain_out) +{ + unsigned int addrlen; + *drain_out = 0; + if (datalen < 2) + return 0; + + switch (state) { + case PROXY_SOCKS4_WANT_CONNECT_OK: + /* Wait for the complete response */ + if (datalen < 8) + return 0; + + if (data[1] != 0x5a) { + *reason = tor_strdup(socks4_response_code_to_string(data[1])); + return -1; + } + + /* Success */ + *drain_out = 8; + return 1; + + case PROXY_SOCKS5_WANT_AUTH_METHOD_NONE: + /* we don't have any credentials */ + if (data[1] != 0x00) { + *reason = tor_strdup("server doesn't support any of our " + "available authentication methods"); + return -1; + } + + log_info(LD_NET, "SOCKS 5 client: continuing without authentication"); + *drain_out = -1; + return 1; + + case PROXY_SOCKS5_WANT_AUTH_METHOD_RFC1929: + /* we have a username and password. return 1 if we can proceed without + * providing authentication, or 2 otherwise. */ + switch (data[1]) { + case 0x00: + log_info(LD_NET, "SOCKS 5 client: we have auth details but server " + "doesn't require authentication."); + *drain_out = -1; + return 1; + case 0x02: + log_info(LD_NET, "SOCKS 5 client: need authentication."); + *drain_out = -1; + return 2; + /* fall through */ + } + + *reason = tor_strdup("server doesn't support any of our available " + "authentication methods"); + return -1; + + case PROXY_SOCKS5_WANT_AUTH_RFC1929_OK: + /* handle server reply to rfc1929 authentication */ + if (data[1] != 0x00) { + *reason = tor_strdup("authentication failed"); + return -1; + } + + log_info(LD_NET, "SOCKS 5 client: authentication successful."); + *drain_out = -1; + return 1; + + case PROXY_SOCKS5_WANT_CONNECT_OK: + /* response is variable length. BND.ADDR, etc, isn't needed + * (don't bother with buf_pullup()), but make sure to eat all + * the data used */ + + /* wait for address type field to arrive */ + if (datalen < 4) + return 0; + + switch (data[3]) { + case 0x01: /* ip4 */ + addrlen = 4; + break; + case 0x04: /* ip6 */ + addrlen = 16; + break; + case 0x03: /* fqdn (can this happen here?) */ + if (datalen < 5) + return 0; + addrlen = 1 + data[4]; + break; + default: + *reason = tor_strdup("invalid response to connect request"); + return -1; + } + + /* wait for address and port */ + if (datalen < 6 + addrlen) + return 0; + + if (data[1] != 0x00) { + *reason = tor_strdup(socks5_response_code_to_string(data[1])); + return -1; + } + + *drain_out = 6 + addrlen; + return 1; + } + + /* shouldn't get here... */ + tor_assert(0); + + return -1; +} + diff --git a/src/or/proto_socks.h b/src/or/proto_socks.h new file mode 100644 index 0000000000..4a24773199 --- /dev/null +++ b/src/or/proto_socks.h @@ -0,0 +1,20 @@ +/* Copyright (c) 2001 Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2017, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PROTO_SOCKS_H +#define TOR_PROTO_SOCKS_H + +struct socks_request_t; +struct buf_t; + +struct socks_request_t *socks_request_new(void); +void socks_request_free(struct socks_request_t *req); +int fetch_from_buf_socks(struct buf_t *buf, socks_request_t *req, + int log_sockstype, int safe_socks); +int fetch_from_buf_socks_client(buf_t *buf, int state, char **reason); + +#endif + diff --git a/src/or/protover.h b/src/or/protover.h index 2066aeec72..ec8da1a0d0 100644 --- a/src/or/protover.h +++ b/src/or/protover.h @@ -21,6 +21,8 @@ #define PROTOVER_HSDIR_V3 2 /** The protover version number that signifies HSv3 intro point support */ #define PROTOVER_HS_INTRO_V3 4 +/** The protover version number that signifies HSv3 rendezvous point support */ +#define PROTOVER_HS_RENDEZVOUS_POINT_V3 2 /** List of recognized subprotocols. */ typedef enum protocol_type_t { diff --git a/src/or/reasons.c b/src/or/reasons.c index e6c325f1b3..717cf57c24 100644 --- a/src/or/reasons.c +++ b/src/or/reasons.c @@ -45,6 +45,8 @@ stream_end_reason_to_control_string(int reason) case END_STREAM_REASON_CANT_ATTACH: return "CANT_ATTACH"; case END_STREAM_REASON_NET_UNREACHABLE: return "NET_UNREACHABLE"; case END_STREAM_REASON_SOCKSPROTOCOL: return "SOCKS_PROTOCOL"; + // XXXX Controlspec + case END_STREAM_REASON_HTTPPROTOCOL: return "HTTP_PROTOCOL"; case END_STREAM_REASON_PRIVATE_ADDR: return "PRIVATE_ADDR"; @@ -138,6 +140,11 @@ stream_end_reason_to_socks5_response(int reason) return SOCKS5_NET_UNREACHABLE; case END_STREAM_REASON_SOCKSPROTOCOL: return SOCKS5_GENERAL_ERROR; + case END_STREAM_REASON_HTTPPROTOCOL: + // LCOV_EXCL_START + tor_assert_nonfatal_unreached(); + return SOCKS5_GENERAL_ERROR; + // LCOV_EXCL_STOP case END_STREAM_REASON_PRIVATE_ADDR: return SOCKS5_GENERAL_ERROR; @@ -442,3 +449,48 @@ bandwidth_weight_rule_to_string(bandwidth_weight_rule_t rule) } } +/** Given a RELAY_END reason value, convert it to an HTTP response to be + * send over an HTTP tunnel connection. */ +const char * +end_reason_to_http_connect_response_line(int endreason) +{ + endreason &= END_STREAM_REASON_MASK; + /* XXXX these are probably all wrong. Should they all be 502? */ + switch (endreason) { + case 0: + return "HTTP/1.0 200 OK\r\n\r\n"; + case END_STREAM_REASON_MISC: + return "HTTP/1.0 500 Internal Server Error\r\n\r\n"; + case END_STREAM_REASON_RESOLVEFAILED: + return "HTTP/1.0 404 Not Found (resolve failed)\r\n\r\n"; + case END_STREAM_REASON_NOROUTE: + return "HTTP/1.0 404 Not Found (no route)\r\n\r\n"; + case END_STREAM_REASON_CONNECTREFUSED: + return "HTTP/1.0 403 Forbidden (connection refused)\r\n\r\n"; + case END_STREAM_REASON_EXITPOLICY: + return "HTTP/1.0 403 Forbidden (exit policy)\r\n\r\n"; + case END_STREAM_REASON_DESTROY: + return "HTTP/1.0 502 Bad Gateway (destroy cell received)\r\n\r\n"; + case END_STREAM_REASON_DONE: + return "HTTP/1.0 502 Bad Gateway (unexpected close)\r\n\r\n"; + case END_STREAM_REASON_TIMEOUT: + return "HTTP/1.0 504 Gateway Timeout\r\n\r\n"; + case END_STREAM_REASON_HIBERNATING: + return "HTTP/1.0 502 Bad Gateway (hibernating server)\r\n\r\n"; + case END_STREAM_REASON_INTERNAL: + return "HTTP/1.0 502 Bad Gateway (internal error)\r\n\r\n"; + case END_STREAM_REASON_RESOURCELIMIT: + return "HTTP/1.0 502 Bad Gateway (resource limit)\r\n\r\n"; + case END_STREAM_REASON_CONNRESET: + return "HTTP/1.0 403 Forbidden (connection reset)\r\n\r\n"; + case END_STREAM_REASON_TORPROTOCOL: + return "HTTP/1.0 502 Bad Gateway (tor protocol violation)\r\n\r\n"; + case END_STREAM_REASON_ENTRYPOLICY: + return "HTTP/1.0 403 Forbidden (entry policy violation)\r\n\r\n"; + case END_STREAM_REASON_NOTDIRECTORY: /* Fall Through */ + default: + tor_assert_nonfatal_unreached(); + return "HTTP/1.0 500 Internal Server Error (weird end reason)\r\n\r\n"; + } +} + diff --git a/src/or/reasons.h b/src/or/reasons.h index 1cadf4e89e..f98db55bdd 100644 --- a/src/or/reasons.h +++ b/src/or/reasons.h @@ -26,6 +26,7 @@ const char *socks4_response_code_to_string(uint8_t code); const char *socks5_response_code_to_string(uint8_t code); const char *bandwidth_weight_rule_to_string(enum bandwidth_weight_rule_t rule); +const char *end_reason_to_http_connect_response_line(int endreason); #endif diff --git a/src/or/relay.c b/src/or/relay.c index 18ccc65b80..ba9c09b843 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -48,6 +48,7 @@ #define RELAY_PRIVATE #include "or.h" #include "addressmap.h" +#include "backtrace.h" #include "buffers.h" #include "channel.h" #include "circpathbias.h" @@ -500,11 +501,13 @@ circuit_package_relay_cell(cell_t *cell, circuit_t *circ, if (!chan) { log_warn(LD_BUG,"outgoing relay cell sent from %s:%d has n_chan==NULL." " Dropping.", filename, lineno); + log_backtrace(LOG_WARN,LD_BUG,""); return 0; /* just drop it */ } if (!CIRCUIT_IS_ORIGIN(circ)) { log_warn(LD_BUG,"outgoing relay cell sent from %s:%d on non-origin " "circ. Dropping.", filename, lineno); + log_backtrace(LOG_WARN,LD_BUG,""); return 0; /* just drop it */ } @@ -1467,8 +1470,9 @@ connection_edge_process_relay_cell_not_open( circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ)); /* don't send a socks reply to transparent conns */ tor_assert(entry_conn->socks_request != NULL); - if (!entry_conn->socks_request->has_finished) + if (!entry_conn->socks_request->has_finished) { connection_ap_handshake_socks_reply(entry_conn, NULL, 0, 0); + } /* Was it a linked dir conn? If so, a dir request just started to * fetch something; this could be a bootstrap status milestone. */ @@ -1670,7 +1674,7 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } stats_n_data_bytes_received += rh.length; - connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE), + connection_buf_add((char*)(cell->payload + RELAY_HEADER_SIZE), rh.length, TO_CONN(conn)); #ifdef MEASUREMENTS_21206 @@ -2038,13 +2042,13 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, /* XXXX We could be more efficient here by sometimes packing * previously-sent optimistic data in the same cell with data * from the inbuf. */ - fetch_from_buf(payload, length, entry_conn->sending_optimistic_data); + buf_get_bytes(entry_conn->sending_optimistic_data, payload, length); if (!buf_datalen(entry_conn->sending_optimistic_data)) { buf_free(entry_conn->sending_optimistic_data); entry_conn->sending_optimistic_data = NULL; } } else { - connection_fetch_from_buf(payload, length, TO_CONN(conn)); + connection_buf_get_bytes(payload, length, TO_CONN(conn)); } log_debug(domain,TOR_SOCKET_T_FORMAT": Packaging %d bytes (%d waiting).", @@ -2056,7 +2060,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, retry */ if (!entry_conn->pending_optimistic_data) entry_conn->pending_optimistic_data = buf_new(); - write_to_buf(payload, length, entry_conn->pending_optimistic_data); + buf_add(entry_conn->pending_optimistic_data, payload, length); } if (connection_edge_send_command(conn, RELAY_COMMAND_DATA, diff --git a/src/or/rendcache.c b/src/or/rendcache.c index 792f6a1302..b98b2bccfa 100644 --- a/src/or/rendcache.c +++ b/src/or/rendcache.c @@ -303,7 +303,7 @@ void rend_cache_purge(void) { if (rend_cache) { - log_info(LD_REND, "Purging HS descriptor cache"); + log_info(LD_REND, "Purging HS v2 descriptor cache"); strmap_free(rend_cache, rend_cache_entry_free_); } rend_cache = strmap_new(); @@ -315,7 +315,7 @@ void rend_cache_failure_purge(void) { if (rend_cache_failure) { - log_info(LD_REND, "Purging HS failure cache"); + log_info(LD_REND, "Purging HS v2 failure cache"); strmap_free(rend_cache_failure, rend_cache_failure_entry_free_); } rend_cache_failure = strmap_new(); diff --git a/src/or/rendclient.c b/src/or/rendclient.c index 0f430d1f88..55f79c50fb 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -227,6 +227,11 @@ rend_client_send_introduction(origin_circuit_t *introcirc, klen = crypto_pk_asn1_encode(extend_info->onion_key, tmp+v3_shift+7+DIGEST_LEN+2, sizeof(tmp)-(v3_shift+7+DIGEST_LEN+2)); + if (klen < 0) { + log_warn(LD_BUG,"Internal error: can't encode public key."); + status = -2; + goto perm_err; + } set_uint16(tmp+v3_shift+7+DIGEST_LEN, htons(klen)); memcpy(tmp+v3_shift+7+DIGEST_LEN+2+klen, rendcirc->rend_data->rend_cookie, REND_COOKIE_LEN); diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index f7ae7c61f1..458a90058f 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -480,7 +480,10 @@ rend_encode_v2_descriptors(smartlist_t *descs_out, tor_assert(descriptor_cookie); } /* Obtain service_id from public key. */ - crypto_pk_get_digest(service_key, service_id); + if (crypto_pk_get_digest(service_key, service_id) < 0) { + log_warn(LD_BUG, "Couldn't compute service key digest."); + return -1; + } /* Calculate current time-period. */ time_period = get_time_period(now, period, service_id); /* Determine how many seconds the descriptor will be valid. */ diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 8dbab2e2a7..01147c0072 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -558,7 +558,10 @@ rend_service_prune_list_impl_(void) * matching surviving configured service. If not, close the circuit. */ while ((ocirc = circuit_get_next_service_intro_circ(ocirc))) { int keep_it = 0; - tor_assert(ocirc->rend_data); + if (ocirc->rend_data == NULL) { + /* This is a v3 circuit, ignore it. */ + continue; + } SMARTLIST_FOREACH_BEGIN(surviving_services, const rend_service_t *, s) { if (rend_circuit_pk_digest_eq(ocirc, (uint8_t *) s->pk_digest)) { /* Keep this circuit as we have a matching configured service. */ @@ -915,8 +918,8 @@ rend_service_del_ephemeral(const char *service_id) (circ->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO || circ->purpose == CIRCUIT_PURPOSE_S_INTRO)) { origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(circ); - tor_assert(oc->rend_data); - if (!rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) { + if (oc->rend_data == NULL || + !rend_circuit_pk_digest_eq(oc, (uint8_t *) s->pk_digest)) { continue; } log_debug(LD_REND, "Closing intro point %s for service %s.", @@ -954,16 +957,23 @@ rend_log_intro_limit(const rend_service_t *service, int min_severity) } time_t intro_period_elapsed = time(NULL) - service->intro_period_started; tor_assert_nonfatal(intro_period_elapsed >= 0); - log_fn(severity, LD_REND, "Hidden service %s %s %d intro points in the last " - "%d seconds. Intro circuit launches are limited to %d per %d " - "seconds.", - service->service_id, - exceeded_limit ? "exceeded launch limit with" : "launched", - service->n_intro_circuits_launched, - (int)intro_period_elapsed, - rend_max_intro_circs_per_period(service->n_intro_points_wanted), - INTRO_CIRC_RETRY_PERIOD); - rend_service_dump_stats(severity); + { + char *msg; + static ratelim_t rlimit = RATELIM_INIT(INTRO_CIRC_RETRY_PERIOD); + if ((msg = rate_limit_log(&rlimit, approx_time()))) { + log_fn(severity, LD_REND, + "Hidden service %s %s %d intro points in the last %d seconds. " + "Intro circuit launches are limited to %d per %d seconds.%s", + service->service_id, + exceeded_limit ? "exceeded launch limit with" : "launched", + service->n_intro_circuits_launched, + (int)intro_period_elapsed, + rend_max_intro_circs_per_period(service->n_intro_points_wanted), + INTRO_CIRC_RETRY_PERIOD, msg); + rend_service_dump_stats(severity); + tor_free(msg); + } + } } /** Replace the old value of <b>service</b>-\>desc with one that reflects @@ -2112,7 +2122,7 @@ find_rp_for_intro(const rend_intro_cell_t *intro, if (intro->version == 0 || intro->version == 1) { rp_nickname = (const char *)(intro->u.v0_v1.rp); - node = node_get_by_nickname(rp_nickname, 0); + node = node_get_by_nickname(rp_nickname, NNF_NO_WARN_UNNAMED); if (!node) { if (err_msg_out) { tor_asprintf(&err_msg, @@ -2666,7 +2676,14 @@ rend_service_decrypt_intro( /* Check that this cell actually matches this service key */ /* first DIGEST_LEN bytes of request is intro or service pk digest */ - crypto_pk_get_digest(key, (char *)key_digest); + if (crypto_pk_get_digest(key, (char *)key_digest) < 0) { + if (err_msg_out) + *err_msg_out = tor_strdup("Couldn't compute RSA digest."); + log_warn(LD_BUG, "Couldn't compute key digest."); + status = -7; + goto err; + } + if (tor_memneq(key_digest, intro->pk, DIGEST_LEN)) { if (err_msg_out) { base32_encode(service_id, REND_SERVICE_ID_LEN_BASE32 + 1, @@ -3318,9 +3335,10 @@ rend_service_rendezvous_has_opened(origin_circuit_t *circuit) NULL); rend_cookie = circuit->rend_data->rend_cookie; - /* Declare the circuit dirty to avoid reuse, and for path-bias */ - if (!circuit->base_.timestamp_dirty) - circuit->base_.timestamp_dirty = time(NULL); + /* Declare the circuit dirty to avoid reuse, and for path-bias. We set the + * timestamp regardless of its content because that circuit could have been + * cannibalized so in any cases, we are about to use that circuit more. */ + circuit->base_.timestamp_dirty = time(NULL); /* This may be redundant */ pathbias_count_use_attempt(circuit); @@ -3915,7 +3933,12 @@ rend_max_intro_circs_per_period(unsigned int n_intro_points_wanted) /* Allow all but one of the initial connections to fail and be * retried. (If all fail, we *want* to wait, because something is broken.) */ tor_assert(n_intro_points_wanted <= NUM_INTRO_POINTS_MAX); - return (int)(2*n_intro_points_wanted + NUM_INTRO_POINTS_EXTRA); + + /* For the normal use case, 3 intro points plus 2 extra for performance and + * allow that twice because once every 24h or so, we can do it twice for two + * descriptors that is the current one and the next one. So (3 + 2) * 2 == + * 12 allowed attempts for one period. */ + return ((n_intro_points_wanted + NUM_INTRO_POINTS_EXTRA) * 2); } /** For every service, check how many intro points it currently has, and: @@ -4240,7 +4263,6 @@ rend_service_set_connection_addr_port(edge_connection_t *conn, tor_assert(circ->base_.purpose == CIRCUIT_PURPOSE_S_REND_JOINED); tor_assert(circ->rend_data); log_debug(LD_REND,"beginning to hunt for addr/port"); - /* XXX: This is version 2 specific (only one supported). */ rend_pk_digest = (char *) rend_data_get_pk_digest(circ->rend_data, NULL); base32_encode(serviceid, REND_SERVICE_ID_LEN_BASE32+1, rend_pk_digest, REND_SERVICE_ID_LEN); diff --git a/src/or/router.c b/src/or/router.c index 7fad572657..2bc7a875f1 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -257,7 +257,11 @@ set_server_identity_key(crypto_pk_t *k) { crypto_pk_free(server_identitykey); server_identitykey = k; - crypto_pk_get_digest(server_identitykey, server_identitykey_digest); + if (crypto_pk_get_digest(server_identitykey, + server_identitykey_digest) < 0) { + log_err(LD_BUG, "Couldn't compute our own identity key digest."); + tor_assert(0); + } } /** Make sure that we have set up our identity keys to match or not match as @@ -956,8 +960,12 @@ init_keys(void) } cert = get_my_v3_authority_cert(); if (cert) { - crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key, - v3_digest); + if (crypto_pk_get_digest(get_my_v3_authority_cert()->identity_key, + v3_digest) < 0) { + log_err(LD_BUG, "Couldn't compute my v3 authority identity key " + "digest."); + return -1; + } v3_digest_set = 1; } } @@ -2279,7 +2287,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) if (!strcasecmp(name, options->Nickname)) continue; /* Don't list ourself, that's redundant */ else - member = node_get_by_nickname(name, 1); + member = node_get_by_nickname(name, 0); if (!member) { int is_legal = is_legal_nickname_or_hexdigest(name); if (!smartlist_contains_string(warned_nonexistent_family, name) && @@ -3489,7 +3497,7 @@ router_get_description(char *buf, const routerinfo_t *ri) return "<null>"; return format_node_description(buf, ri->cache_info.identity_digest, - router_is_named(ri), + 0, ri->nickname, NULL, ri->addr); @@ -3615,21 +3623,16 @@ extend_info_describe(const extend_info_t *ei) * verbose representation of the identity of <b>router</b>. The format is: * A dollar sign. * The upper-case hexadecimal encoding of the SHA1 hash of router's identity. - * A "=" if the router is named; a "~" if it is not. + * A "=" if the router is named (no longer implemented); a "~" if it is not. * The router's nickname. **/ void router_get_verbose_nickname(char *buf, const routerinfo_t *router) { - const char *good_digest = networkstatus_get_router_digest_by_nickname( - router->nickname); - int is_named = good_digest && tor_memeq(good_digest, - router->cache_info.identity_digest, - DIGEST_LEN); buf[0] = '$'; base16_encode(buf+1, HEX_DIGEST_LEN+1, router->cache_info.identity_digest, DIGEST_LEN); - buf[1+HEX_DIGEST_LEN] = is_named ? '=' : '~'; + buf[1+HEX_DIGEST_LEN] = '~'; strlcpy(buf+1+HEX_DIGEST_LEN+1, router->nickname, MAX_NICKNAME_LEN+1); } diff --git a/src/or/routerkeys.c b/src/or/routerkeys.c index 2f20758b5b..4822ff3be3 100644 --- a/src/or/routerkeys.c +++ b/src/or/routerkeys.c @@ -1338,7 +1338,9 @@ make_tap_onion_key_crosscert(const crypto_pk_t *onion_key, uint8_t signed_data[DIGEST_LEN + ED25519_PUBKEY_LEN]; *len_out = 0; - crypto_pk_get_digest(rsa_id_key, (char*)signed_data); + if (crypto_pk_get_digest(rsa_id_key, (char*)signed_data) < 0) { + return NULL; + } memcpy(signed_data + DIGEST_LEN, master_id_key->pubkey, ED25519_PUBKEY_LEN); int r = crypto_pk_private_sign(onion_key, diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 9894019476..9111d93c3c 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -2799,6 +2799,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, const int need_desc = (flags & CRN_NEED_DESC) != 0; const int pref_addr = (flags & CRN_PREF_ADDR) != 0; const int direct_conn = (flags & CRN_DIRECT_CONN) != 0; + const int rendezvous_v3 = (flags & CRN_RENDEZVOUS_V3) != 0; smartlist_t *sl=smartlist_new(), *excludednodes=smartlist_new(); @@ -2810,12 +2811,19 @@ router_choose_random_node(smartlist_t *excludedsmartlist, rule = weight_for_exit ? WEIGHT_FOR_EXIT : (need_guard ? WEIGHT_FOR_GUARD : WEIGHT_FOR_MID); - /* Exclude relays that allow single hop exit circuits. This is an obsolete - * option since 0.2.9.2-alpha and done by default in 0.3.1.0-alpha. */ - SMARTLIST_FOREACH(nodelist_get_list(), node_t *, node, + SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), node_t *, node) { if (node_allows_single_hop_exits(node)) { + /* Exclude relays that allow single hop exit circuits. This is an + * obsolete option since 0.2.9.2-alpha and done by default in + * 0.3.1.0-alpha. */ smartlist_add(excludednodes, node); - }); + } else if (rendezvous_v3 && + !node_supports_v3_rendezvous_point(node)) { + /* Exclude relays that do not support to rendezvous for a hidden service + * version 3. */ + smartlist_add(excludednodes, node); + } + } SMARTLIST_FOREACH_END(node); if ((r = routerlist_find_my_routerinfo())) routerlist_add_node_and_family(excludednodes, r); @@ -2926,7 +2934,7 @@ hex_digest_nickname_decode(const char *hexdigest, * <b>hexdigest</b> is malformed, or it doesn't match. */ int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named) + const char *nickname) { char digest[DIGEST_LEN]; char nn_char='\0'; @@ -2935,30 +2943,20 @@ hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, if (hex_digest_nickname_decode(hexdigest, digest, &nn_char, nn_buf) == -1) return 0; - if (nn_char == '=' || nn_char == '~') { - if (!nickname) + if (nn_char == '=') { + return 0; + } + + if (nn_char == '~') { + if (!nickname) // XXX This seems wrong. -NM return 0; if (strcasecmp(nn_buf, nickname)) return 0; - if (nn_char == '=' && !is_named) - return 0; } return tor_memeq(digest, identity_digest, DIGEST_LEN); } -/** Return true iff <b>router</b> is listed as named in the current - * consensus. */ -int -router_is_named(const routerinfo_t *router) -{ - const char *digest = - networkstatus_get_router_digest_by_nickname(router->nickname); - - return (digest && - tor_memeq(digest, router->cache_info.identity_digest, DIGEST_LEN)); -} - /** Return true iff <b>digest</b> is the digest of the identity key of a * trusted directory matching at least one bit of <b>type</b>. If <b>type</b> * is zero (NO_DIRINFO), or ALL_DIRINFO, any authority is okay. */ diff --git a/src/or/routerlist.h b/src/or/routerlist.h index e0ed4e623a..27c8d88f05 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -80,7 +80,6 @@ const node_t *router_choose_random_node(smartlist_t *excludedsmartlist, struct routerset_t *excludedset, router_crn_flags_t flags); -int router_is_named(const routerinfo_t *router); int router_digest_is_trusted_dir_type(const char *digest, dirinfo_type_t type); #define router_digest_is_trusted_dir(d) \ @@ -228,7 +227,7 @@ int hex_digest_nickname_decode(const char *hexdigest, char *nickname_out); int hex_digest_nickname_matches(const char *hexdigest, const char *identity_digest, - const char *nickname, int is_named); + const char *nickname); #ifdef ROUTERLIST_PRIVATE STATIC int choose_array_element_by_weight(const uint64_t *entries, diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 3449e6f6b5..08c3fc0a02 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -2708,6 +2708,9 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->supports_v3_hsdir = protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, PROTOVER_HSDIR_V3); + rs->supports_v3_rendezvous_point = + protocol_list_supports_protocol(tok->args[0], PRT_HSDIR, + PROTOVER_HS_RENDEZVOUS_POINT_V3); } if ((tok = find_opt_by_keyword(tokens, K_V))) { tor_assert(tok->n_args == 1); @@ -5297,7 +5300,10 @@ rend_parse_v2_service_descriptor(rend_service_descriptor_t **parsed_out, "v2 rendezvous service descriptor") < 0) goto err; /* Verify that descriptor ID belongs to public key and secret ID part. */ - crypto_pk_get_digest(result->pk, public_key_hash); + if (crypto_pk_get_digest(result->pk, public_key_hash) < 0) { + log_warn(LD_REND, "Unable to compute rend descriptor public key digest"); + goto err; + } rend_get_descriptor_id_bytes(test_desc_id, public_key_hash, secret_id_part); if (tor_memneq(desc_id_out, test_desc_id, DIGEST_LEN)) { diff --git a/src/or/routerset.c b/src/or/routerset.c index 4906c6a51d..54e26ef943 100644 --- a/src/or/routerset.c +++ b/src/or/routerset.c @@ -362,7 +362,7 @@ routerset_get_all_nodes(smartlist_t *out, const routerset_t *routerset, /* No routers are specified by type; all are given by name or digest. * we can do a lookup in O(len(routerset)). */ SMARTLIST_FOREACH(routerset->list, const char *, name, { - const node_t *node = node_get_by_nickname(name, 1); + const node_t *node = node_get_by_nickname(name, 0); if (node) { if (!running_only || node->is_running) if (!routerset_contains_node(excludeset, node)) diff --git a/src/or/torcert.c b/src/or/torcert.c index 69b157446a..05a0a518e1 100644 --- a/src/or/torcert.c +++ b/src/or/torcert.c @@ -356,12 +356,12 @@ tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, * * Return 0 on success, negative on failure. */ -int -rsa_ed25519_crosscert_check(const uint8_t *crosscert, - const size_t crosscert_len, - const crypto_pk_t *rsa_id_key, - const ed25519_public_key_t *master_key, - const time_t reject_if_expired_before) +MOCK_IMPL(int, +rsa_ed25519_crosscert_check, (const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before)) { rsa_ed_crosscert_t *cc = NULL; int rv; diff --git a/src/or/torcert.h b/src/or/torcert.h index 51f7665f1e..416ff7aa23 100644 --- a/src/or/torcert.h +++ b/src/or/torcert.h @@ -75,11 +75,12 @@ ssize_t tor_make_rsa_ed25519_crosscert(const ed25519_public_key_t *ed_key, const crypto_pk_t *rsa_key, time_t expires, uint8_t **cert); -int rsa_ed25519_crosscert_check(const uint8_t *crosscert, - const size_t crosscert_len, - const crypto_pk_t *rsa_id_key, - const ed25519_public_key_t *master_key, - const time_t reject_if_expired_before); +MOCK_DECL(int, +rsa_ed25519_crosscert_check, (const uint8_t *crosscert, + const size_t crosscert_len, + const crypto_pk_t *rsa_id_key, + const ed25519_public_key_t *master_key, + const time_t reject_if_expired_before)); or_handshake_certs_t *or_handshake_certs_new(void); void or_handshake_certs_free(or_handshake_certs_t *certs); |