diff options
Diffstat (limited to 'src')
160 files changed, 23692 insertions, 2612 deletions
diff --git a/src/common/address.c b/src/common/address.c index cfa8fd1dca..86c32efdd9 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -1039,6 +1039,8 @@ tor_addr_compare_masked(const tor_addr_t *addr1, const tor_addr_t *addr2, return r; } case AF_INET6: { + if (mbits > 128) + mbits = 128; const uint8_t *a1 = tor_addr_to_in6_addr8(addr1); const uint8_t *a2 = tor_addr_to_in6_addr8(addr2); const int bytes = mbits >> 3; @@ -1272,7 +1274,7 @@ typedef ULONG (WINAPI *GetAdaptersAddresses_fn_t)( * into smartlist of <b>tor_addr_t</b> structures. */ STATIC smartlist_t * -ifaddrs_to_smartlist(const struct ifaddrs *ifa) +ifaddrs_to_smartlist(const struct ifaddrs *ifa, sa_family_t family) { smartlist_t *result = smartlist_new(); const struct ifaddrs *i; @@ -1286,6 +1288,8 @@ ifaddrs_to_smartlist(const struct ifaddrs *ifa) if (i->ifa_addr->sa_family != AF_INET && i->ifa_addr->sa_family != AF_INET6) continue; + if (family != AF_UNSPEC && i->ifa_addr->sa_family != family) + continue; if (tor_addr_from_sockaddr(&tmp, i->ifa_addr, NULL) < 0) continue; smartlist_add(result, tor_memdup(&tmp, sizeof(tmp))); @@ -1299,7 +1303,7 @@ ifaddrs_to_smartlist(const struct ifaddrs *ifa) * <b>tor_addr_t</b> structures. */ STATIC smartlist_t * -get_interface_addresses_ifaddrs(int severity) +get_interface_addresses_ifaddrs(int severity, sa_family_t family) { /* Most free Unixy systems provide getifaddrs, which gives us a linked list @@ -1312,7 +1316,7 @@ get_interface_addresses_ifaddrs(int severity) return NULL; } - result = ifaddrs_to_smartlist(ifa); + result = ifaddrs_to_smartlist(ifa, family); freeifaddrs(ifa); @@ -1354,7 +1358,7 @@ ip_adapter_addresses_to_smartlist(const IP_ADAPTER_ADDRESSES *addresses) * <b>tor_addr_t</b> structures. */ STATIC smartlist_t * -get_interface_addresses_win32(int severity) +get_interface_addresses_win32(int severity, sa_family_t family) { /* Windows XP began to provide GetAdaptersAddresses. Windows 2000 had a @@ -1388,7 +1392,7 @@ get_interface_addresses_win32(int severity) /* Guess how much space we need. */ size = 15*1024; addresses = tor_malloc(size); - res = fn(AF_UNSPEC, FLAGS, NULL, addresses, &size); + res = fn(family, FLAGS, NULL, addresses, &size); if (res == ERROR_BUFFER_OVERFLOW) { /* we didn't guess that we needed enough space; try again */ tor_free(addresses); @@ -1462,15 +1466,26 @@ ifreq_to_smartlist(char *buf, size_t buflen) * <b>tor_addr_t</b> structures. */ STATIC smartlist_t * -get_interface_addresses_ioctl(int severity) +get_interface_addresses_ioctl(int severity, sa_family_t family) { /* Some older unixy systems make us use ioctl(SIOCGIFCONF) */ struct ifconf ifc; int fd; smartlist_t *result = NULL; - /* This interface, AFAICT, only supports AF_INET addresses */ - fd = socket(AF_INET, SOCK_DGRAM, 0); + /* This interface, AFAICT, only supports AF_INET addresses, + * except on AIX. For Solaris, we could use SIOCGLIFCONF. */ + + /* Bail out if family is neither AF_INET nor AF_UNSPEC since + * ioctl() technique supports non-IPv4 interface addresses on + * a small number of niche systems only. If family is AF_UNSPEC, + * fall back to getting AF_INET addresses only. */ + if (family == AF_UNSPEC) + family = AF_INET; + else if (family != AF_INET) + return NULL; + + fd = socket(family, SOCK_DGRAM, 0); if (fd < 0) { tor_log(severity, LD_NET, "socket failed: %s", strerror(errno)); goto done; @@ -1505,21 +1520,23 @@ get_interface_addresses_ioctl(int severity) /** Try to ask our network interfaces what addresses they are bound to. * Return a new smartlist of tor_addr_t on success, and NULL on failure. * (An empty smartlist indicates that we successfully learned that we have no - * addresses.) Log failure messages at <b>severity</b>. */ + * addresses.) Log failure messages at <b>severity</b>. Only return the + * interface addresses of requested <b>family</b> and ignore the addresses + * of other address families. */ MOCK_IMPL(smartlist_t *, -get_interface_addresses_raw,(int severity)) +get_interface_addresses_raw,(int severity, sa_family_t family)) { smartlist_t *result = NULL; #if defined(HAVE_IFADDRS_TO_SMARTLIST) - if ((result = get_interface_addresses_ifaddrs(severity))) + if ((result = get_interface_addresses_ifaddrs(severity, family))) return result; #endif #if defined(HAVE_IP_ADAPTER_TO_SMARTLIST) - if ((result = get_interface_addresses_win32(severity))) + if ((result = get_interface_addresses_win32(severity, family))) return result; #endif #if defined(HAVE_IFCONF_TO_SMARTLIST) - if ((result = get_interface_addresses_ioctl(severity))) + if ((result = get_interface_addresses_ioctl(severity, family))) return result; #endif (void) severity; @@ -1527,7 +1544,7 @@ get_interface_addresses_raw,(int severity)) } /** Return true iff <b>a</b> is a multicast address. */ -STATIC int +int tor_addr_is_multicast(const tor_addr_t *a) { sa_family_t family = tor_addr_family(a); @@ -1544,8 +1561,9 @@ tor_addr_is_multicast(const tor_addr_t *a) } /** Attempt to retrieve IP address of current host by utilizing some - * UDP socket trickery. Only look for address of given <b>family</b>. - * Set result to *<b>addr</b>. Return 0 on success, -1 on failure. + * UDP socket trickery. Only look for address of given <b>family</b> + * (only AF_INET and AF_INET6 are supported). Set result to *<b>addr</b>. + * Return 0 on success, -1 on failure. */ MOCK_IMPL(int, get_interface_address6_via_udp_socket_hack,(int severity, @@ -1683,15 +1701,9 @@ MOCK_IMPL(smartlist_t *,get_interface_address6_list,(int severity, tor_addr_t addr; /* Try to do this the smart way if possible. */ - if ((addrs = get_interface_addresses_raw(severity))) { + if ((addrs = get_interface_addresses_raw(severity, family))) { SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, a) { - if (family != AF_UNSPEC && family != tor_addr_family(a)) { - SMARTLIST_DEL_CURRENT(addrs, a); - tor_free(a); - continue; - } - if (tor_addr_is_loopback(a) || tor_addr_is_multicast(a)) { SMARTLIST_DEL_CURRENT(addrs, a); @@ -1717,15 +1729,27 @@ MOCK_IMPL(smartlist_t *,get_interface_address6_list,(int severity, } /* Okay, the smart way is out. */ - if (get_interface_address6_via_udp_socket_hack(severity,family,&addr)) - return smartlist_new(); - if (!include_internal && tor_addr_is_internal(&addr, 0)) { - return smartlist_new(); - } else { - addrs = smartlist_new(); - smartlist_add(addrs, tor_dup_addr(&addr)); - return addrs; + addrs = smartlist_new(); + + if (family == AF_INET || family == AF_UNSPEC) { + if (get_interface_address6_via_udp_socket_hack(severity,AF_INET, + &addr) == 0) { + if (include_internal || !tor_addr_is_internal(&addr, 0)) { + smartlist_add(addrs, tor_dup_addr(&addr)); + } + } } + + if (family == AF_INET6 || family == AF_UNSPEC) { + if (get_interface_address6_via_udp_socket_hack(severity,AF_INET6, + &addr) == 0) { + if (include_internal || !tor_addr_is_internal(&addr, 0)) { + smartlist_add(addrs, tor_dup_addr(&addr)); + } + } + } + + return addrs; } /* ====== @@ -1781,7 +1805,7 @@ tor_addr_port_parse(int severity, const char *addrport, } /** Given an address of the form "host[:port]", try to divide it into its host - * ane port portions, setting *<b>address_out</b> to a newly allocated string + * and port portions, setting *<b>address_out</b> to a newly allocated string * holding the address portion and *<b>port_out</b> to the port (or 0 if no * port is given). Return 0 on success, -1 on failure. */ int diff --git a/src/common/address.h b/src/common/address.h index d2841e1c9d..76090136d8 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -73,13 +73,13 @@ typedef struct tor_addr_port_t #define TOR_ADDR_NULL {AF_UNSPEC, {0}} -static INLINE const struct in6_addr *tor_addr_to_in6(const tor_addr_t *a); -static INLINE uint32_t tor_addr_to_ipv4n(const tor_addr_t *a); -static INLINE uint32_t tor_addr_to_ipv4h(const tor_addr_t *a); -static INLINE uint32_t tor_addr_to_mapped_ipv4h(const tor_addr_t *a); -static INLINE sa_family_t tor_addr_family(const tor_addr_t *a); -static INLINE const struct in_addr *tor_addr_to_in(const tor_addr_t *a); -static INLINE int tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u); +static inline const struct in6_addr *tor_addr_to_in6(const tor_addr_t *a); +static inline uint32_t tor_addr_to_ipv4n(const tor_addr_t *a); +static inline uint32_t tor_addr_to_ipv4h(const tor_addr_t *a); +static inline uint32_t tor_addr_to_mapped_ipv4h(const tor_addr_t *a); +static inline sa_family_t tor_addr_family(const tor_addr_t *a); +static inline const struct in_addr *tor_addr_to_in(const tor_addr_t *a); +static inline int tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u); socklen_t tor_addr_to_sockaddr(const tor_addr_t *a, uint16_t port, struct sockaddr *sa_out, socklen_t len); @@ -91,7 +91,7 @@ char *tor_sockaddr_to_str(const struct sockaddr *sa); /** Return an in6_addr* equivalent to <b>a</b>, or NULL if <b>a</b> is not * an IPv6 address. */ -static INLINE const struct in6_addr * +static inline const struct in6_addr * tor_addr_to_in6(const tor_addr_t *a) { return a->family == AF_INET6 ? &a->addr.in6_addr : NULL; @@ -115,14 +115,14 @@ tor_addr_to_in6(const tor_addr_t *a) /** Return an IPv4 address in network order for <b>a</b>, or 0 if * <b>a</b> is not an IPv4 address. */ -static INLINE uint32_t +static inline uint32_t tor_addr_to_ipv4n(const tor_addr_t *a) { return a->family == AF_INET ? a->addr.in_addr.s_addr : 0; } /** Return an IPv4 address in host order for <b>a</b>, or 0 if * <b>a</b> is not an IPv4 address. */ -static INLINE uint32_t +static inline uint32_t tor_addr_to_ipv4h(const tor_addr_t *a) { return ntohl(tor_addr_to_ipv4n(a)); @@ -131,7 +131,7 @@ tor_addr_to_ipv4h(const tor_addr_t *a) * 0 if <b>a</b> is not an IPv6 address. * * (Does not check whether the address is really a mapped address */ -static INLINE uint32_t +static inline uint32_t tor_addr_to_mapped_ipv4h(const tor_addr_t *a) { if (a->family == AF_INET6) { @@ -149,21 +149,21 @@ tor_addr_to_mapped_ipv4h(const tor_addr_t *a) } /** Return the address family of <b>a</b>. Possible values are: * AF_INET6, AF_INET, AF_UNSPEC. */ -static INLINE sa_family_t +static inline sa_family_t tor_addr_family(const tor_addr_t *a) { return a->family; } /** Return an in_addr* equivalent to <b>a</b>, or NULL if <b>a</b> is not * an IPv4 address. */ -static INLINE const struct in_addr * +static inline const struct in_addr * tor_addr_to_in(const tor_addr_t *a) { return a->family == AF_INET ? &a->addr.in_addr : NULL; } /** Return true iff <b>a</b> is an IPv4 address equal to the host-ordered * address in <b>u</b>. */ -static INLINE int +static inline int tor_addr_eq_ipv4h(const tor_addr_t *a, uint32_t u) { return a->family == AF_INET ? (tor_addr_to_ipv4h(a) == u) : 0; @@ -221,6 +221,7 @@ int tor_addr_is_internal_(const tor_addr_t *ip, int for_listening, const char *filename, int lineno); #define tor_addr_is_internal(addr, for_listening) \ tor_addr_is_internal_((addr), (for_listening), SHORT_FILE__, __LINE__) +int tor_addr_is_multicast(const tor_addr_t *a); /** Longest length that can be required for a reverse lookup name. */ /* 32 nybbles, 32 dots, 8 characters of "ip6.arpa", 1 NUL: 73 characters. */ @@ -288,7 +289,7 @@ char *tor_dup_ip(uint32_t addr) ATTR_MALLOC; MOCK_DECL(int,get_interface_address,(int severity, uint32_t *addr)); /** Free a smartlist of IP addresses returned by get_interface_address_list. */ -static INLINE void +static inline void free_interface_address_list(smartlist_t *addrs) { free_interface_address6_list(addrs); @@ -301,7 +302,7 @@ free_interface_address_list(smartlist_t *addrs) * Returns NULL on failure. * Use free_interface_address_list to free the returned list. */ -static INLINE smartlist_t * +static inline smartlist_t * get_interface_address_list(int severity, int include_internal) { return get_interface_address6_list(severity, AF_INET, include_internal); @@ -310,27 +311,31 @@ get_interface_address_list(int severity, int include_internal) tor_addr_port_t *tor_addr_port_new(const tor_addr_t *addr, uint16_t port); #ifdef ADDRESS_PRIVATE -MOCK_DECL(smartlist_t *,get_interface_addresses_raw,(int severity)); -STATIC int tor_addr_is_multicast(const tor_addr_t *a); +MOCK_DECL(smartlist_t *,get_interface_addresses_raw,(int severity, + sa_family_t family)); MOCK_DECL(int,get_interface_address6_via_udp_socket_hack,(int severity, sa_family_t family, tor_addr_t *addr)); #ifdef HAVE_IFADDRS_TO_SMARTLIST -STATIC smartlist_t *ifaddrs_to_smartlist(const struct ifaddrs *ifa); -STATIC smartlist_t *get_interface_addresses_ifaddrs(int severity); +STATIC smartlist_t *ifaddrs_to_smartlist(const struct ifaddrs *ifa, + sa_family_t family); +STATIC smartlist_t *get_interface_addresses_ifaddrs(int severity, + sa_family_t family); #endif #ifdef HAVE_IP_ADAPTER_TO_SMARTLIST STATIC smartlist_t *ip_adapter_addresses_to_smartlist( const IP_ADAPTER_ADDRESSES *addresses); -STATIC smartlist_t *get_interface_addresses_win32(int severity); +STATIC smartlist_t *get_interface_addresses_win32(int severity, + sa_family_t family); #endif #ifdef HAVE_IFCONF_TO_SMARTLIST STATIC smartlist_t *ifreq_to_smartlist(char *ifr, size_t buflen); -STATIC smartlist_t *get_interface_addresses_ioctl(int severity); +STATIC smartlist_t *get_interface_addresses_ioctl(int severity, + sa_family_t family); #endif #endif // ADDRESS_PRIVATE diff --git a/src/common/aes.c b/src/common/aes.c index 5f2c3f2f03..7b6cc39b20 100644 --- a/src/common/aes.c +++ b/src/common/aes.c @@ -271,7 +271,7 @@ evaluate_ctr_for_aes(void) * Helper function: set <b>cipher</b>'s internal buffer to the encrypted * value of the current counter. */ -static INLINE void +static inline void aes_fill_buf_(aes_cnt_cipher_t *cipher) { /* We don't currently use OpenSSL's counter mode implementation because: diff --git a/src/common/backtrace.c b/src/common/backtrace.c index bed0442471..94de1eb5ee 100644 --- a/src/common/backtrace.c +++ b/src/common/backtrace.c @@ -215,9 +215,10 @@ int configure_backtrace_handler(const char *tor_version) { tor_free(bt_version); - if (!tor_version) - tor_version = ""; - tor_asprintf(&bt_version, "Tor %s", tor_version); + if (tor_version) + tor_asprintf(&bt_version, "Tor %s", tor_version); + else + tor_asprintf(&bt_version, "Tor"); return install_bt_handler(); } diff --git a/src/common/compat.c b/src/common/compat.c index 7d72b4b7fd..c1d4f89621 100644 --- a/src/common/compat.c +++ b/src/common/compat.c @@ -71,6 +71,9 @@ #ifdef HAVE_SYS_STATVFS_H #include <sys/statvfs.h> #endif +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif #ifdef _WIN32 #include <conio.h> @@ -1078,7 +1081,7 @@ static int n_sockets_open = 0; static tor_mutex_t *socket_accounting_mutex = NULL; /** Helper: acquire the socket accounting lock. */ -static INLINE void +static inline void socket_accounting_lock(void) { if (PREDICT_UNLIKELY(!socket_accounting_mutex)) @@ -1087,7 +1090,7 @@ socket_accounting_lock(void) } /** Helper: release the socket accounting lock. */ -static INLINE void +static inline void socket_accounting_unlock(void) { tor_mutex_release(socket_accounting_mutex); @@ -1163,7 +1166,7 @@ tor_close_socket(tor_socket_t s) #ifdef DEBUG_SOCKET_COUNTING /** Helper: if DEBUG_SOCKET_COUNTING is enabled, remember that <b>s</b> is * now an open socket. */ -static INLINE void +static inline void mark_socket_open(tor_socket_t s) { /* XXXX This bitarray business will NOT work on windows: sockets aren't @@ -1486,6 +1489,20 @@ tor_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) } #ifdef NEED_ERSATZ_SOCKETPAIR + +static inline socklen_t +SIZEOF_SOCKADDR(int domain) +{ + switch (domain) { + case AF_INET: + return sizeof(struct sockaddr_in); + case AF_INET6: + return sizeof(struct sockaddr_in6); + default: + return 0; + } +} + /** * Helper used to implement socketpair on systems that lack it, by * making a direct connection to localhost. @@ -1501,13 +1518,21 @@ tor_ersatz_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) tor_socket_t listener = TOR_INVALID_SOCKET; tor_socket_t connector = TOR_INVALID_SOCKET; tor_socket_t acceptor = TOR_INVALID_SOCKET; - struct sockaddr_in listen_addr; - struct sockaddr_in connect_addr; + tor_addr_t listen_tor_addr; + struct sockaddr_storage connect_addr_ss, listen_addr_ss; + struct sockaddr *listen_addr = (struct sockaddr *) &listen_addr_ss; + uint16_t listen_port = 0; + tor_addr_t connect_tor_addr; + uint16_t connect_port = 0; + struct sockaddr *connect_addr = (struct sockaddr *) &connect_addr_ss; socklen_t size; int saved_errno = -1; + int ersatz_domain = AF_INET; - memset(&connect_addr, 0, sizeof(connect_addr)); - memset(&listen_addr, 0, sizeof(listen_addr)); + memset(&connect_tor_addr, 0, sizeof(connect_tor_addr)); + memset(&connect_addr_ss, 0, sizeof(connect_addr_ss)); + memset(&listen_tor_addr, 0, sizeof(listen_tor_addr)); + memset(&listen_addr_ss, 0, sizeof(listen_addr_ss)); if (protocol #ifdef AF_UNIX @@ -1524,47 +1549,71 @@ tor_ersatz_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) return -EINVAL; } - listener = tor_open_socket(AF_INET, type, 0); - if (!SOCKET_OK(listener)) - return -tor_socket_errno(-1); - memset(&listen_addr, 0, sizeof(listen_addr)); - listen_addr.sin_family = AF_INET; - listen_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - listen_addr.sin_port = 0; /* kernel chooses port. */ - if (bind(listener, (struct sockaddr *) &listen_addr, sizeof (listen_addr)) - == -1) + listener = tor_open_socket(ersatz_domain, type, 0); + if (!SOCKET_OK(listener)) { + int first_errno = tor_socket_errno(-1); + if (first_errno == SOCK_ERRNO(EPROTONOSUPPORT) + && ersatz_domain == AF_INET) { + /* Assume we're on an IPv6-only system */ + ersatz_domain = AF_INET6; + listener = tor_open_socket(ersatz_domain, type, 0); + if (!SOCKET_OK(listener)) { + /* Keep the previous behaviour, which was to return the IPv4 error. + * (This may be less informative on IPv6-only systems.) + * XX/teor - is there a better way to decide which errno to return? + * (I doubt we care much either way, once there is an error.) + */ + return -first_errno; + } + } + } + /* If there is no 127.0.0.1 or ::1, this will and must fail. Otherwise, we + * risk exposing a socketpair on a routable IP address. (Some BSD jails + * use a routable address for localhost. Fortunately, they have the real + * AF_UNIX socketpair.) */ + if (ersatz_domain == AF_INET) { + tor_addr_from_ipv4h(&listen_tor_addr, INADDR_LOOPBACK); + } else { + tor_addr_parse(&listen_tor_addr, "[::1]"); + } + tor_assert(tor_addr_is_loopback(&listen_tor_addr)); + size = tor_addr_to_sockaddr(&listen_tor_addr, + 0 /* kernel chooses port. */, + listen_addr, + sizeof(listen_addr_ss)); + if (bind(listener, listen_addr, size) == -1) goto tidy_up_and_fail; if (listen(listener, 1) == -1) goto tidy_up_and_fail; - connector = tor_open_socket(AF_INET, type, 0); + connector = tor_open_socket(ersatz_domain, type, 0); if (!SOCKET_OK(connector)) goto tidy_up_and_fail; /* We want to find out the port number to connect to. */ - size = sizeof(connect_addr); - if (getsockname(listener, (struct sockaddr *) &connect_addr, &size) == -1) + size = sizeof(connect_addr_ss); + if (getsockname(listener, connect_addr, &size) == -1) goto tidy_up_and_fail; - if (size != sizeof (connect_addr)) + if (size != SIZEOF_SOCKADDR (connect_addr->sa_family)) goto abort_tidy_up_and_fail; - if (connect(connector, (struct sockaddr *) &connect_addr, - sizeof(connect_addr)) == -1) + if (connect(connector, connect_addr, size) == -1) goto tidy_up_and_fail; - size = sizeof(listen_addr); - acceptor = tor_accept_socket(listener, - (struct sockaddr *) &listen_addr, &size); + size = sizeof(listen_addr_ss); + acceptor = tor_accept_socket(listener, listen_addr, &size); if (!SOCKET_OK(acceptor)) goto tidy_up_and_fail; - if (size != sizeof(listen_addr)) + if (size != SIZEOF_SOCKADDR(listen_addr->sa_family)) goto abort_tidy_up_and_fail; /* Now check we are talking to ourself by matching port and host on the two sockets. */ - if (getsockname(connector, (struct sockaddr *) &connect_addr, &size) == -1) + if (getsockname(connector, connect_addr, &size) == -1) goto tidy_up_and_fail; - if (size != sizeof (connect_addr) - || listen_addr.sin_family != connect_addr.sin_family - || listen_addr.sin_addr.s_addr != connect_addr.sin_addr.s_addr - || listen_addr.sin_port != connect_addr.sin_port) { + /* Set *_tor_addr and *_port to the address and port that was used */ + tor_addr_from_sockaddr(&listen_tor_addr, listen_addr, &listen_port); + tor_addr_from_sockaddr(&connect_tor_addr, connect_addr, &connect_port); + if (size != SIZEOF_SOCKADDR (connect_addr->sa_family) + || tor_addr_compare(&listen_tor_addr, &connect_tor_addr, CMP_SEMANTIC) + || listen_port != connect_port) { goto abort_tidy_up_and_fail; } tor_close_socket(listener); @@ -1590,6 +1639,9 @@ tor_ersatz_socketpair(int family, int type, int protocol, tor_socket_t fd[2]) tor_close_socket(acceptor); return -saved_errno; } + +#undef SIZEOF_SOCKADDR + #endif /* Return the maximum number of allowed sockets. */ @@ -1917,17 +1969,99 @@ tor_getpwuid(uid_t uid) } #endif +/** Return true iff we were compiled with capability support, and capabilities + * seem to work. **/ +int +have_capability_support(void) +{ +#ifdef HAVE_LINUX_CAPABILITIES + cap_t caps = cap_get_proc(); + if (caps == NULL) + return 0; + cap_free(caps); + return 1; +#else + return 0; +#endif +} + +#ifdef HAVE_LINUX_CAPABILITIES +/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as + * appropriate. + * + * If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and + * CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across + * setuid(). + * + * If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable + * PR_KEEPCAPS. + * + * Return 0 on success, and -1 on failure. + */ +static int +drop_capabilities(int pre_setuid) +{ + /* We keep these three capabilities, and these only, as we setuid. + * After we setuid, we drop all but the first. */ + const cap_value_t caplist[] = { + CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID + }; + const char *where = pre_setuid ? "pre-setuid" : "post-setuid"; + const int n_effective = pre_setuid ? 3 : 1; + const int n_permitted = pre_setuid ? 3 : 1; + const int n_inheritable = 1; + const int keepcaps = pre_setuid ? 1 : 0; + + /* Sets whether we keep capabilities across a setuid. */ + if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) { + log_warn(LD_CONFIG, "Unable to call prctl() %s: %s", + where, strerror(errno)); + return -1; + } + + cap_t caps = cap_get_proc(); + if (!caps) { + log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s", + where, strerror(errno)); + return -1; + } + cap_clear(caps); + + cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET); + cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET); + cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET); + + int r = cap_set_proc(caps); + cap_free(caps); + if (r < 0) { + log_warn(LD_CONFIG, "No permission to set capabilities %s: %s", + where, strerror(errno)); + return -1; + } + + return 0; +} +#endif + /** Call setuid and setgid to run as <b>user</b> and switch to their * primary group. Return 0 on success. On failure, log and return -1. + * + * If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability + * system to retain the abilitity to bind low ports. + * + * If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have + * don't have capability support. */ int -switch_id(const char *user) +switch_id(const char *user, const unsigned flags) { #ifndef _WIN32 const struct passwd *pw = NULL; uid_t old_uid; gid_t old_gid; static int have_already_switched_id = 0; + const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW); + const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS); tor_assert(user); @@ -1951,6 +2085,20 @@ switch_id(const char *user) return -1; } +#ifdef HAVE_LINUX_CAPABILITIES + (void) warn_if_no_caps; + if (keep_bindlow) { + if (drop_capabilities(1)) + return -1; + } +#else + (void) keep_bindlow; + if (warn_if_no_caps) { + log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support " + "on this system."); + } +#endif + /* Properly switch egid,gid,euid,uid here or bail out */ if (setgroups(1, &pw->pw_gid)) { log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".", @@ -2004,6 +2152,12 @@ switch_id(const char *user) /* We've properly switched egid, gid, euid, uid, and supplementary groups if * we're here. */ +#ifdef HAVE_LINUX_CAPABILITIES + if (keep_bindlow) { + if (drop_capabilities(0)) + return -1; + } +#endif #if !defined(CYGWIN) && !defined(__CYGWIN__) /* If we tried to drop privilege to a group/user other than root, attempt to @@ -2051,9 +2205,9 @@ switch_id(const char *user) #else (void)user; + (void)flags; - log_warn(LD_CONFIG, - "User specified but switching users is unsupported on your OS."); + log_warn(LD_CONFIG, "Switching users is unsupported on your OS."); return -1; #endif } @@ -2537,8 +2691,7 @@ static int uname_result_is_set = 0; /** Return a pointer to a description of our platform. */ -const char * -get_uname(void) +MOCK_IMPL(const char *, get_uname, (void)) { #ifdef HAVE_UNAME struct utsname u; diff --git a/src/common/compat.h b/src/common/compat.h index c7c468c754..8f35dfd110 100644 --- a/src/common/compat.h +++ b/src/common/compat.h @@ -75,9 +75,7 @@ /* inline is __inline on windows. */ #ifdef _WIN32 -#define INLINE __inline -#else -#define INLINE inline +#define inline __inline #endif /* Try to get a reasonable __func__ substitute in place. */ @@ -118,6 +116,7 @@ #define ATTR_CONST __attribute__((const)) #define ATTR_MALLOC __attribute__((malloc)) #define ATTR_NORETURN __attribute__((noreturn)) +#define ATTR_WUR __attribute__((warn_unused_result)) /* Alas, nonnull is not at present a good idea for us. We'd like to get * warnings when we pass NULL where we shouldn't (which nonnull does, albeit * spottily), but we don't want to tell the compiler to make optimizations @@ -153,6 +152,7 @@ #define ATTR_NORETURN #define ATTR_NONNULL(x) #define ATTR_UNUSED +#define ATTR_WUR #define PREDICT_LIKELY(exp) (exp) #define PREDICT_UNLIKELY(exp) (exp) #endif @@ -288,7 +288,7 @@ const void *tor_memmem(const void *haystack, size_t hlen, const void *needle, size_t nlen) ATTR_NONNULL((1,3)); static const void *tor_memstr(const void *haystack, size_t hlen, const char *needle) ATTR_NONNULL((1,3)); -static INLINE const void * +static inline const void * tor_memstr(const void *haystack, size_t hlen, const char *needle) { return tor_memmem(haystack, hlen, needle, strlen(needle)); @@ -299,7 +299,7 @@ tor_memstr(const void *haystack, size_t hlen, const char *needle) #define DECLARE_CTYPE_FN(name) \ static int TOR_##name(char c); \ extern const uint32_t TOR_##name##_TABLE[]; \ - static INLINE int TOR_##name(char c) { \ + static inline int TOR_##name(char c) { \ uint8_t u = c; \ return !!(TOR_##name##_TABLE[(u >> 5) & 7] & (1u << (u & 31))); \ } @@ -601,7 +601,7 @@ typedef enum { } socks5_reply_status_t; /* ===== OS compatibility */ -const char *get_uname(void); +MOCK_DECL(const char *, get_uname, (void)); uint16_t get_uint16(const void *cp) ATTR_NONNULL((1)); uint32_t get_uint32(const void *cp) ATTR_NONNULL((1)); @@ -613,7 +613,7 @@ void set_uint64(void *cp, uint64_t v) ATTR_NONNULL((1)); /* These uint8 variants are defined to make the code more uniform. */ #define get_uint8(cp) (*(const uint8_t*)(cp)) static void set_uint8(void *cp, uint8_t v); -static INLINE void +static inline void set_uint8(void *cp, uint8_t v) { *(uint8_t*)cp = v; @@ -625,7 +625,18 @@ typedef unsigned long rlim_t; int get_max_sockets(void); int set_max_file_descriptors(rlim_t limit, int *max); int tor_disable_debugger_attach(void); -int switch_id(const char *user); + +#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC) +#define HAVE_LINUX_CAPABILITIES +#endif + +int have_capability_support(void); + +/** Flag for switch_id; see switch_id() for documentation */ +#define SWITCH_ID_KEEP_BINDLOW (1<<0) +/** Flag for switch_id; see switch_id() for documentation */ +#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1) +int switch_id(const char *user, unsigned flags); #ifdef HAVE_PWD_H char *get_user_homedir(const char *username); #endif diff --git a/src/common/compat_libevent.c b/src/common/compat_libevent.c index a366b6c9c6..29e5c5f63c 100644 --- a/src/common/compat_libevent.c +++ b/src/common/compat_libevent.c @@ -11,6 +11,7 @@ #include "orconfig.h" #include "compat.h" +#define COMPAT_LIBEVENT_PRIVATE #include "compat_libevent.h" #include "crypto.h" @@ -28,39 +29,11 @@ #include <event.h> #endif -/** A number representing a version of Libevent. - - This is a 4-byte number, with the first three bytes representing the - major, minor, and patchlevel respectively of the library. The fourth - byte is unused. - - This is equivalent to the format of LIBEVENT_VERSION_NUMBER on Libevent - 2.0.1 or later. For versions of Libevent before 1.4.0, which followed the - format of "1.0, 1.0a, 1.0b", we define 1.0 to be equivalent to 1.0.0, 1.0a - to be equivalent to 1.0.1, and so on. -*/ -typedef uint32_t le_version_t; - -/** @{ */ -/** Macros: returns the number of a libevent version as a le_version_t */ -#define V(major, minor, patch) \ - (((major) << 24) | ((minor) << 16) | ((patch) << 8)) -#define V_OLD(major, minor, patch) \ - V((major), (minor), (patch)-'a'+1) -/** @} */ - -/** Represetns a version of libevent so old we can't figure out what version - * it is. */ -#define LE_OLD V(0,0,0) -/** Represents a version of libevent so weird we can't figure out what version - * it is. */ -#define LE_OTHER V(0,0,99) - /** A string which, if it appears in a libevent log, should be ignored. */ static const char *suppress_msg = NULL; /** Callback function passed to event_set_log() so we can intercept * log messages from libevent. */ -static void +STATIC void libevent_logging_callback(int severity, const char *msg) { char buf[1024]; @@ -291,7 +264,7 @@ tor_libevent_get_method(void) /** Return the le_version_t for the version of libevent specified in the * string <b>v</b>. If the version is very new or uses an unrecognized * version, format, return LE_OTHER. */ -static le_version_t +STATIC le_version_t tor_decode_libevent_version(const char *v) { unsigned major, minor, patchlevel; @@ -322,7 +295,7 @@ tor_decode_libevent_version(const char *v) * Two different versions with different numbers are sure not to be binary * compatible. Two different versions with the same numbers have a decent * chance of binary compatibility.*/ -static int +STATIC int le_versions_compatibility(le_version_t v) { if (v == LE_OTHER) diff --git a/src/common/compat_libevent.h b/src/common/compat_libevent.h index 39181efb7b..8ee02c0b6d 100644 --- a/src/common/compat_libevent.h +++ b/src/common/compat_libevent.h @@ -91,5 +91,42 @@ void tor_gettimeofday_cache_set(const struct timeval *tv); #endif void tor_gettimeofday_cached_monotonic(struct timeval *tv); +#ifdef COMPAT_LIBEVENT_PRIVATE +/** A number representing a version of Libevent. + + This is a 4-byte number, with the first three bytes representing the + major, minor, and patchlevel respectively of the library. The fourth + byte is unused. + + This is equivalent to the format of LIBEVENT_VERSION_NUMBER on Libevent + 2.0.1 or later. For versions of Libevent before 1.4.0, which followed the + format of "1.0, 1.0a, 1.0b", we define 1.0 to be equivalent to 1.0.0, 1.0a + to be equivalent to 1.0.1, and so on. +*/ +typedef uint32_t le_version_t; + +/** @{ */ +/** Macros: returns the number of a libevent version as a le_version_t */ +#define V(major, minor, patch) \ + (((major) << 24) | ((minor) << 16) | ((patch) << 8)) +#define V_OLD(major, minor, patch) \ + V((major), (minor), (patch)-'a'+1) +/** @} */ + +/** Represetns a version of libevent so old we can't figure out what version + * it is. */ +#define LE_OLD V(0,0,0) +/** Represents a version of libevent so weird we can't figure out what version + * it is. */ +#define LE_OTHER V(0,0,99) + +STATIC void +libevent_logging_callback(int severity, const char *msg); +STATIC le_version_t +tor_decode_libevent_version(const char *v); +STATIC int +le_versions_compatibility(le_version_t v); +#endif + #endif diff --git a/src/common/compat_openssl.h b/src/common/compat_openssl.h new file mode 100644 index 0000000000..9c98181bdd --- /dev/null +++ b/src/common/compat_openssl.h @@ -0,0 +1,44 @@ +/* Copyright (c) 2001, Matej Pfajfar. + * Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_COMPAT_OPENSSL_H +#define TOR_COMPAT_OPENSSL_H + +#include <openssl/opensslv.h> + +/** + * \file compat_openssl.h + * + * \brief compatability definitions for working with different openssl forks + **/ + +#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,0,0) +#error "We require OpenSSL >= 1.0.0" +#endif + +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) && \ + ! defined(LIBRESSL_VERSION_NUMBER) +/* We define this macro if we're trying to build with the majorly refactored + * API in OpenSSL 1.1 */ +#define OPENSSL_1_1_API +#endif + +#ifndef OPENSSL_1_1_API +#define OPENSSL_VERSION SSLEAY_VERSION +#define OpenSSL_version(v) SSLeay_version(v) +#define OpenSSL_version_num() SSLeay() +#define RAND_OpenSSL() RAND_SSLeay() +#define STATE_IS_SW_SERVER_HELLO(st) \ + (((st) == SSL3_ST_SW_SRVR_HELLO_A) || \ + ((st) == SSL3_ST_SW_SRVR_HELLO_B)) +#define OSSL_HANDSHAKE_STATE int +#else +#define STATE_IS_SW_SERVER_HELLO(st) \ + ((st) == TLS_ST_SW_SRVR_HELLO) +#endif + +#endif + diff --git a/src/common/container.c b/src/common/container.c index 636dfb6c57..9f40dfa2e0 100644 --- a/src/common/container.c +++ b/src/common/container.c @@ -55,11 +55,12 @@ smartlist_free,(smartlist_t *sl)) void smartlist_clear(smartlist_t *sl) { + memset(sl->list, 0, sizeof(void *) * sl->num_used); sl->num_used = 0; } /** Make sure that <b>sl</b> can hold at least <b>size</b> entries. */ -static INLINE void +static inline void smartlist_ensure_capacity(smartlist_t *sl, int size) { #if SIZEOF_SIZE_T > SIZEOF_INT @@ -82,9 +83,11 @@ smartlist_ensure_capacity(smartlist_t *sl, int size) while (size > higher) higher *= 2; } - sl->capacity = higher; sl->list = tor_reallocarray(sl->list, sizeof(void *), - ((size_t)sl->capacity)); + ((size_t)higher)); + memset(sl->list + sl->capacity, 0, + sizeof(void *) * (higher - sl->capacity)); + sl->capacity = higher; } #undef ASSERT_CAPACITY #undef MAX_CAPACITY @@ -123,6 +126,7 @@ smartlist_remove(smartlist_t *sl, const void *element) if (sl->list[i] == element) { sl->list[i] = sl->list[--sl->num_used]; /* swap with the end */ i--; /* so we process the new i'th element */ + sl->list[sl->num_used] = NULL; } } @@ -132,9 +136,11 @@ void * smartlist_pop_last(smartlist_t *sl) { tor_assert(sl); - if (sl->num_used) - return sl->list[--sl->num_used]; - else + if (sl->num_used) { + void *tmp = sl->list[--sl->num_used]; + sl->list[sl->num_used] = NULL; + return tmp; + } else return NULL; } @@ -165,6 +171,7 @@ smartlist_string_remove(smartlist_t *sl, const char *element) tor_free(sl->list[i]); sl->list[i] = sl->list[--sl->num_used]; /* swap with the end */ i--; /* so we process the new i'th element */ + sl->list[sl->num_used] = NULL; } } } @@ -321,6 +328,7 @@ smartlist_intersect(smartlist_t *sl1, const smartlist_t *sl2) if (!smartlist_contains(sl2, sl1->list[i])) { sl1->list[i] = sl1->list[--sl1->num_used]; /* swap with the end */ i--; /* so we process the new i'th element */ + sl1->list[sl1->num_used] = NULL; } } @@ -345,6 +353,7 @@ smartlist_del(smartlist_t *sl, int idx) tor_assert(idx>=0); tor_assert(idx < sl->num_used); sl->list[idx] = sl->list[--sl->num_used]; + sl->list[sl->num_used] = NULL; } /** Remove the <b>idx</b>th element of sl; if idx is not the last element, @@ -360,6 +369,7 @@ smartlist_del_keeporder(smartlist_t *sl, int idx) --sl->num_used; if (idx < sl->num_used) memmove(sl->list+idx, sl->list+idx+1, sizeof(void*)*(sl->num_used-idx)); + sl->list[sl->num_used] = NULL; } /** Insert the value <b>val</b> as the new <b>idx</b>th element of @@ -857,7 +867,7 @@ smartlist_sort_pointers(smartlist_t *sl) /** Helper. <b>sl</b> may have at most one violation of the heap property: * the item at <b>idx</b> may be greater than one or both of its children. * Restore the heap property. */ -static INLINE void +static inline void smartlist_heapify(smartlist_t *sl, int (*compare)(const void *a, const void *b), int idx_field_offset, @@ -937,9 +947,11 @@ smartlist_pqueue_pop(smartlist_t *sl, *IDXP(top)=-1; if (--sl->num_used) { sl->list[0] = sl->list[sl->num_used]; + sl->list[sl->num_used] = NULL; UPDATE_IDX(0); smartlist_heapify(sl, compare, idx_field_offset, 0); } + sl->list[sl->num_used] = NULL; return top; } @@ -959,9 +971,11 @@ smartlist_pqueue_remove(smartlist_t *sl, --sl->num_used; *IDXP(item) = -1; if (idx == sl->num_used) { + sl->list[sl->num_used] = NULL; return; } else { sl->list[idx] = sl->list[sl->num_used]; + sl->list[sl->num_used] = NULL; UPDATE_IDX(idx); smartlist_heapify(sl, compare, idx_field_offset, idx); } @@ -1054,35 +1068,35 @@ DEFINE_MAP_STRUCTS(digestmap_t, char key[DIGEST_LEN], digestmap_); DEFINE_MAP_STRUCTS(digest256map_t, uint8_t key[DIGEST256_LEN], digest256map_); /** Helper: compare strmap_entry_t objects by key value. */ -static INLINE int +static inline int strmap_entries_eq(const strmap_entry_t *a, const strmap_entry_t *b) { return !strcmp(a->key, b->key); } /** Helper: return a hash value for a strmap_entry_t. */ -static INLINE unsigned int +static inline unsigned int strmap_entry_hash(const strmap_entry_t *a) { return (unsigned) siphash24g(a->key, strlen(a->key)); } /** Helper: compare digestmap_entry_t objects by key value. */ -static INLINE int +static inline int digestmap_entries_eq(const digestmap_entry_t *a, const digestmap_entry_t *b) { return tor_memeq(a->key, b->key, DIGEST_LEN); } /** Helper: return a hash value for a digest_map_t. */ -static INLINE unsigned int +static inline unsigned int digestmap_entry_hash(const digestmap_entry_t *a) { return (unsigned) siphash24g(a->key, DIGEST_LEN); } /** Helper: compare digestmap_entry_t objects by key value. */ -static INLINE int +static inline int digest256map_entries_eq(const digest256map_entry_t *a, const digest256map_entry_t *b) { @@ -1090,7 +1104,7 @@ digest256map_entries_eq(const digest256map_entry_t *a, } /** Helper: return a hash value for a digest_map_t. */ -static INLINE unsigned int +static inline unsigned int digest256map_entry_hash(const digest256map_entry_t *a) { return (unsigned) siphash24g(a->key, DIGEST256_LEN); @@ -1113,49 +1127,49 @@ HT_GENERATE2(digest256map_impl, digest256map_entry_t, node, digest256map_entry_hash, digest256map_entries_eq, 0.6, tor_reallocarray_, tor_free_) -static INLINE void +static inline void strmap_entry_free(strmap_entry_t *ent) { tor_free(ent->key); tor_free(ent); } -static INLINE void +static inline void digestmap_entry_free(digestmap_entry_t *ent) { tor_free(ent); } -static INLINE void +static inline void digest256map_entry_free(digest256map_entry_t *ent) { tor_free(ent); } -static INLINE void +static inline void strmap_assign_tmp_key(strmap_entry_t *ent, const char *key) { ent->key = (char*)key; } -static INLINE void +static inline void digestmap_assign_tmp_key(digestmap_entry_t *ent, const char *key) { memcpy(ent->key, key, DIGEST_LEN); } -static INLINE void +static inline void digest256map_assign_tmp_key(digest256map_entry_t *ent, const uint8_t *key) { memcpy(ent->key, key, DIGEST256_LEN); } -static INLINE void +static inline void strmap_assign_key(strmap_entry_t *ent, const char *key) { ent->key = tor_strdup(key); } -static INLINE void +static inline void digestmap_assign_key(digestmap_entry_t *ent, const char *key) { memcpy(ent->key, key, DIGEST_LEN); } -static INLINE void +static inline void digest256map_assign_key(digest256map_entry_t *ent, const uint8_t *key) { memcpy(ent->key, key, DIGEST256_LEN); diff --git a/src/common/container.h b/src/common/container.h index bf4f04762c..af7d5c37ce 100644 --- a/src/common/container.h +++ b/src/common/container.h @@ -53,21 +53,21 @@ void smartlist_subtract(smartlist_t *sl1, const smartlist_t *sl2); #ifdef DEBUG_SMARTLIST /** Return the number of items in sl. */ -static INLINE int smartlist_len(const smartlist_t *sl); -static INLINE int smartlist_len(const smartlist_t *sl) { +static inline int smartlist_len(const smartlist_t *sl); +static inline int smartlist_len(const smartlist_t *sl) { tor_assert(sl); return (sl)->num_used; } /** Return the <b>idx</b>th element of sl. */ -static INLINE void *smartlist_get(const smartlist_t *sl, int idx); -static INLINE void *smartlist_get(const smartlist_t *sl, int idx) { +static inline void *smartlist_get(const smartlist_t *sl, int idx); +static inline void *smartlist_get(const smartlist_t *sl, int idx) { tor_assert(sl); tor_assert(idx>=0); tor_assert(sl->num_used > idx); return sl->list[idx]; } -static INLINE void smartlist_set(smartlist_t *sl, int idx, void *val) { +static inline void smartlist_set(smartlist_t *sl, int idx, void *val) { tor_assert(sl); tor_assert(idx>=0); tor_assert(sl->num_used > idx); @@ -81,7 +81,7 @@ static INLINE void smartlist_set(smartlist_t *sl, int idx, void *val) { /** Exchange the elements at indices <b>idx1</b> and <b>idx2</b> of the * smartlist <b>sl</b>. */ -static INLINE void smartlist_swap(smartlist_t *sl, int idx1, int idx2) +static inline void smartlist_swap(smartlist_t *sl, int idx1, int idx2) { if (idx1 != idx2) { void *elt = smartlist_get(sl, idx1); @@ -500,64 +500,64 @@ void* strmap_remove_lc(strmap_t *map, const char *key); #define DECLARE_TYPED_DIGESTMAP_FNS(prefix, maptype, valtype) \ typedef struct maptype maptype; \ typedef struct prefix##iter_t *prefix##iter_t; \ - ATTR_UNUSED static INLINE maptype* \ + ATTR_UNUSED static inline maptype* \ prefix##new(void) \ { \ return (maptype*)digestmap_new(); \ } \ - ATTR_UNUSED static INLINE digestmap_t* \ + ATTR_UNUSED static inline digestmap_t* \ prefix##to_digestmap(maptype *map) \ { \ return (digestmap_t*)map; \ } \ - ATTR_UNUSED static INLINE valtype* \ + ATTR_UNUSED static inline valtype* \ prefix##get(maptype *map, const char *key) \ { \ return (valtype*)digestmap_get((digestmap_t*)map, key); \ } \ - ATTR_UNUSED static INLINE valtype* \ + ATTR_UNUSED static inline valtype* \ prefix##set(maptype *map, const char *key, valtype *val) \ { \ return (valtype*)digestmap_set((digestmap_t*)map, key, val); \ } \ - ATTR_UNUSED static INLINE valtype* \ + ATTR_UNUSED static inline valtype* \ prefix##remove(maptype *map, const char *key) \ { \ return (valtype*)digestmap_remove((digestmap_t*)map, key); \ } \ - ATTR_UNUSED static INLINE void \ + ATTR_UNUSED static inline void \ prefix##free(maptype *map, void (*free_val)(void*)) \ { \ digestmap_free((digestmap_t*)map, free_val); \ } \ - ATTR_UNUSED static INLINE int \ + ATTR_UNUSED static inline int \ prefix##isempty(maptype *map) \ { \ return digestmap_isempty((digestmap_t*)map); \ } \ - ATTR_UNUSED static INLINE int \ + ATTR_UNUSED static inline int \ prefix##size(maptype *map) \ { \ return digestmap_size((digestmap_t*)map); \ } \ - ATTR_UNUSED static INLINE \ + ATTR_UNUSED static inline \ prefix##iter_t *prefix##iter_init(maptype *map) \ { \ return (prefix##iter_t*) digestmap_iter_init((digestmap_t*)map); \ } \ - ATTR_UNUSED static INLINE \ + ATTR_UNUSED static inline \ prefix##iter_t *prefix##iter_next(maptype *map, prefix##iter_t *iter) \ { \ return (prefix##iter_t*) digestmap_iter_next( \ (digestmap_t*)map, (digestmap_iter_t*)iter); \ } \ - ATTR_UNUSED static INLINE prefix##iter_t* \ + ATTR_UNUSED static inline prefix##iter_t* \ prefix##iter_next_rmv(maptype *map, prefix##iter_t *iter) \ { \ return (prefix##iter_t*) digestmap_iter_next_rmv( \ (digestmap_t*)map, (digestmap_iter_t*)iter); \ } \ - ATTR_UNUSED static INLINE void \ + ATTR_UNUSED static inline void \ prefix##iter_get(prefix##iter_t *iter, \ const char **keyp, \ valtype **valp) \ @@ -566,7 +566,7 @@ void* strmap_remove_lc(strmap_t *map, const char *key); digestmap_iter_get((digestmap_iter_t*) iter, keyp, &v); \ *valp = v; \ } \ - ATTR_UNUSED static INLINE int \ + ATTR_UNUSED static inline int \ prefix##iter_done(prefix##iter_t *iter) \ { \ return digestmap_iter_done((digestmap_iter_t*)iter); \ @@ -584,7 +584,7 @@ void* strmap_remove_lc(strmap_t *map, const char *key); /** A random-access array of one-bit-wide elements. */ typedef unsigned int bitarray_t; /** Create a new bit array that can hold <b>n_bits</b> bits. */ -static INLINE bitarray_t * +static inline bitarray_t * bitarray_init_zero(unsigned int n_bits) { /* round up to the next int. */ @@ -594,7 +594,7 @@ bitarray_init_zero(unsigned int n_bits) /** Expand <b>ba</b> from holding <b>n_bits_old</b> to <b>n_bits_new</b>, * clearing all new bits. Returns a possibly changed pointer to the * bitarray. */ -static INLINE bitarray_t * +static inline bitarray_t * bitarray_expand(bitarray_t *ba, unsigned int n_bits_old, unsigned int n_bits_new) { @@ -611,26 +611,26 @@ bitarray_expand(bitarray_t *ba, return (bitarray_t*) ptr; } /** Free the bit array <b>ba</b>. */ -static INLINE void +static inline void bitarray_free(bitarray_t *ba) { tor_free(ba); } /** Set the <b>bit</b>th bit in <b>b</b> to 1. */ -static INLINE void +static inline void bitarray_set(bitarray_t *b, int bit) { b[bit >> BITARRAY_SHIFT] |= (1u << (bit & BITARRAY_MASK)); } /** Set the <b>bit</b>th bit in <b>b</b> to 0. */ -static INLINE void +static inline void bitarray_clear(bitarray_t *b, int bit) { b[bit >> BITARRAY_SHIFT] &= ~ (1u << (bit & BITARRAY_MASK)); } /** Return true iff <b>bit</b>th bit in <b>b</b> is nonzero. NOTE: does * not necessarily return 1 on true. */ -static INLINE unsigned int +static inline unsigned int bitarray_is_set(bitarray_t *b, int bit) { return b[bit >> BITARRAY_SHIFT] & (1u << (bit & BITARRAY_MASK)); @@ -645,7 +645,7 @@ typedef struct { #define BIT(n) ((n) & set->mask) /** Add the digest <b>digest</b> to <b>set</b>. */ -static INLINE void +static inline void digestset_add(digestset_t *set, const char *digest) { const uint64_t x = siphash24g(digest, 20); @@ -661,7 +661,7 @@ digestset_add(digestset_t *set, const char *digest) /** If <b>digest</b> is in <b>set</b>, return nonzero. Otherwise, * <em>probably</em> return zero. */ -static INLINE int +static inline int digestset_contains(const digestset_t *set, const char *digest) { const uint64_t x = siphash24g(digest, 20); @@ -689,33 +689,33 @@ double find_nth_double(double *array, int n_elements, int nth); int32_t find_nth_int32(int32_t *array, int n_elements, int nth); uint32_t find_nth_uint32(uint32_t *array, int n_elements, int nth); long find_nth_long(long *array, int n_elements, int nth); -static INLINE int +static inline int median_int(int *array, int n_elements) { return find_nth_int(array, n_elements, (n_elements-1)/2); } -static INLINE time_t +static inline time_t median_time(time_t *array, int n_elements) { return find_nth_time(array, n_elements, (n_elements-1)/2); } -static INLINE double +static inline double median_double(double *array, int n_elements) { return find_nth_double(array, n_elements, (n_elements-1)/2); } -static INLINE uint32_t +static inline uint32_t median_uint32(uint32_t *array, int n_elements) { return find_nth_uint32(array, n_elements, (n_elements-1)/2); } -static INLINE int32_t +static inline int32_t median_int32(int32_t *array, int n_elements) { return find_nth_int32(array, n_elements, (n_elements-1)/2); } -static INLINE uint32_t +static inline uint32_t third_quartile_uint32(uint32_t *array, int n_elements) { return find_nth_uint32(array, n_elements, (n_elements*3)/4); diff --git a/src/common/crypto.c b/src/common/crypto.c index c50a303c1d..de4f9db94f 100644 --- a/src/common/crypto.c +++ b/src/common/crypto.c @@ -21,18 +21,13 @@ #undef OCSP_RESPONSE #endif -#include <openssl/opensslv.h> - #define CRYPTO_PRIVATE #include "crypto.h" +#include "compat_openssl.h" #include "crypto_curve25519.h" #include "crypto_ed25519.h" #include "crypto_format.h" -#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,0,0) -#error "We require OpenSSL >= 1.0.0" -#endif - #include <openssl/err.h> #include <openssl/rsa.h> #include <openssl/pem.h> @@ -48,6 +43,7 @@ #include <ctype.h> #endif #ifdef HAVE_UNISTD_H +#define _GNU_SOURCE #include <unistd.h> #endif #ifdef HAVE_FCNTL_H @@ -56,6 +52,9 @@ #ifdef HAVE_SYS_FCNTL_H #include <sys/fcntl.h> #endif +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif #include "torlog.h" #include "aes.h" @@ -65,6 +64,8 @@ #include "sandbox.h" #include "util_format.h" +#include "keccak-tiny/keccak-tiny.h" + #ifdef ANDROID /* Android's OpenSSL seems to have removed all of its Engine support. */ #define DISABLE_ENGINES @@ -73,6 +74,9 @@ /** Longest recognized */ #define MAX_DNS_LABEL_SIZE 63 +/** Largest strong entropy request */ +#define MAX_STRONGEST_RAND_SIZE 256 + /** Macro: is k a valid RSA public or private key? */ #define PUBLIC_KEY_OK(k) ((k) && (k)->key && (k)->key->n) /** Macro: is k a valid RSA private key? */ @@ -110,7 +114,7 @@ static int tor_check_dh_key(int severity, BIGNUM *bn); /** Return the number of bytes added by padding method <b>padding</b>. */ -static INLINE int +static inline int crypto_get_rsa_padding_overhead(int padding) { switch (padding) @@ -122,7 +126,7 @@ crypto_get_rsa_padding_overhead(int padding) /** Given a padding method <b>padding</b>, return the correct OpenSSL constant. */ -static INLINE int +static inline int crypto_get_rsa_padding(int padding) { switch (padding) @@ -227,7 +231,7 @@ const char * crypto_openssl_get_version_str(void) { if (crypto_openssl_version_str == NULL) { - const char *raw_version = SSLeay_version(SSLEAY_VERSION); + const char *raw_version = OpenSSL_version(OPENSSL_VERSION); crypto_openssl_version_str = parse_openssl_version_str(raw_version); } return crypto_openssl_version_str; @@ -248,14 +252,16 @@ crypto_openssl_get_header_version_str(void) /** Make sure that openssl is using its default PRNG. Return 1 if we had to * adjust it; 0 otherwise. */ -static int +STATIC int crypto_force_rand_ssleay(void) { - if (RAND_get_rand_method() != RAND_SSLeay()) { + RAND_METHOD *default_method; + default_method = RAND_OpenSSL(); + if (RAND_get_rand_method() != default_method) { log_notice(LD_CRYPTO, "It appears that one of our engines has provided " "a replacement the OpenSSL RNG. Resetting it to the default " "implementation."); - RAND_set_rand_method(RAND_SSLeay()); + RAND_set_rand_method(default_method); return 1; } return 0; @@ -270,8 +276,7 @@ crypto_init_siphash_key(void) if (have_seeded_siphash) return 0; - if (crypto_rand((char*) &key, sizeof(key)) < 0) - return -1; + crypto_rand((char*) &key, sizeof(key)); siphash_set_global_key(&key); have_seeded_siphash = 1; return 0; @@ -291,16 +296,18 @@ crypto_early_init(void) setup_openssl_threading(); - if (SSLeay() == OPENSSL_VERSION_NUMBER && - !strcmp(SSLeay_version(SSLEAY_VERSION), OPENSSL_VERSION_TEXT)) { + unsigned long version_num = OpenSSL_version_num(); + const char *version_str = OpenSSL_version(OPENSSL_VERSION); + if (version_num == OPENSSL_VERSION_NUMBER && + !strcmp(version_str, OPENSSL_VERSION_TEXT)) { log_info(LD_CRYPTO, "OpenSSL version matches version from headers " - "(%lx: %s).", SSLeay(), SSLeay_version(SSLEAY_VERSION)); + "(%lx: %s).", version_num, version_str); } else { log_warn(LD_CRYPTO, "OpenSSL version from headers does not match the " "version we're running with. If you get weird crashes, that " "might be why. (Compiled with %lx: %s; running with %lx: %s).", (unsigned long)OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_TEXT, - SSLeay(), SSLeay_version(SSLEAY_VERSION)); + version_num, version_str); } crypto_force_rand_ssleay(); @@ -322,7 +329,8 @@ int crypto_global_init(int useAccel, const char *accelName, const char *accelDir) { if (!crypto_global_initialized_) { - crypto_early_init(); + if (crypto_early_init() < 0) + return -1; crypto_global_initialized_ = 1; @@ -404,11 +412,7 @@ crypto_global_init(int useAccel, const char *accelName, const char *accelDir) void crypto_thread_cleanup(void) { -#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) ERR_remove_thread_state(NULL); -#else - ERR_remove_state(0); -#endif } /** used by tortls.c: wrap an RSA* in a crypto_pk_t. */ @@ -432,9 +436,10 @@ crypto_pk_get_rsa_(crypto_pk_t *env) } /** used by tortls.c: get an equivalent EVP_PKEY* for a crypto_pk_t. Iff - * private is set, include the private-key portion of the key. */ -EVP_PKEY * -crypto_pk_get_evp_pkey_(crypto_pk_t *env, int private) + * private is set, include the private-key portion of the key. Return a valid + * pointer on success, and NULL on failure. */ +MOCK_IMPL(EVP_PKEY *, + crypto_pk_get_evp_pkey_,(crypto_pk_t *env, int private)) { RSA *key = NULL; EVP_PKEY *pkey = NULL; @@ -470,8 +475,8 @@ crypto_dh_get_dh_(crypto_dh_t *dh) /** Allocate and return storage for a public key. The key itself will not yet * be set. */ -crypto_pk_t * -crypto_pk_new(void) +MOCK_IMPL(crypto_pk_t *, + crypto_pk_new,(void)) { RSA *rsa; @@ -553,8 +558,8 @@ crypto_cipher_free(crypto_cipher_t *env) /** Generate a <b>bits</b>-bit new public/private keypair in <b>env</b>. * Return 0 on success, -1 on failure. */ -int -crypto_pk_generate_key_with_bits(crypto_pk_t *env, int bits) +MOCK_IMPL(int, + crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)) { tor_assert(env); @@ -656,7 +661,8 @@ crypto_pk_read_private_key_from_filename(crypto_pk_t *env, return 0; } -/** Helper function to implement crypto_pk_write_*_key_to_string. */ +/** Helper function to implement crypto_pk_write_*_key_to_string. Return 0 on + * success, -1 on failure. */ static int crypto_pk_write_key_to_string_impl(crypto_pk_t *env, char **dest, size_t *len, int is_public) @@ -897,7 +903,8 @@ crypto_pk_dup_key(crypto_pk_t *env) return env; } -/** Make a real honest-to-goodness copy of <b>env</b>, and return it. */ +/** Make a real honest-to-goodness copy of <b>env</b>, and return it. + * Returns NULL on failure. */ crypto_pk_t * crypto_pk_copy_full(crypto_pk_t *env) { @@ -1189,7 +1196,8 @@ crypto_pk_public_hybrid_encrypt(crypto_pk_t *env, return -1; } -/** Invert crypto_pk_public_hybrid_encrypt. */ +/** Invert crypto_pk_public_hybrid_encrypt. Returns the number of bytes + * written on success, -1 on failure. */ int crypto_pk_private_hybrid_decrypt(crypto_pk_t *env, char *to, @@ -1332,7 +1340,7 @@ crypto_pk_get_all_digests(crypto_pk_t *pk, digests_t *digests_out) } /** Copy <b>in</b> to the <b>outlen</b>-byte buffer <b>out</b>, adding spaces - * every four spaces. */ + * every four characters. */ void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in) { @@ -1484,7 +1492,7 @@ crypto_cipher_get_key(crypto_cipher_t *env) /** Encrypt <b>fromlen</b> bytes from <b>from</b> using the cipher * <b>env</b>; on success, store the result to <b>to</b> and return 0. - * On failure, return -1. + * Does not check for failure. */ int crypto_cipher_encrypt(crypto_cipher_t *env, char *to, @@ -1503,7 +1511,7 @@ crypto_cipher_encrypt(crypto_cipher_t *env, char *to, /** Decrypt <b>fromlen</b> bytes from <b>from</b> using the cipher * <b>env</b>; on success, store the result to <b>to</b> and return 0. - * On failure, return -1. + * Does not check for failure. */ int crypto_cipher_decrypt(crypto_cipher_t *env, char *to, @@ -1519,7 +1527,7 @@ crypto_cipher_decrypt(crypto_cipher_t *env, char *to, } /** Encrypt <b>len</b> bytes on <b>from</b> using the cipher in <b>env</b>; - * on success, return 0. On failure, return -1. + * on success, return 0. Does not check for failure. */ int crypto_cipher_crypt_inplace(crypto_cipher_t *env, char *buf, size_t len) @@ -1591,7 +1599,7 @@ crypto_cipher_decrypt_with_iv(const char *key, /** Compute the SHA1 digest of the <b>len</b> bytes on data stored in * <b>m</b>. Write the DIGEST_LEN byte result into <b>digest</b>. - * Return 0 on success, -1 on failure. + * Return 0 on success, 1 on failure. */ int crypto_digest(char *digest, const char *m, size_t len) @@ -1603,15 +1611,37 @@ crypto_digest(char *digest, const char *m, size_t len) /** Compute a 256-bit digest of <b>len</b> bytes in data stored in <b>m</b>, * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN256-byte result - * into <b>digest</b>. Return 0 on success, -1 on failure. */ + * into <b>digest</b>. Return 0 on success, 1 on failure. */ int crypto_digest256(char *digest, const char *m, size_t len, digest_algorithm_t algorithm) { tor_assert(m); tor_assert(digest); - tor_assert(algorithm == DIGEST_SHA256); - return (SHA256((const unsigned char*)m,len,(unsigned char*)digest) == NULL); + tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); + if (algorithm == DIGEST_SHA256) + return (SHA256((const uint8_t*)m,len,(uint8_t*)digest) == NULL); + else + return (sha3_256((uint8_t *)digest, DIGEST256_LEN,(const uint8_t *)m, len) + == -1); +} + +/** Compute a 512-bit digest of <b>len</b> bytes in data stored in <b>m</b>, + * using the algorithm <b>algorithm</b>. Write the DIGEST_LEN512-byte result + * into <b>digest</b>. Return 0 on success, 1 on failure. */ +int +crypto_digest512(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm) +{ + tor_assert(m); + tor_assert(digest); + tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); + if (algorithm == DIGEST_SHA512) + return (SHA512((const unsigned char*)m,len,(unsigned char*)digest) + == NULL); + else + return (sha3_512((uint8_t*)digest, DIGEST512_LEN, (const uint8_t*)m, len) + == -1); } /** Set the digests_t in <b>ds_out</b> to contain every digest on the @@ -1626,8 +1656,20 @@ crypto_digest_all(digests_t *ds_out, const char *m, size_t len) if (crypto_digest(ds_out->d[DIGEST_SHA1], m, len) < 0) return -1; for (i = DIGEST_SHA256; i < N_DIGEST_ALGORITHMS; ++i) { - if (crypto_digest256(ds_out->d[i], m, len, i) < 0) - return -1; + switch (i) { + case DIGEST_SHA256: /* FALLSTHROUGH */ + case DIGEST_SHA3_256: + if (crypto_digest256(ds_out->d[i], m, len, i) < 0) + return -1; + break; + case DIGEST_SHA512: + case DIGEST_SHA3_512: /* FALLSTHROUGH */ + if (crypto_digest512(ds_out->d[i], m, len, i) < 0) + return -1; + break; + default: + return -1; + } } return 0; } @@ -1641,6 +1683,12 @@ crypto_digest_algorithm_get_name(digest_algorithm_t alg) return "sha1"; case DIGEST_SHA256: return "sha256"; + case DIGEST_SHA512: + return "sha512"; + case DIGEST_SHA3_256: + return "sha3-256"; + case DIGEST_SHA3_512: + return "sha3-512"; default: tor_fragile_assert(); return "??unknown_digest??"; @@ -1656,27 +1704,90 @@ crypto_digest_algorithm_parse_name(const char *name) return DIGEST_SHA1; else if (!strcmp(name, "sha256")) return DIGEST_SHA256; + else if (!strcmp(name, "sha512")) + return DIGEST_SHA512; + else if (!strcmp(name, "sha3-256")) + return DIGEST_SHA3_256; + else if (!strcmp(name, "sha3-512")) + return DIGEST_SHA3_512; else return -1; } +/** Given an algorithm, return the digest length in bytes. */ +static inline size_t +crypto_digest_algorithm_get_length(digest_algorithm_t alg) +{ + switch (alg) { + case DIGEST_SHA1: + return DIGEST_LEN; + case DIGEST_SHA256: + return DIGEST256_LEN; + case DIGEST_SHA512: + return DIGEST512_LEN; + case DIGEST_SHA3_256: + return DIGEST256_LEN; + case DIGEST_SHA3_512: + return DIGEST512_LEN; + default: + tor_assert(0); + return 0; /* Unreachable */ + } +} + /** Intermediate information about the digest of a stream of data. */ struct crypto_digest_t { + digest_algorithm_t algorithm; /**< Which algorithm is in use? */ + /** State for the digest we're using. Only one member of the + * union is usable, depending on the value of <b>algorithm</b>. Note also + * that space for other members might not even be allocated! + */ union { SHA_CTX sha1; /**< state for SHA1 */ SHA256_CTX sha2; /**< state for SHA256 */ - } d; /**< State for the digest we're using. Only one member of the - * union is usable, depending on the value of <b>algorithm</b>. */ - digest_algorithm_bitfield_t algorithm : 8; /**< Which algorithm is in use? */ + SHA512_CTX sha512; /**< state for SHA512 */ + keccak_state sha3; /**< state for SHA3-[256,512] */ + } d; }; +/** + * Return the number of bytes we need to malloc in order to get a + * crypto_digest_t for <b>alg</b>, or the number of bytes we need to wipe + * when we free one. + */ +static size_t +crypto_digest_alloc_bytes(digest_algorithm_t alg) +{ + /* Helper: returns the number of bytes in the 'f' field of 'st' */ +#define STRUCT_FIELD_SIZE(st, f) (sizeof( ((st*)0)->f )) + /* Gives the length of crypto_digest_t through the end of the field 'd' */ +#define END_OF_FIELD(f) (STRUCT_OFFSET(crypto_digest_t, f) + \ + STRUCT_FIELD_SIZE(crypto_digest_t, f)) + switch (alg) { + case DIGEST_SHA1: + return END_OF_FIELD(d.sha1); + case DIGEST_SHA256: + return END_OF_FIELD(d.sha2); + case DIGEST_SHA512: + return END_OF_FIELD(d.sha512); + case DIGEST_SHA3_256: + case DIGEST_SHA3_512: + return END_OF_FIELD(d.sha3); + default: + tor_assert(0); + return 0; + } +#undef END_OF_FIELD +#undef STRUCT_FIELD_SIZE +} + /** Allocate and return a new digest object to compute SHA1 digests. */ crypto_digest_t * crypto_digest_new(void) { crypto_digest_t *r; - r = tor_malloc(sizeof(crypto_digest_t)); + r = tor_malloc(crypto_digest_alloc_bytes(DIGEST_SHA1)); SHA1_Init(&r->d.sha1); r->algorithm = DIGEST_SHA1; return r; @@ -1688,9 +1799,28 @@ crypto_digest_t * crypto_digest256_new(digest_algorithm_t algorithm) { crypto_digest_t *r; - tor_assert(algorithm == DIGEST_SHA256); - r = tor_malloc(sizeof(crypto_digest_t)); - SHA256_Init(&r->d.sha2); + tor_assert(algorithm == DIGEST_SHA256 || algorithm == DIGEST_SHA3_256); + r = tor_malloc(crypto_digest_alloc_bytes(algorithm)); + if (algorithm == DIGEST_SHA256) + SHA256_Init(&r->d.sha2); + else + keccak_digest_init(&r->d.sha3, 256); + r->algorithm = algorithm; + return r; +} + +/** Allocate and return a new digest object to compute 512-bit digests + * using <b>algorithm</b>. */ +crypto_digest_t * +crypto_digest512_new(digest_algorithm_t algorithm) +{ + crypto_digest_t *r; + tor_assert(algorithm == DIGEST_SHA512 || algorithm == DIGEST_SHA3_512); + r = tor_malloc(crypto_digest_alloc_bytes(algorithm)); + if (algorithm == DIGEST_SHA512) + SHA512_Init(&r->d.sha512); + else + keccak_digest_init(&r->d.sha3, 512); r->algorithm = algorithm; return r; } @@ -1702,7 +1832,8 @@ crypto_digest_free(crypto_digest_t *digest) { if (!digest) return; - memwipe(digest, 0, sizeof(crypto_digest_t)); + size_t bytes = crypto_digest_alloc_bytes(digest->algorithm); + memwipe(digest, 0, bytes); tor_free(digest); } @@ -1726,6 +1857,13 @@ crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, case DIGEST_SHA256: SHA256_Update(&digest->d.sha2, (void*)data, len); break; + case DIGEST_SHA512: + SHA512_Update(&digest->d.sha512, (void*)data, len); + break; + case DIGEST_SHA3_256: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: + keccak_digest_update(&digest->d.sha3, (const uint8_t *)data, len); + break; default: tor_fragile_assert(); break; @@ -1734,33 +1872,45 @@ crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, /** Compute the hash of the data that has been passed to the digest * object; write the first out_len bytes of the result to <b>out</b>. - * <b>out_len</b> must be \<= DIGEST256_LEN. + * <b>out_len</b> must be \<= DIGEST512_LEN. */ void crypto_digest_get_digest(crypto_digest_t *digest, char *out, size_t out_len) { - unsigned char r[DIGEST256_LEN]; + unsigned char r[DIGEST512_LEN]; crypto_digest_t tmpenv; tor_assert(digest); tor_assert(out); + tor_assert(out_len <= crypto_digest_algorithm_get_length(digest->algorithm)); + + /* The SHA-3 code handles copying into a temporary ctx, and also can handle + * short output buffers by truncating appropriately. */ + if (digest->algorithm == DIGEST_SHA3_256 || + digest->algorithm == DIGEST_SHA3_512) { + keccak_digest_sum(&digest->d.sha3, (uint8_t *)out, out_len); + return; + } + + const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); /* memcpy into a temporary ctx, since SHA*_Final clears the context */ - memcpy(&tmpenv, digest, sizeof(crypto_digest_t)); + memcpy(&tmpenv, digest, alloc_bytes); switch (digest->algorithm) { case DIGEST_SHA1: - tor_assert(out_len <= DIGEST_LEN); SHA1_Final(r, &tmpenv.d.sha1); break; case DIGEST_SHA256: - tor_assert(out_len <= DIGEST256_LEN); SHA256_Final(r, &tmpenv.d.sha2); break; + case DIGEST_SHA512: + SHA512_Final(r, &tmpenv.d.sha512); + break; + case DIGEST_SHA3_256: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: + log_warn(LD_BUG, "Handling unexpected algorithm %d", digest->algorithm); + tor_assert(0); /* This is fatal, because it should never happen. */ default: - log_warn(LD_BUG, "Called with unknown algorithm %d", digest->algorithm); - /* If fragile_assert is not enabled, then we should at least not - * leak anything. */ - memwipe(r, 0xff, sizeof(r)); - tor_fragile_assert(); + tor_assert(0); /* Unreachable. */ break; } memcpy(out, r, out_len); @@ -1773,15 +1923,14 @@ crypto_digest_get_digest(crypto_digest_t *digest, crypto_digest_t * crypto_digest_dup(const crypto_digest_t *digest) { - crypto_digest_t *r; tor_assert(digest); - r = tor_malloc(sizeof(crypto_digest_t)); - memcpy(r,digest,sizeof(crypto_digest_t)); - return r; + const size_t alloc_bytes = crypto_digest_alloc_bytes(digest->algorithm); + return tor_memdup(digest, alloc_bytes); } /** Replace the state of the digest object <b>into</b> with the state - * of the digest object <b>from</b>. + * of the digest object <b>from</b>. Requires that 'into' and 'from' + * have the same digest type. */ void crypto_digest_assign(crypto_digest_t *into, @@ -1789,14 +1938,16 @@ crypto_digest_assign(crypto_digest_t *into, { tor_assert(into); tor_assert(from); - memcpy(into,from,sizeof(crypto_digest_t)); + tor_assert(into->algorithm == from->algorithm); + const size_t alloc_bytes = crypto_digest_alloc_bytes(from->algorithm); + memcpy(into,from,alloc_bytes); } /** Given a list of strings in <b>lst</b>, set the <b>len_out</b>-byte digest * at <b>digest_out</b> to the hash of the concatenation of those strings, * plus the optional string <b>append</b>, computed with the algorithm * <b>alg</b>. - * <b>out_len</b> must be \<= DIGEST256_LEN. */ + * <b>out_len</b> must be \<= DIGEST512_LEN. */ void crypto_digest_smartlist(char *digest_out, size_t len_out, const smartlist_t *lst, @@ -1811,7 +1962,7 @@ crypto_digest_smartlist(char *digest_out, size_t len_out, * optional string <b>prepend</b>, those strings, * and the optional string <b>append</b>, computed with the algorithm * <b>alg</b>. - * <b>out_len</b> must be \<= DIGEST256_LEN. */ + * <b>len_out</b> must be \<= DIGEST512_LEN. */ void crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, const char *prepend, @@ -1819,11 +1970,27 @@ crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, const char *append, digest_algorithm_t alg) { - crypto_digest_t *d; - if (alg == DIGEST_SHA1) - d = crypto_digest_new(); - else - d = crypto_digest256_new(alg); + crypto_digest_t *d = NULL; + switch (alg) { + case DIGEST_SHA1: + d = crypto_digest_new(); + break; + case DIGEST_SHA256: /* FALLSTHROUGH */ + case DIGEST_SHA3_256: + d = crypto_digest256_new(alg); + break; + case DIGEST_SHA512: /* FALLSTHROUGH */ + case DIGEST_SHA3_512: + d = crypto_digest512_new(alg); + break; + default: + log_warn(LD_BUG, "Called with unknown algorithm %d", alg); + /* If fragile_assert is not enabled, wipe output and return + * without running any calculations */ + memwipe(digest_out, 0xff, len_out); + tor_fragile_assert(); + goto free; + } if (prepend) crypto_digest_add_bytes(d, prepend, strlen(prepend)); SMARTLIST_FOREACH(lst, const char *, cp, @@ -1831,23 +1998,78 @@ crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, if (append) crypto_digest_add_bytes(d, append, strlen(append)); crypto_digest_get_digest(d, digest_out, len_out); + + free: crypto_digest_free(d); } /** Compute the HMAC-SHA-256 of the <b>msg_len</b> bytes in <b>msg</b>, using * the <b>key</b> of length <b>key_len</b>. Store the DIGEST256_LEN-byte - * result in <b>hmac_out</b>. + * result in <b>hmac_out</b>. Asserts on failure. */ void crypto_hmac_sha256(char *hmac_out, const char *key, size_t key_len, const char *msg, size_t msg_len) { + unsigned char *rv = NULL; /* If we've got OpenSSL >=0.9.8 we can use its hmac implementation. */ tor_assert(key_len < INT_MAX); tor_assert(msg_len < INT_MAX); - HMAC(EVP_sha256(), key, (int)key_len, (unsigned char*)msg, (int)msg_len, - (unsigned char*)hmac_out, NULL); + tor_assert(hmac_out); + rv = HMAC(EVP_sha256(), key, (int)key_len, (unsigned char*)msg, (int)msg_len, + (unsigned char*)hmac_out, NULL); + tor_assert(rv); +} + +/** Internal state for a eXtendable-Output Function (XOF). */ +struct crypto_xof_t { + keccak_state s; +}; + +/** Allocate a new XOF object backed by SHAKE-256. The security level + * provided is a function of the length of the output used. Read and + * understand FIPS-202 A.2 "Additional Consideration for Extendable-Output + * Functions" before using this construct. + */ +crypto_xof_t * +crypto_xof_new(void) +{ + crypto_xof_t *xof; + xof = tor_malloc(sizeof(crypto_xof_t)); + keccak_xof_init(&xof->s, 256); + return xof; +} + +/** Absorb bytes into a XOF object. Must not be called after a call to + * crypto_xof_squeeze_bytes() for the same instance, and will assert + * if attempted. + */ +void +crypto_xof_add_bytes(crypto_xof_t *xof, const uint8_t *data, size_t len) +{ + int i = keccak_xof_absorb(&xof->s, data, len); + tor_assert(i == 0); +} + +/** Squeeze bytes out of a XOF object. Calling this routine will render + * the XOF instance ineligible to absorb further data. + */ +void +crypto_xof_squeeze_bytes(crypto_xof_t *xof, uint8_t *out, size_t len) +{ + int i = keccak_xof_squeeze(&xof->s, out, len); + tor_assert(i == 0); +} + +/** Cleanse and deallocate a XOF object. */ +void +crypto_xof_free(crypto_xof_t *xof) +{ + if (!xof) + return; + memwipe(xof, 0, sizeof(crypto_xof_t)); + tor_free(xof); } /* DH */ @@ -1941,7 +2163,8 @@ init_dh_param(void) */ #define DH_PRIVATE_KEY_BITS 320 -/** Allocate and return a new DH object for a key exchange. +/** Allocate and return a new DH object for a key exchange. Returns NULL on + * failure. */ crypto_dh_t * crypto_dh_new(int dh_type) @@ -2164,7 +2387,7 @@ int crypto_expand_key_material_TAP(const uint8_t *key_in, size_t key_in_len, uint8_t *key_out, size_t key_out_len) { - int i; + int i, r = -1; uint8_t *cp, *tmp = tor_malloc(key_in_len+1); uint8_t digest[DIGEST_LEN]; @@ -2176,19 +2399,16 @@ crypto_expand_key_material_TAP(const uint8_t *key_in, size_t key_in_len, ++i, cp += DIGEST_LEN) { tmp[key_in_len] = i; if (crypto_digest((char*)digest, (const char *)tmp, key_in_len+1)) - goto err; + goto exit; memcpy(cp, digest, MIN(DIGEST_LEN, key_out_len-(cp-key_out))); } - memwipe(tmp, 0, key_in_len+1); - tor_free(tmp); - memwipe(digest, 0, sizeof(digest)); - return 0; - err: + r = 0; + exit: memwipe(tmp, 0, key_in_len+1); tor_free(tmp); memwipe(digest, 0, sizeof(digest)); - return -1; + return r; } /** Expand some secret key material according to RFC5869, using SHA256 as the @@ -2196,7 +2416,7 @@ crypto_expand_key_material_TAP(const uint8_t *key_in, size_t key_in_len, * secret key material; the <b>salt_in_len</b> bytes at <b>salt_in</b> and the * <b>info_in_len</b> bytes in <b>info_in_len</b> are the algorithm's "salt" * and "info" parameters respectively. On success, write <b>key_out_len</b> - * bytes to <b>key_out</b> and return 0. On failure, return -1. + * bytes to <b>key_out</b> and return 0. Assert on failure. */ int crypto_expand_key_material_rfc5869_sha256( @@ -2280,23 +2500,18 @@ crypto_seed_weak_rng(tor_weak_rng_t *rng) } /** Try to get <b>out_len</b> bytes of the strongest entropy we can generate, - * storing it into <b>out</b>. + * via system calls, storing it into <b>out</b>. Return 0 on success, -1 on + * failure. A maximum request size of 256 bytes is imposed. */ -int -crypto_strongest_rand(uint8_t *out, size_t out_len) +static int +crypto_strongest_rand_syscall(uint8_t *out, size_t out_len) { -#ifdef _WIN32 + tor_assert(out_len <= MAX_STRONGEST_RAND_SIZE); + +#if defined(_WIN32) static int provider_set = 0; static HCRYPTPROV provider; -#else - static const char *filenames[] = { - "/dev/srandom", "/dev/urandom", "/dev/random", NULL - }; - int fd, i; - size_t n; -#endif -#ifdef _WIN32 if (!provider_set) { if (!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { @@ -2311,7 +2526,84 @@ crypto_strongest_rand(uint8_t *out, size_t out_len) } return 0; +#elif defined(__linux__) && defined(SYS_getrandom) + static int getrandom_works = 1; /* Be optimitic about our chances... */ + + /* getrandom() isn't as straight foward as getentropy(), and has + * no glibc wrapper. + * + * As far as I can tell from getrandom(2) and the source code, the + * requests we issue will always succeed (though it will block on the + * call if /dev/urandom isn't seeded yet), since we are NOT specifying + * GRND_NONBLOCK and the request is <= 256 bytes. + * + * The manpage is unclear on what happens if a signal interrupts the call + * while the request is blocked due to lack of entropy.... + * + * We optimistically assume that getrandom() is available and functional + * because it is the way of the future, and 2 branch mispredicts pale in + * comparision to the overheads involved with failing to open + * /dev/srandom followed by opening and reading from /dev/urandom. + */ + if (PREDICT_LIKELY(getrandom_works)) { + long ret; + /* A flag of '0' here means to read from '/dev/urandom', and to + * block if insufficient entropy is available to service the + * request. + */ + const unsigned int flags = 0; + do { + ret = syscall(SYS_getrandom, out, out_len, flags); + } while (ret == -1 && ((errno == EINTR) ||(errno == EAGAIN))); + + if (PREDICT_UNLIKELY(ret == -1)) { + tor_assert(errno != EAGAIN); + tor_assert(errno != EINTR); + + /* Probably ENOSYS. */ + log_warn(LD_CRYPTO, "Can't get entropy from getrandom()."); + getrandom_works = 0; /* Don't bother trying again. */ + return -1; + } + + tor_assert(ret == (long)out_len); + return 0; + } + + return -1; /* getrandom() previously failed unexpectedly. */ +#elif defined(HAVE_GETENTROPY) + /* getentropy() is what Linux's getrandom() wants to be when it grows up. + * the only gotcha is that requests are limited to 256 bytes. + */ + return getentropy(out, out_len); #else + (void) out; +#endif + + /* This platform doesn't have a supported syscall based random. */ + return -1; +} + +/** Try to get <b>out_len</b> bytes of the strongest entropy we can generate, + * via the per-platform fallback mechanism, storing it into <b>out</b>. + * Return 0 on success, -1 on failure. A maximum request size of 256 bytes + * is imposed. + */ +static int +crypto_strongest_rand_fallback(uint8_t *out, size_t out_len) +{ +#ifdef _WIN32 + /* Windows exclusively uses crypto_strongest_rand_syscall(). */ + (void)out; + (void)out_len; + return -1; +#else + static const char *filenames[] = { + "/dev/srandom", "/dev/urandom", "/dev/random", NULL + }; + int fd, i; + size_t n; + for (i = 0; filenames[i]; ++i) { log_debug(LD_FS, "Opening %s for entropy", filenames[i]); fd = open(sandbox_intern_string(filenames[i]), O_RDONLY, 0); @@ -2329,14 +2621,95 @@ crypto_strongest_rand(uint8_t *out, size_t out_len) return 0; } - log_warn(LD_CRYPTO, "Cannot get strong entropy: no entropy source found."); return -1; #endif } +/** Try to get <b>out_len</b> bytes of the strongest entropy we can generate, + * storing it into <b>out</b>. Return 0 on success, -1 on failure. A maximum + * request size of 256 bytes is imposed. + */ +static int +crypto_strongest_rand_raw(uint8_t *out, size_t out_len) +{ + static const size_t sanity_min_size = 16; + static const int max_attempts = 3; + tor_assert(out_len <= MAX_STRONGEST_RAND_SIZE); + + /* For buffers >= 16 bytes (128 bits), we sanity check the output by + * zero filling the buffer and ensuring that it actually was at least + * partially modified. + * + * Checking that any individual byte is non-zero seems like it would + * fail too often (p = out_len * 1/256) for comfort, but this is an + * "adjust according to taste" sort of check. + */ + memwipe(out, 0, out_len); + for (int i = 0; i < max_attempts; i++) { + /* Try to use the syscall/OS favored mechanism to get strong entropy. */ + if (crypto_strongest_rand_syscall(out, out_len) != 0) { + /* Try to use the less-favored mechanism to get strong entropy. */ + if (crypto_strongest_rand_fallback(out, out_len) != 0) { + /* Welp, we tried. Hopefully the calling code terminates the process + * since we're basically boned without good entropy. + */ + log_warn(LD_CRYPTO, + "Cannot get strong entropy: no entropy source found."); + return -1; + } + } + + if ((out_len < sanity_min_size) || !tor_mem_is_zero((char*)out, out_len)) + return 0; + } + + /* We tried max_attempts times to fill a buffer >= 128 bits long, + * and each time it returned all '0's. Either the system entropy + * source is busted, or the user should go out and buy a ticket to + * every lottery on the planet. + */ + log_warn(LD_CRYPTO, "Strong OS entropy returned all zero buffer."); + return -1; +} + +/** Try to get <b>out_len</b> bytes of the strongest entropy we can generate, + * storing it into <b>out</b>. + */ +void +crypto_strongest_rand(uint8_t *out, size_t out_len) +{ +#define DLEN SHA512_DIGEST_LENGTH + /* We're going to hash DLEN bytes from the system RNG together with some + * bytes from the openssl PRNG, in order to yield DLEN bytes. + */ + uint8_t inp[DLEN*2]; + uint8_t tmp[DLEN]; + tor_assert(out); + while (out_len) { + crypto_rand((char*) inp, DLEN); + if (crypto_strongest_rand_raw(inp+DLEN, DLEN) < 0) { + log_err(LD_CRYPTO, "Failed to load strong entropy when generating an " + "important key. Exiting."); + /* Die with an assertion so we get a stack trace. */ + tor_assert(0); + } + if (out_len >= DLEN) { + SHA512(inp, sizeof(inp), out); + out += DLEN; + out_len -= DLEN; + } else { + SHA512(inp, sizeof(inp), tmp); + memcpy(out, tmp, out_len); + break; + } + } + memwipe(tmp, 0, sizeof(tmp)); + memwipe(inp, 0, sizeof(inp)); +#undef DLEN +} + /** Seed OpenSSL's random number generator with bytes from the operating - * system. <b>startup</b> should be true iff we have just started Tor and - * have not yet allocated a bunch of fds. Return 0 on success, -1 on failure. + * system. Return 0 on success, -1 on failure. */ int crypto_seed_rng(void) @@ -2351,41 +2724,51 @@ crypto_seed_rng(void) if (rand_poll_ok == 0) log_warn(LD_CRYPTO, "RAND_poll() failed."); - load_entropy_ok = !crypto_strongest_rand(buf, sizeof(buf)); + load_entropy_ok = !crypto_strongest_rand_raw(buf, sizeof(buf)); if (load_entropy_ok) { RAND_seed(buf, sizeof(buf)); } memwipe(buf, 0, sizeof(buf)); - if (rand_poll_ok || load_entropy_ok) + if ((rand_poll_ok || load_entropy_ok) && RAND_status() == 1) return 0; else return -1; } -/** Write <b>n</b> bytes of strong random data to <b>to</b>. Return 0 on - * success, -1 on failure, with support for mocking for unit tests. +/** Write <b>n</b> bytes of strong random data to <b>to</b>. Supports mocking + * for unit tests. + * + * This function is not allowed to fail; if it would fail to generate strong + * entropy, it must terminate the process instead. */ -MOCK_IMPL(int, +MOCK_IMPL(void, crypto_rand, (char *to, size_t n)) { - return crypto_rand_unmocked(to, n); + crypto_rand_unmocked(to, n); } -/** Write <b>n</b> bytes of strong random data to <b>to</b>. Return 0 on - * success, -1 on failure. Most callers will want crypto_rand instead. +/** Write <b>n</b> bytes of strong random data to <b>to</b>. Most callers + * will want crypto_rand instead. + * + * This function is not allowed to fail; if it would fail to generate strong + * entropy, it must terminate the process instead. */ -int +void crypto_rand_unmocked(char *to, size_t n) { int r; + if (n == 0) + return; + tor_assert(n < INT_MAX); tor_assert(to); r = RAND_bytes((unsigned char*)to, (int)n); - if (r == 0) - crypto_log_errors(LOG_WARN, "generating random data"); - return (r == 1) ? 0 : -1; + /* We consider a PRNG failure non-survivable. Let's assert so that we get a + * stack trace about where it happened. + */ + tor_assert(r >= 0); } /** Return a pseudorandom integer, chosen uniformly from the values @@ -2411,8 +2794,8 @@ crypto_rand_int(unsigned int max) } } -/** Return a pseudorandom integer, chosen uniformly from the values <i>i</i> - * such that <b>min</b> <= <i>i</i> < <b>max</b>. +/** Return a pseudorandom integer, chosen uniformly from the values i such + * that min <= i < max. * * <b>min</b> MUST be in range [0, <b>max</b>). * <b>max</b> MUST be in range (min, INT_MAX]. @@ -2489,7 +2872,7 @@ crypto_rand_double(void) /** Generate and return a new random hostname starting with <b>prefix</b>, * ending with <b>suffix</b>, and containing no fewer than * <b>min_rand_len</b> and no more than <b>max_rand_len</b> random base32 - * characters between. + * characters. Does not check for failure. * * Clip <b>max_rand_len</b> to MAX_DNS_LABEL_SIZE. **/ @@ -2585,13 +2968,32 @@ memwipe(void *mem, uint8_t byte, size_t sz) * have this function call "memset". A smart compiler could inline it, then * eliminate dead memsets, and declare itself to be clever. */ +#if defined(SecureZeroMemory) || defined(HAVE_SECUREZEROMEMORY) + /* Here's what you do on windows. */ + SecureZeroMemory(mem,sz); +#elif defined(HAVE_RTLSECUREZEROMEMORY) + RtlSecureZeroMemory(mem,sz); +#elif defined(HAVE_EXPLICIT_BZERO) + /* The BSDs provide this. */ + explicit_bzero(mem, sz); +#elif defined(HAVE_MEMSET_S) + /* This is in the C99 standard. */ + memset_s(mem, sz, 0, sz); +#else /* This is a slow and ugly function from OpenSSL that fills 'mem' with junk * based on the pointer value, then uses that junk to update a global * variable. It's an elaborate ruse to trick the compiler into not * optimizing out the "wipe this memory" code. Read it if you like zany * programming tricks! In later versions of Tor, we should look for better - * not-optimized-out memory wiping stuff. */ + * not-optimized-out memory wiping stuff... + * + * ...or maybe not. In practice, there are pure-asm implementations of + * OPENSSL_cleanse() on most platforms, which ought to do the job. + **/ + OPENSSL_cleanse(mem, sz); +#endif + /* Just in case some caller of memwipe() is relying on getting a buffer * filled with a particular value, fill the buffer. * @@ -2626,6 +3028,10 @@ openssl_locking_cb_(int mode, int n, const char *file, int line) tor_mutex_release(openssl_mutexes_[n]); } +#if 0 +/* This code is disabled, because OpenSSL never actually uses these callbacks. + */ + /** OpenSSL helper type: wraps a Tor mutex so that OpenSSL can use it * as a lock. */ struct CRYPTO_dynlock_value { @@ -2670,6 +3076,7 @@ openssl_dynlock_destroy_cb_(struct CRYPTO_dynlock_value *v, tor_mutex_free(v->lock); tor_free(v); } +#endif static void tor_set_openssl_thread_id(CRYPTO_THREADID *threadid) @@ -2679,7 +3086,7 @@ tor_set_openssl_thread_id(CRYPTO_THREADID *threadid) /** @{ */ /** Helper: Construct mutexes, and set callbacks to help OpenSSL handle being - * multithreaded. */ + * multithreaded. Returns 0. */ static int setup_openssl_threading(void) { @@ -2691,23 +3098,22 @@ setup_openssl_threading(void) openssl_mutexes_[i] = tor_mutex_new(); CRYPTO_set_locking_callback(openssl_locking_cb_); CRYPTO_THREADID_set_callback(tor_set_openssl_thread_id); +#if 0 CRYPTO_set_dynlock_create_callback(openssl_dynlock_create_cb_); CRYPTO_set_dynlock_lock_callback(openssl_dynlock_lock_cb_); CRYPTO_set_dynlock_destroy_callback(openssl_dynlock_destroy_cb_); +#endif return 0; } -/** Uninitialize the crypto library. Return 0 on success, -1 on failure. +/** Uninitialize the crypto library. Return 0 on success. Does not detect + * failure. */ int crypto_global_cleanup(void) { EVP_cleanup(); -#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) ERR_remove_thread_state(NULL); -#else - ERR_remove_state(0); -#endif ERR_free_strings(); if (dh_param_p) diff --git a/src/common/crypto.h b/src/common/crypto.h index 6256f7346b..fa2ed610c7 100644 --- a/src/common/crypto.h +++ b/src/common/crypto.h @@ -16,6 +16,7 @@ #include <stdio.h> #include "torint.h" #include "testsupport.h" +#include "compat.h" /* Macro to create an arbitrary OpenSSL version number as used by @@ -54,6 +55,8 @@ /** Length of the output of our second (improved) message digests. (For now * this is just sha256, but it could be any other 256-bit digest.) */ #define DIGEST256_LEN 32 +/** Length of the output of our 64-bit optimized message digests (SHA512). */ +#define DIGEST512_LEN 64 /** Length of our symmetric cipher's keys. */ #define CIPHER_KEY_LEN 16 /** Length of our symmetric cipher's IV. */ @@ -69,6 +72,9 @@ /** Length of a sha256 message digest when encoded in base64 with trailing = * signs removed. */ #define BASE64_DIGEST256_LEN 43 +/** Length of a sha512 message digest when encoded in base64 with trailing = + * signs removed. */ +#define BASE64_DIGEST512_LEN 86 /** Constant used to indicate OAEP padding for public-key encryption */ #define PK_PKCS1_OAEP_PADDING 60002 @@ -83,43 +89,48 @@ #define HEX_DIGEST_LEN 40 /** Length of hex encoding of SHA256 digest, not including final NUL. */ #define HEX_DIGEST256_LEN 64 +/** Length of hex encoding of SHA512 digest, not including final NUL. */ +#define HEX_DIGEST512_LEN 128 typedef enum { DIGEST_SHA1 = 0, DIGEST_SHA256 = 1, + DIGEST_SHA512 = 2, + DIGEST_SHA3_256 = 3, + DIGEST_SHA3_512 = 4, } digest_algorithm_t; -#define N_DIGEST_ALGORITHMS (DIGEST_SHA256+1) -#define digest_algorithm_bitfield_t ENUM_BF(digest_algorithm_t) +#define N_DIGEST_ALGORITHMS (DIGEST_SHA3_512+1) /** A set of all the digests we know how to compute, taken on a single - * string. Any digests that are shorter than 256 bits are right-padded + * string. Any digests that are shorter than 512 bits are right-padded * with 0 bits. * - * Note that this representation wastes 12 bytes for the SHA1 case, so + * Note that this representation wastes 44 bytes for the SHA1 case, so * don't use it for anything where we need to allocate a whole bunch at * once. **/ typedef struct { - char d[N_DIGEST_ALGORITHMS][DIGEST256_LEN]; + char d[N_DIGEST_ALGORITHMS][DIGEST512_LEN]; } digests_t; typedef struct crypto_pk_t crypto_pk_t; typedef struct crypto_cipher_t crypto_cipher_t; typedef struct crypto_digest_t crypto_digest_t; +typedef struct crypto_xof_t crypto_xof_t; typedef struct crypto_dh_t crypto_dh_t; /* global state */ const char * crypto_openssl_get_version_str(void); const char * crypto_openssl_get_header_version_str(void); -int crypto_early_init(void); +int crypto_early_init(void) ATTR_WUR; int crypto_global_init(int hardwareAccel, const char *accelName, - const char *accelPath); + const char *accelPath) ATTR_WUR; void crypto_thread_cleanup(void); int crypto_global_cleanup(void); /* environment setup */ -crypto_pk_t *crypto_pk_new(void); +MOCK_DECL(crypto_pk_t *,crypto_pk_new,(void)); void crypto_pk_free(crypto_pk_t *env); void crypto_set_tls_dh_prime(void); @@ -128,7 +139,7 @@ crypto_cipher_t *crypto_cipher_new_with_iv(const char *key, const char *iv); void crypto_cipher_free(crypto_cipher_t *env); /* public key crypto */ -int crypto_pk_generate_key_with_bits(crypto_pk_t *env, int bits); +MOCK_DECL(int, crypto_pk_generate_key_with_bits,(crypto_pk_t *env, int bits)); #define crypto_pk_generate_key(env) \ crypto_pk_generate_key_with_bits((env), (PK_BYTES*8)) @@ -207,6 +218,8 @@ int crypto_cipher_decrypt_with_iv(const char *key, int crypto_digest(char *digest, const char *m, size_t len); int crypto_digest256(char *digest, const char *m, size_t len, digest_algorithm_t algorithm); +int crypto_digest512(char *digest, const char *m, size_t len, + digest_algorithm_t algorithm); int crypto_digest_all(digests_t *ds_out, const char *m, size_t len); struct smartlist_t; void crypto_digest_smartlist_prefix(char *digest_out, size_t len_out, @@ -221,6 +234,7 @@ const char *crypto_digest_algorithm_get_name(digest_algorithm_t alg); int crypto_digest_algorithm_parse_name(const char *name); crypto_digest_t *crypto_digest_new(void); crypto_digest_t *crypto_digest256_new(digest_algorithm_t algorithm); +crypto_digest_t *crypto_digest512_new(digest_algorithm_t algorithm); void crypto_digest_free(crypto_digest_t *digest); void crypto_digest_add_bytes(crypto_digest_t *digest, const char *data, size_t len); @@ -232,6 +246,10 @@ void crypto_digest_assign(crypto_digest_t *into, void crypto_hmac_sha256(char *hmac_out, const char *key, size_t key_len, const char *msg, size_t msg_len); +crypto_xof_t *crypto_xof_new(void); +void crypto_xof_add_bytes(crypto_xof_t *xof, const uint8_t *data, size_t len); +void crypto_xof_squeeze_bytes(crypto_xof_t *xof, uint8_t *out, size_t len); +void crypto_xof_free(crypto_xof_t *xof); /* Key negotiation */ #define DH_TYPE_CIRCUIT 1 @@ -258,10 +276,10 @@ int crypto_expand_key_material_rfc5869_sha256( uint8_t *key_out, size_t key_out_len); /* random numbers */ -int crypto_seed_rng(void); -MOCK_DECL(int,crypto_rand,(char *to, size_t n)); -int crypto_rand_unmocked(char *to, size_t n); -int crypto_strongest_rand(uint8_t *out, size_t out_len); +int crypto_seed_rng(void) ATTR_WUR; +MOCK_DECL(void,crypto_rand,(char *to, size_t n)); +void crypto_rand_unmocked(char *to, size_t n); +void crypto_strongest_rand(uint8_t *out, size_t out_len); int crypto_rand_int(unsigned int max); int crypto_rand_int_range(unsigned int min, unsigned int max); uint64_t crypto_rand_uint64_range(uint64_t min, uint64_t max); @@ -289,11 +307,15 @@ struct evp_pkey_st; struct dh_st; struct rsa_st *crypto_pk_get_rsa_(crypto_pk_t *env); crypto_pk_t *crypto_new_pk_from_rsa_(struct rsa_st *rsa); -struct evp_pkey_st *crypto_pk_get_evp_pkey_(crypto_pk_t *env, - int private); +MOCK_DECL(struct evp_pkey_st *, crypto_pk_get_evp_pkey_,(crypto_pk_t *env, + int private)); struct dh_st *crypto_dh_get_dh_(crypto_dh_t *dh); void crypto_add_spaces_to_fp(char *out, size_t outlen, const char *in); +#ifdef CRYPTO_PRIVATE +STATIC int crypto_force_rand_ssleay(void); +#endif + #endif diff --git a/src/common/crypto_curve25519.c b/src/common/crypto_curve25519.c index ac0b08a552..2002483265 100644 --- a/src/common/crypto_curve25519.c +++ b/src/common/crypto_curve25519.c @@ -111,19 +111,11 @@ curve25519_public_key_is_ok(const curve25519_public_key_t *key) int curve25519_rand_seckey_bytes(uint8_t *out, int extra_strong) { - uint8_t k_tmp[CURVE25519_SECKEY_LEN]; + if (extra_strong) + crypto_strongest_rand(out, CURVE25519_SECKEY_LEN); + else + crypto_rand((char*)out, CURVE25519_SECKEY_LEN); - if (crypto_rand((char*)out, CURVE25519_SECKEY_LEN) < 0) - return -1; - if (extra_strong && !crypto_strongest_rand(k_tmp, CURVE25519_SECKEY_LEN)) { - /* If they asked for extra-strong entropy and we have some, use it as an - * HMAC key to improve not-so-good entropy rather than using it directly, - * just in case the extra-strong entropy is less amazing than we hoped. */ - crypto_hmac_sha256((char*) out, - (const char *)k_tmp, sizeof(k_tmp), - (const char *)out, CURVE25519_SECKEY_LEN); - } - memwipe(k_tmp, 0, sizeof(k_tmp)); return 0; } diff --git a/src/common/crypto_ed25519.c b/src/common/crypto_ed25519.c index 1749efc34c..9df665f66a 100644 --- a/src/common/crypto_ed25519.c +++ b/src/common/crypto_ed25519.c @@ -96,6 +96,28 @@ get_ed_impl(void) return ed25519_impl; } +#ifdef TOR_UNIT_TESTS +static const ed25519_impl_t *saved_ed25519_impl = NULL; +void +crypto_ed25519_testing_force_impl(const char *name) +{ + tor_assert(saved_ed25519_impl == NULL); + saved_ed25519_impl = ed25519_impl; + if (! strcmp(name, "donna")) { + ed25519_impl = &impl_donna; + } else { + tor_assert(!strcmp(name, "ref10")); + ed25519_impl = &impl_ref10; + } +} +void +crypto_ed25519_testing_restore_impl(void) +{ + ed25519_impl = saved_ed25519_impl; + saved_ed25519_impl = NULL; +} +#endif + /** * Initialize a new ed25519 secret key in <b>seckey_out</b>. If * <b>extra_strong</b>, take the RNG inputs directly from the operating @@ -107,7 +129,9 @@ ed25519_secret_key_generate(ed25519_secret_key_t *seckey_out, { int r; uint8_t seed[32]; - if (! extra_strong || crypto_strongest_rand(seed, sizeof(seed)) < 0) + if (extra_strong) + crypto_strongest_rand(seed, sizeof(seed)); + else crypto_rand((char*)seed, sizeof(seed)); r = get_ed_impl()->seckey_expand(seckey_out->seckey, seed); diff --git a/src/common/crypto_ed25519.h b/src/common/crypto_ed25519.h index bdac12eb27..4fa7ea11cf 100644 --- a/src/common/crypto_ed25519.h +++ b/src/common/crypto_ed25519.h @@ -111,5 +111,10 @@ int ed25519_pubkey_eq(const ed25519_public_key_t *key1, void ed25519_set_impl_params(int use_donna); void ed25519_init(void); +#ifdef TOR_UNIT_TESTS +void crypto_ed25519_testing_force_impl(const char *name); +void crypto_ed25519_testing_restore_impl(void); +#endif + #endif diff --git a/src/common/di_ops.c b/src/common/di_ops.c index c9d1350880..70f2da7377 100644 --- a/src/common/di_ops.c +++ b/src/common/di_ops.c @@ -25,6 +25,9 @@ int tor_memcmp(const void *a, const void *b, size_t len) { +#ifdef HAVE_TIMINGSAFE_MEMCMP + return timingsafe_memcmp(a, b, len); +#else const uint8_t *x = a; const uint8_t *y = b; size_t i = len; @@ -83,6 +86,7 @@ tor_memcmp(const void *a, const void *b, size_t len) } return retval; +#endif /* timingsafe_memcmp */ } /** diff --git a/src/common/include.am b/src/common/include.am index 7de93ba2ac..5afb30da6a 100644 --- a/src/common/include.am +++ b/src/common/include.am @@ -78,7 +78,8 @@ LIBOR_A_SOURCES = \ $(threads_impl_source) \ $(readpassphrase_source) -src/common/log.o: micro-revision.i +src/common/src_common_libor_testing_a-log.$(OBJEXT) \ + src/common/log.$(OBJEXT): micro-revision.i LIBOR_CRYPTO_A_SOURCES = \ src/common/aes.c \ @@ -118,6 +119,7 @@ COMMONHEADERS = \ src/common/ciphers.inc \ src/common/compat.h \ src/common/compat_libevent.h \ + src/common/compat_openssl.h \ src/common/compat_threads.h \ src/common/container.h \ src/common/crypto.h \ diff --git a/src/common/log.c b/src/common/log.c index e23691b6ab..4a8a7b1165 100644 --- a/src/common/log.c +++ b/src/common/log.c @@ -64,7 +64,7 @@ typedef struct logfile_t { static void log_free(logfile_t *victim); /** Helper: map a log severity to descriptive string. */ -static INLINE const char * +static inline const char * sev_to_string(int severity) { switch (severity) { @@ -80,7 +80,7 @@ sev_to_string(int severity) } /** Helper: decide whether to include the function name in the log message. */ -static INLINE int +static inline int should_log_function_name(log_domain_mask_t domain, int severity) { switch (severity) { @@ -163,7 +163,7 @@ static void close_log(logfile_t *victim); static char *domain_to_string(log_domain_mask_t domain, char *buf, size_t buflen); -static INLINE char *format_msg(char *buf, size_t buf_len, +static inline char *format_msg(char *buf, size_t buf_len, log_domain_mask_t domain, int severity, const char *funcname, const char *suffix, const char *format, va_list ap, size_t *msg_len_out) @@ -199,7 +199,7 @@ set_log_time_granularity(int granularity_msec) /** Helper: Write the standard prefix for log lines to a * <b>buf_len</b> character buffer in <b>buf</b>. */ -static INLINE size_t +static inline size_t log_prefix_(char *buf, size_t buf_len, int severity) { time_t t; @@ -278,7 +278,7 @@ const char bug_suffix[] = " (on Tor " VERSION * than once.) Return a pointer to the first character of the message * portion of the formatted string. */ -static INLINE char * +static inline char * format_msg(char *buf, size_t buf_len, log_domain_mask_t domain, int severity, const char *funcname, const char *suffix, @@ -393,7 +393,7 @@ pending_log_message_free(pending_log_message_t *msg) /** Return true iff <b>lf</b> would like to receive a message with the * specified <b>severity</b> in the specified <b>domain</b>. */ -static INLINE int +static inline int logfile_wants_message(const logfile_t *lf, int severity, log_domain_mask_t domain) { @@ -416,7 +416,7 @@ logfile_wants_message(const logfile_t *lf, int severity, * we already deferred this message for pending callbacks and don't need to do * it again. Otherwise, if we need to do it, do it, and set * <b>callbacks_deferred</b> to 1. */ -static INLINE void +static inline void logfile_deliver(logfile_t *lf, const char *buf, size_t msg_len, const char *msg_after_prefix, log_domain_mask_t domain, int severity, int *callbacks_deferred) @@ -1097,14 +1097,25 @@ add_file_log(const log_severity_list_t *severity, const char *filename, #ifdef HAVE_SYSLOG_H /** * Add a log handler to send messages to they system log facility. + * + * If this is the first log handler, opens syslog with ident Tor or + * Tor-<syslog_identity_tag> if that is not NULL. */ int -add_syslog_log(const log_severity_list_t *severity) +add_syslog_log(const log_severity_list_t *severity, + const char* syslog_identity_tag) { logfile_t *lf; - if (syslog_count++ == 0) + if (syslog_count++ == 0) { /* This is the first syslog. */ - openlog("Tor", LOG_PID | LOG_NDELAY, LOGFACILITY); + static char buf[256]; + if (syslog_identity_tag) { + tor_snprintf(buf, sizeof(buf), "Tor-%s", syslog_identity_tag); + } else { + tor_snprintf(buf, sizeof(buf), "Tor"); + } + openlog(buf, LOG_PID | LOG_NDELAY, LOGFACILITY); + } lf = tor_malloc_zero(sizeof(logfile_t)); lf->fd = -1; diff --git a/src/common/memarea.c b/src/common/memarea.c index 6841ba54e7..a8e6d455d6 100644 --- a/src/common/memarea.c +++ b/src/common/memarea.c @@ -61,7 +61,7 @@ #endif /** Increment <b>ptr</b> until it is aligned to MEMAREA_ALIGN. */ -static INLINE void * +static inline void * realign_pointer(void *ptr) { uintptr_t x = (uintptr_t)ptr; diff --git a/src/common/procmon.c b/src/common/procmon.c index 2d0f021724..346a0c6943 100644 --- a/src/common/procmon.c +++ b/src/common/procmon.c @@ -192,7 +192,8 @@ tor_process_monitor_new(struct event_base *base, tor_procmon_callback_t cb, void *cb_arg, const char **msg) { - tor_process_monitor_t *procmon = tor_malloc(sizeof(tor_process_monitor_t)); + tor_process_monitor_t *procmon = tor_malloc_zero( + sizeof(tor_process_monitor_t)); struct parsed_process_specifier_t ppspec; tor_assert(msg != NULL); diff --git a/src/common/sandbox.c b/src/common/sandbox.c index 950a92fbb3..d747a87563 100644 --- a/src/common/sandbox.c +++ b/src/common/sandbox.c @@ -199,6 +199,10 @@ static int filter_nopar_gen[] = { SCMP_SYS(stat64), #endif +#ifdef __NR_getrandom + SCMP_SYS(getrandom), +#endif + /* * These socket syscalls are not required on x86_64 and not supported with * some libseccomp versions (eg: 1.0.1) diff --git a/src/common/torgzip.c b/src/common/torgzip.c index 4f23407e23..5ba8ec4501 100644 --- a/src/common/torgzip.c +++ b/src/common/torgzip.c @@ -91,7 +91,7 @@ tor_zlib_get_header_version_str(void) } /** Return the 'bits' value to tell zlib to use <b>method</b>.*/ -static INLINE int +static inline int method_bits(compress_method_t method, zlib_compression_level_t level) { /* Bits+16 means "use gzip" in zlib >= 1.2 */ @@ -104,7 +104,7 @@ method_bits(compress_method_t method, zlib_compression_level_t level) } } -static INLINE int +static inline int get_memlevel(zlib_compression_level_t level) { switch (level) { diff --git a/src/common/torint.h b/src/common/torint.h index 6171700898..418fe0fabf 100644 --- a/src/common/torint.h +++ b/src/common/torint.h @@ -336,6 +336,32 @@ typedef uint32_t uintptr_t; #endif /* time_t_is_signed */ #endif /* ifndef(TIME_MAX) */ +#ifndef TIME_MIN + +#ifdef TIME_T_IS_SIGNED + +#if (SIZEOF_TIME_T == SIZEOF_INT) +#define TIME_MIN ((time_t)INT_MIN) +#elif (SIZEOF_TIME_T == SIZEOF_LONG) +#define TIME_MIN ((time_t)LONG_MIN) +#elif (SIZEOF_TIME_T == 8) +#define TIME_MIN ((time_t)INT64_MIN) +#else +#error "Can't define (signed) TIME_MIN" +#endif + +#else +/* Unsigned case */ +#if (SIZEOF_TIME_T == 4) +#define TIME_MIN ((time_t)UINT32_MIN) +#elif (SIZEOF_TIME_T == 8) +#define TIME_MIN ((time_t)UINT64_MIN) +#else +#error "Can't define (unsigned) TIME_MIN" +#endif +#endif /* time_t_is_signed */ +#endif /* ifndef(TIME_MIN) */ + #ifndef SIZE_MAX #if (SIZEOF_SIZE_T == 4) #define SIZE_MAX UINT32_MAX diff --git a/src/common/torlog.h b/src/common/torlog.h index 67edf14c04..3e8667895f 100644 --- a/src/common/torlog.h +++ b/src/common/torlog.h @@ -135,7 +135,8 @@ void add_stream_log(const log_severity_list_t *severity, const char *name, int add_file_log(const log_severity_list_t *severity, const char *filename, const int truncate); #ifdef HAVE_SYSLOG_H -int add_syslog_log(const log_severity_list_t *severity); +int add_syslog_log(const log_severity_list_t *severity, + const char* syslog_identity_tag); #endif int add_callback_log(const log_severity_list_t *severity, log_callback cb); void logs_set_domain_logging(int enabled); @@ -183,25 +184,25 @@ void log_fn_ratelim_(struct ratelim_t *ratelim, int severity, /** Log a message at level <b>severity</b>, using a pretty-printed version * of the current function name. */ #define log_fn(severity, domain, args...) \ - log_fn_(severity, domain, __PRETTY_FUNCTION__, args) + log_fn_(severity, domain, __FUNCTION__, args) /** As log_fn, but use <b>ratelim</b> (an instance of ratelim_t) to control * the frequency at which messages can appear. */ #define log_fn_ratelim(ratelim, severity, domain, args...) \ - log_fn_ratelim_(ratelim, severity, domain, __PRETTY_FUNCTION__, args) + log_fn_ratelim_(ratelim, severity, domain, __FUNCTION__, args) #define log_debug(domain, args...) \ STMT_BEGIN \ if (PREDICT_UNLIKELY(log_global_min_severity_ == LOG_DEBUG)) \ - log_fn_(LOG_DEBUG, domain, __PRETTY_FUNCTION__, args); \ + log_fn_(LOG_DEBUG, domain, __FUNCTION__, args); \ STMT_END #define log_info(domain, args...) \ - log_fn_(LOG_INFO, domain, __PRETTY_FUNCTION__, args) + log_fn_(LOG_INFO, domain, __FUNCTION__, args) #define log_notice(domain, args...) \ - log_fn_(LOG_NOTICE, domain, __PRETTY_FUNCTION__, args) + log_fn_(LOG_NOTICE, domain, __FUNCTION__, args) #define log_warn(domain, args...) \ - log_fn_(LOG_WARN, domain, __PRETTY_FUNCTION__, args) + log_fn_(LOG_WARN, domain, __FUNCTION__, args) #define log_err(domain, args...) \ - log_fn_(LOG_ERR, domain, __PRETTY_FUNCTION__, args) + log_fn_(LOG_ERR, domain, __FUNCTION__, args) #else /* ! defined(__GNUC__) */ diff --git a/src/common/tortls.c b/src/common/tortls.c index 536043e558..6e4cd3d480 100644 --- a/src/common/tortls.c +++ b/src/common/tortls.c @@ -16,6 +16,8 @@ #include "orconfig.h" +#define TORTLS_PRIVATE + #include <assert.h> #ifdef _WIN32 /*wrkard for dtls1.h >= 0.9.8m of "#include <winsock.h>"*/ #include <winsock2.h> @@ -38,9 +40,6 @@ #include <openssl/opensslv.h> #include "crypto.h" -#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,0,0) -#error "We require OpenSSL >= 1.0.0" -#endif #ifdef OPENSSL_NO_EC #error "We require OpenSSL with ECC support" #endif @@ -69,6 +68,7 @@ #include "compat_libevent.h" #endif +#define TORTLS_PRIVATE #include "tortls.h" #include "util.h" #include "torlog.h" @@ -80,11 +80,6 @@ #define X509_get_notAfter_const(cert) \ ((const ASN1_TIME*) X509_get_notAfter((X509 *)cert)) -/* Enable the "v2" TLS handshake. - */ -#define V2_HANDSHAKE_SERVER -#define V2_HANDSHAKE_CLIENT - /* Copied from or.h */ #define LEGAL_NICKNAME_CHARACTERS \ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" @@ -113,29 +108,6 @@ #define SSL3_FLAGS_ALLOW_UNSAFE_LEGACY_RENEGOTIATION 0x0010 #endif -/** Structure that we use for a single certificate. */ -struct tor_x509_cert_t { - X509 *cert; - uint8_t *encoded; - size_t encoded_len; - unsigned pkey_digests_set : 1; - digests_t cert_digests; - digests_t pkey_digests; -}; - -/** Holds a SSL_CTX object and related state used to configure TLS - * connections. - */ -typedef struct tor_tls_context_t { - int refcnt; - SSL_CTX *ctx; - tor_x509_cert_t *my_link_cert; - tor_x509_cert_t *my_id_cert; - tor_x509_cert_t *my_auth_cert; - crypto_pk_t *link_key; - crypto_pk_t *auth_key; -} tor_tls_context_t; - /** Return values for tor_tls_classify_client_ciphers. * * @{ @@ -154,60 +126,12 @@ typedef struct tor_tls_context_t { #define CIPHERS_UNRESTRICTED 3 /** @} */ -#define TOR_TLS_MAGIC 0x71571571 - -typedef enum { - TOR_TLS_ST_HANDSHAKE, TOR_TLS_ST_OPEN, TOR_TLS_ST_GOTCLOSE, - TOR_TLS_ST_SENTCLOSE, TOR_TLS_ST_CLOSED, TOR_TLS_ST_RENEGOTIATE, - TOR_TLS_ST_BUFFEREVENT -} tor_tls_state_t; -#define tor_tls_state_bitfield_t ENUM_BF(tor_tls_state_t) - -/** Holds a SSL object and its associated data. Members are only - * accessed from within tortls.c. - */ -struct tor_tls_t { - uint32_t magic; - tor_tls_context_t *context; /** A link to the context object for this tls. */ - SSL *ssl; /**< An OpenSSL SSL object. */ - int socket; /**< The underlying file descriptor for this TLS connection. */ - char *address; /**< An address to log when describing this connection. */ - tor_tls_state_bitfield_t state : 3; /**< The current SSL state, - * depending on which operations - * have completed successfully. */ - unsigned int isServer:1; /**< True iff this is a server-side connection */ - unsigned int wasV2Handshake:1; /**< True iff the original handshake for - * this connection used the updated version - * of the connection protocol (client sends - * different cipher list, server sends only - * one certificate). */ - /** True iff we should call negotiated_callback when we're done reading. */ - unsigned int got_renegotiate:1; - /** Return value from tor_tls_classify_client_ciphers, or 0 if we haven't - * called that function yet. */ - int8_t client_cipher_list_type; - /** Incremented every time we start the server side of a handshake. */ - uint8_t server_handshake_count; - size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last - * time. */ - /** Last values retrieved from BIO_number_read()/write(); see - * tor_tls_get_n_raw_bytes() for usage. - */ - unsigned long last_write_count; - unsigned long last_read_count; - /** If set, a callback to invoke whenever the client tries to renegotiate - * the handshake. */ - void (*negotiated_callback)(tor_tls_t *tls, void *arg); - /** Argument to pass to negotiated_callback. */ - void *callback_arg; -}; - /** The ex_data index in which we store a pointer to an SSL object's * corresponding tor_tls_t object. */ -static int tor_tls_object_ex_data_index = -1; +STATIC int tor_tls_object_ex_data_index = -1; /** Helper: Allocate tor_tls_object_ex_data_index. */ -static void +STATIC void tor_tls_allocate_tor_tls_object_ex_data_index(void) { if (tor_tls_object_ex_data_index == -1) { @@ -219,7 +143,7 @@ tor_tls_allocate_tor_tls_object_ex_data_index(void) /** Helper: given a SSL* pointer, return the tor_tls_t object using that * pointer. */ -static INLINE tor_tls_t * +STATIC inline tor_tls_t * tor_tls_get_by_ssl(const SSL *ssl) { tor_tls_t *result = SSL_get_ex_data(ssl, tor_tls_object_ex_data_index); @@ -230,21 +154,7 @@ tor_tls_get_by_ssl(const SSL *ssl) static void tor_tls_context_decref(tor_tls_context_t *ctx); static void tor_tls_context_incref(tor_tls_context_t *ctx); -static X509* tor_tls_create_certificate(crypto_pk_t *rsa, - crypto_pk_t *rsa_sign, - const char *cname, - const char *cname_sign, - unsigned int cert_lifetime); - -static int tor_tls_context_init_one(tor_tls_context_t **ppcontext, - crypto_pk_t *identity, - unsigned int key_lifetime, - unsigned int flags, - int is_client); -static tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity, - unsigned int key_lifetime, - unsigned int flags, - int is_client); + static int check_cert_lifetime_internal(int severity, const X509 *cert, int past_tolerance, int future_tolerance); @@ -252,8 +162,8 @@ static int check_cert_lifetime_internal(int severity, const X509 *cert, * to touch them. * * @{ */ -static tor_tls_context_t *server_tls_context = NULL; -static tor_tls_context_t *client_tls_context = NULL; +STATIC tor_tls_context_t *server_tls_context = NULL; +STATIC tor_tls_context_t *client_tls_context = NULL; /**@}*/ /** True iff tor_tls_init() has been called. */ @@ -347,7 +257,7 @@ tor_tls_log_one_error(tor_tls_t *tls, unsigned long err, /** Log all pending tls errors at level <b>severity</b> in log domain * <b>domain</b>. Use <b>doing</b> to describe our current activities. */ -static void +STATIC void tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) { unsigned long err; @@ -359,7 +269,7 @@ tls_log_errors(tor_tls_t *tls, int severity, int domain, const char *doing) /** Convert an errno (or a WSAerrno on windows) into a TOR_TLS_* error * code. */ -static int +STATIC int tor_errno_to_tls_error(int e) { switch (e) { @@ -410,7 +320,7 @@ tor_tls_err_to_string(int err) * If an error has occurred, log it at level <b>severity</b> and describe the * current action as <b>doing</b>. */ -static int +STATIC int tor_tls_get_error(tor_tls_t *tls, int r, int extra, const char *doing, int severity, int domain) { @@ -466,8 +376,9 @@ tor_tls_init(void) #if (SIZEOF_VOID_P >= 8 && \ OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,0,1)) - long version = SSLeay(); + long version = OpenSSL_version_num(); + /* LCOV_EXCL_START : we can't test these lines on the same machine */ if (version >= OPENSSL_V_SERIES(1,0,1)) { /* Warn if we could *almost* be running with much faster ECDH. If we're built for a 64-bit target, using OpenSSL 1.0.1, but we @@ -494,6 +405,7 @@ tor_tls_init(void) "support (using the enable-ec_nistp_64_gcc_128 option " "when configuring it) would make ECDH much faster."); } + /* LCOV_EXCL_STOP */ #endif tor_tls_allocate_tor_tls_object_ex_data_index(); @@ -524,7 +436,7 @@ tor_tls_free_all(void) * it: We always accept peer certs and complete the handshake. We * don't validate them until later. */ -static int +STATIC int always_accept_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx) { @@ -539,16 +451,20 @@ tor_x509_name_new(const char *cname) { int nid; X509_NAME *name; + /* LCOV_EXCL_BR_START : these branches will only fail on OOM errors */ if (!(name = X509_NAME_new())) return NULL; if ((nid = OBJ_txt2nid("commonName")) == NID_undef) goto error; if (!(X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, (unsigned char*)cname, -1, -1, 0))) goto error; + /* LCOV_EXCL_BR_STOP */ return name; error: + /* LCOV_EXCL_START : these lines will only execute on out of memory errors*/ X509_NAME_free(name); return NULL; + /* LCOV_EXCL_STOP */ } /** Generate and sign an X509 certificate with the public key <b>rsa</b>, @@ -559,12 +475,12 @@ tor_x509_name_new(const char *cname) * * Return a certificate on success, NULL on failure. */ -static X509 * -tor_tls_create_certificate(crypto_pk_t *rsa, - crypto_pk_t *rsa_sign, - const char *cname, - const char *cname_sign, - unsigned int cert_lifetime) +MOCK_IMPL(STATIC X509 *, + tor_tls_create_certificate,(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)) { /* OpenSSL generates self-signed certificates with random 64-bit serial * numbers, so let's do that too. */ @@ -601,8 +517,7 @@ tor_tls_create_certificate(crypto_pk_t *rsa, goto error; { /* our serial number is 8 random bytes. */ - if (crypto_rand((char *)serial_tmp, sizeof(serial_tmp)) < 0) - goto error; + crypto_rand((char *)serial_tmp, sizeof(serial_tmp)); if (!(serial_number = BN_bin2bn(serial_tmp, sizeof(serial_tmp), NULL))) goto error; if (!(BN_to_ASN1_INTEGER(serial_number, X509_get_serialNumber(x509)))) @@ -731,7 +646,9 @@ tor_x509_cert_free(tor_x509_cert_t *cert) X509_free(cert->cert); tor_free(cert->encoded); memwipe(cert, 0x03, sizeof(*cert)); + /* LCOV_EXCL_BR_START since cert will never be NULL here */ tor_free(cert); + /* LCOV_EXCL_BR_STOP */ } /** @@ -739,8 +656,8 @@ tor_x509_cert_free(tor_x509_cert_t *cert) * * Steals a reference to x509_cert. */ -static tor_x509_cert_t * -tor_x509_cert_new(X509 *x509_cert) +MOCK_IMPL(STATIC tor_x509_cert_t *, + tor_x509_cert_new,(X509 *x509_cert)) { tor_x509_cert_t *cert; EVP_PKEY *pkey; @@ -754,10 +671,12 @@ tor_x509_cert_new(X509 *x509_cert) length = i2d_X509(x509_cert, &buf); cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); if (length <= 0 || buf == NULL) { + /* LCOV_EXCL_START for the same reason as the exclusion above */ tor_free(cert); log_err(LD_CRYPTO, "Couldn't get length of encoded x509 certificate"); X509_free(x509_cert); return NULL; + /* LCOV_EXCL_STOP */ } cert->encoded_len = (size_t) length; cert->encoded = tor_malloc(length); @@ -864,7 +783,9 @@ tor_tls_context_decref(tor_tls_context_t *ctx) tor_x509_cert_free(ctx->my_auth_cert); crypto_pk_free(ctx->link_key); crypto_pk_free(ctx->auth_key); + /* LCOV_EXCL_BR_START since ctx will never be NULL here */ tor_free(ctx); + /* LCOV_EXCL_BR_STOP */ } } @@ -960,11 +881,13 @@ tor_tls_cert_is_valid(int severity, int check_rsa_1024) { check_no_tls_errors(); - EVP_PKEY *cert_key; - EVP_PKEY *signing_key = X509_get_pubkey(signing_cert->cert); int r, key_ok = 0; + if (!signing_cert || !cert) + goto bad; + + EVP_PKEY *signing_key = X509_get_pubkey(signing_cert->cert); if (!signing_key) goto bad; r = X509_verify(cert->cert, signing_key); @@ -1085,7 +1008,7 @@ tor_tls_context_init(unsigned flags, * it generates new certificates; all new connections will use * the new SSL context. */ -static int +STATIC int tor_tls_context_init_one(tor_tls_context_t **ppcontext, crypto_pk_t *identity, unsigned int key_lifetime, @@ -1119,7 +1042,7 @@ tor_tls_context_init_one(tor_tls_context_t **ppcontext, * <b>identity</b> should be set to the identity key used to sign the * certificate. */ -static tor_tls_context_t * +STATIC tor_tls_context_t * tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, unsigned flags, int is_client) { @@ -1200,23 +1123,6 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, * historically been chosen for fingerprinting resistance. */ SSL_CTX_set_options(result->ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); - /* Disable TLS1.1 and TLS1.2 if they exist. We need to do this to - * workaround a bug present in all OpenSSL 1.0.1 versions (as of 1 - * June 2012), wherein renegotiating while using one of these TLS - * protocols will cause the client to send a TLS 1.0 ServerHello - * rather than a ServerHello written with the appropriate protocol - * version. Once some version of OpenSSL does TLS1.1 and TLS1.2 - * renegotiation properly, we can turn them back on when built with - * that version. */ -#if OPENSSL_VERSION_NUMBER < OPENSSL_V(1,0,1,'e') -#ifdef SSL_OP_NO_TLSv1_2 - SSL_CTX_set_options(result->ctx, SSL_OP_NO_TLSv1_2); -#endif -#ifdef SSL_OP_NO_TLSv1_1 - SSL_CTX_set_options(result->ctx, SSL_OP_NO_TLSv1_1); -#endif -#endif - /* Disable TLS tickets if they're supported. We never want to use them; * using them can make our perfect forward secrecy a little worse, *and* * create an opportunity to fingerprint us (since it's unusual to use them @@ -1343,11 +1249,13 @@ tor_tls_context_new(crypto_pk_t *identity, unsigned int key_lifetime, } /** Invoked when a TLS state changes: log the change at severity 'debug' */ -static void +STATIC void tor_tls_debug_state_callback(const SSL *ssl, int type, int val) { + /* LCOV_EXCL_START since this depends on whether debug is captured or not */ log_debug(LD_HANDSHAKE, "SSL %p is now in state %s [type=%d,val=%d].", ssl, SSL_state_string_long(ssl), type, val); + /* LCOV_EXCL_STOP */ } /* Return the name of the negotiated ciphersuite in use on <b>tls</b> */ @@ -1357,13 +1265,11 @@ tor_tls_get_ciphersuite_name(tor_tls_t *tls) return SSL_get_cipher(tls->ssl); } -#ifdef V2_HANDSHAKE_SERVER - /* Here's the old V2 cipher list we sent from 0.2.1.1-alpha up to * 0.2.3.17-beta. If a client is using this list, we can't believe the ciphers * that it claims to support. We'll prune this list to remove the ciphers * *we* don't recognize. */ -static uint16_t v2_cipher_list[] = { +STATIC uint16_t v2_cipher_list[] = { 0xc00a, /* TLS1_TXT_ECDHE_ECDSA_WITH_AES_256_CBC_SHA */ 0xc014, /* TLS1_TXT_ECDHE_RSA_WITH_AES_256_CBC_SHA */ 0x0039, /* TLS1_TXT_DHE_RSA_WITH_AES_256_SHA */ @@ -1399,11 +1305,12 @@ static int v2_cipher_list_pruned = 0; /** Return 0 if <b>m</b> does not support the cipher with ID <b>cipher</b>; * return 1 if it does support it, or if we have no way to tell. */ -static int +STATIC int find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) { const SSL_CIPHER *c; #ifdef HAVE_SSL_CIPHER_FIND + (void) m; { unsigned char cipherid[3]; tor_assert(ssl); @@ -1416,7 +1323,9 @@ find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) tor_assert((SSL_CIPHER_get_id(c) & 0xffff) == cipher); return c != NULL; } -#elif defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) +#else + +# if defined(HAVE_STRUCT_SSL_METHOD_ST_GET_CIPHER_BY_CHAR) if (m && m->get_cipher_by_char) { unsigned char cipherid[3]; set_uint16(cipherid, htons(cipher)); @@ -1427,9 +1336,9 @@ find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) if (c) tor_assert((c->id & 0xffff) == cipher); return c != NULL; - } else -#endif -#if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) + } +# endif +# if OPENSSL_VERSION_NUMBER < OPENSSL_V_SERIES(1,1,0) if (m && m->get_cipher && m->num_ciphers) { /* It would seem that some of the "let's-clean-up-openssl" forks have * removed the get_cipher_by_char function. Okay, so now you get a @@ -1444,11 +1353,12 @@ find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, uint16_t cipher) } return 0; } -#endif +# endif (void) ssl; (void) m; (void) cipher; return 1; /* No way to search */ +#endif } /** Remove from v2_cipher_list every cipher that we don't support, so that @@ -1481,7 +1391,7 @@ prune_v2_cipher_list(const SSL *ssl) * client it is. Return one of CIPHERS_ERR, CIPHERS_V1, CIPHERS_V2, * CIPHERS_UNRESTRICTED. **/ -static int +STATIC int tor_tls_classify_client_ciphers(const SSL *ssl, STACK_OF(SSL_CIPHER) *peer_ciphers) { @@ -1563,7 +1473,7 @@ tor_tls_classify_client_ciphers(const SSL *ssl, /** Return true iff the cipher list suggested by the client for <b>ssl</b> is * a list that indicates that the client knows how to do the v2 TLS connection * handshake. */ -static int +STATIC int tor_tls_client_is_using_v2_ciphers(const SSL *ssl) { STACK_OF(SSL_CIPHER) *ciphers; @@ -1587,11 +1497,10 @@ tor_tls_client_is_using_v2_ciphers(const SSL *ssl) * do not send or request extra certificates in v2 handshakes.</li> * <li>To detect renegotiation</li></ul> */ -static void +STATIC void tor_tls_server_info_callback(const SSL *ssl, int type, int val) { tor_tls_t *tls; - int ssl_state; (void) val; tor_tls_debug_state_callback(ssl, type, val); @@ -1599,11 +1508,9 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) if (type != SSL_CB_ACCEPT_LOOP) return; - ssl_state = SSL_state(ssl); - if ((ssl_state != SSL3_ST_SW_SRVR_HELLO_A) && - (ssl_state != SSL3_ST_SW_SRVR_HELLO_B)) + OSSL_HANDSHAKE_STATE ssl_state = SSL_get_state(ssl); + if (! STATE_IS_SW_SERVER_HELLO(ssl_state)) return; - tls = tor_tls_get_by_ssl(ssl); if (tls) { /* Check whether we're watching for renegotiates. If so, this is one! */ @@ -1633,11 +1540,12 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) if (tls) { tls->wasV2Handshake = 1; } else { + /* LCOV_EXCL_START this line is not reachable */ log_warn(LD_BUG, "Couldn't look up the tls for an SSL*. How odd!"); + /* LCOV_EXCL_STOP */ } } } -#endif /** Callback to get invoked on a server after we've read the list of ciphers * the client supports, but before we pick our own ciphersuite. @@ -1651,7 +1559,7 @@ tor_tls_server_info_callback(const SSL *ssl, int type, int val) * authentication on the fly. But as long as we return 0, we won't actually be * setting up a shared secret, and all will be fine. */ -static int +STATIC int tor_tls_session_secret_cb(SSL *ssl, void *secret, int *secret_len, STACK_OF(SSL_CIPHER) *peer_ciphers, SSL_CIPHER **cipher, void *arg) @@ -1747,12 +1655,9 @@ tor_tls_new(int sock, int isServer) log_warn(LD_NET, "Newly created BIO has read count %lu, write count %lu", result->last_read_count, result->last_write_count); } -#ifdef V2_HANDSHAKE_SERVER if (isServer) { SSL_set_info_callback(result->ssl, tor_tls_server_info_callback); - } else -#endif - { + } else { SSL_set_info_callback(result->ssl, tor_tls_debug_state_callback); } @@ -1791,13 +1696,11 @@ tor_tls_set_renegotiate_callback(tor_tls_t *tls, tls->negotiated_callback = cb; tls->callback_arg = arg; tls->got_renegotiate = 0; -#ifdef V2_HANDSHAKE_SERVER if (cb) { SSL_set_info_callback(tls->ssl, tor_tls_server_info_callback); } else { SSL_set_info_callback(tls->ssl, tor_tls_debug_state_callback); } -#endif } /** If this version of openssl requires it, turn on renegotiation on @@ -1884,7 +1787,6 @@ tor_tls_read,(tor_tls_t *tls, char *cp, size_t len)) tor_assert(len<INT_MAX); r = SSL_read(tls->ssl, cp, (int)len); if (r > 0) { -#ifdef V2_HANDSHAKE_SERVER if (tls->got_renegotiate) { /* Renegotiation happened! */ log_info(LD_NET, "Got a TLS renegotiation from %s", ADDR(tls)); @@ -1892,7 +1794,6 @@ tor_tls_read,(tor_tls_t *tls, char *cp, size_t len)) tls->negotiated_callback(tls, tls->callback_arg); tls->got_renegotiate = 0; } -#endif return r; } err = tor_tls_get_error(tls, r, CATCH_ZERO, "reading", LOG_DEBUG, LD_NET); @@ -1909,10 +1810,10 @@ tor_tls_read,(tor_tls_t *tls, char *cp, size_t len)) /** Total number of bytes that we've used TLS to send. Used to track TLS * overhead. */ -static uint64_t total_bytes_written_over_tls = 0; +STATIC uint64_t total_bytes_written_over_tls = 0; /** Total number of bytes that TLS has put on the network for us. Used to * track TLS overhead. */ -static uint64_t total_bytes_written_by_tls = 0; +STATIC uint64_t total_bytes_written_by_tls = 0; /** Underlying function for TLS writing. Write up to <b>n</b> * characters from <b>cp</b> onto <b>tls</b>. On success, returns the @@ -1957,12 +1858,14 @@ int tor_tls_handshake(tor_tls_t *tls) { int r; - int oldstate; tor_assert(tls); tor_assert(tls->ssl); tor_assert(tls->state == TOR_TLS_ST_HANDSHAKE); + check_no_tls_errors(); - oldstate = SSL_state(tls->ssl); + + OSSL_HANDSHAKE_STATE oldstate = SSL_get_state(tls->ssl); + if (tls->isServer) { log_debug(LD_HANDSHAKE, "About to call SSL_accept on %p (%s)", tls, SSL_state_string_long(tls->ssl)); @@ -1972,7 +1875,10 @@ tor_tls_handshake(tor_tls_t *tls) SSL_state_string_long(tls->ssl)); r = SSL_connect(tls->ssl); } - if (oldstate != SSL_state(tls->ssl)) + + OSSL_HANDSHAKE_STATE newstate = SSL_get_state(tls->ssl); + + if (oldstate != newstate) log_debug(LD_HANDSHAKE, "After call, %p was in state %s", tls, SSL_state_string_long(tls->ssl)); /* We need to call this here and not earlier, since OpenSSL has a penchant @@ -2008,7 +1914,6 @@ tor_tls_finish_handshake(tor_tls_t *tls) SSL_set_info_callback(tls->ssl, NULL); SSL_set_verify(tls->ssl, SSL_VERIFY_PEER, always_accept_verify_cb); SSL_clear_mode(tls->ssl, SSL_MODE_NO_AUTO_CHAIN); -#ifdef V2_HANDSHAKE_SERVER if (tor_tls_client_is_using_v2_ciphers(tls->ssl)) { /* This check is redundant, but back when we did it in the callback, * we might have not been able to look up the tor_tls_t if the code @@ -2023,26 +1928,10 @@ tor_tls_finish_handshake(tor_tls_t *tls) } else { tls->wasV2Handshake = 0; } -#endif } else { -#ifdef V2_HANDSHAKE_CLIENT - /* If we got no ID cert, we're a v2 handshake. */ - X509 *cert = SSL_get_peer_certificate(tls->ssl); - STACK_OF(X509) *chain = SSL_get_peer_cert_chain(tls->ssl); - int n_certs = sk_X509_num(chain); - if (n_certs > 1 || (n_certs == 1 && cert != sk_X509_value(chain, 0))) { - log_debug(LD_HANDSHAKE, "Server sent back multiple certificates; it " - "looks like a v1 handshake on %p", tls); - tls->wasV2Handshake = 0; - } else { - log_debug(LD_HANDSHAKE, - "Server sent back a single certificate; looks like " - "a v2 handshake on %p.", tls); - tls->wasV2Handshake = 1; - } - if (cert) - X509_free(cert); -#endif + /* Client-side */ + tls->wasV2Handshake = 1; + /* XXXX this can move, probably? -NM */ if (SSL_set_cipher_list(tls->ssl, SERVER_CIPHER_LIST) == 0) { tls_log_errors(NULL, LOG_WARN, LD_HANDSHAKE, "re-setting ciphers"); r = TOR_TLS_ERROR_MISC; @@ -2052,52 +1941,6 @@ tor_tls_finish_handshake(tor_tls_t *tls) return r; } -#ifdef USE_BUFFEREVENTS -/** Put <b>tls</b>, which must be a client connection, into renegotiation - * mode. */ -int -tor_tls_start_renegotiating(tor_tls_t *tls) -{ - int r = SSL_renegotiate(tls->ssl); - if (r <= 0) { - return tor_tls_get_error(tls, r, 0, "renegotiating", LOG_WARN, - LD_HANDSHAKE); - } - return 0; -} -#endif - -/** Client only: Renegotiate a TLS session. When finished, returns - * TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD, or - * TOR_TLS_WANTWRITE. - */ -int -tor_tls_renegotiate(tor_tls_t *tls) -{ - int r; - tor_assert(tls); - /* We could do server-initiated renegotiation too, but that would be tricky. - * Instead of "SSL_renegotiate, then SSL_do_handshake until done" */ - tor_assert(!tls->isServer); - - check_no_tls_errors(); - if (tls->state != TOR_TLS_ST_RENEGOTIATE) { - int r = SSL_renegotiate(tls->ssl); - if (r <= 0) { - return tor_tls_get_error(tls, r, 0, "renegotiating", LOG_WARN, - LD_HANDSHAKE); - } - tls->state = TOR_TLS_ST_RENEGOTIATE; - } - r = SSL_do_handshake(tls->ssl); - if (r == 1) { - tls->state = TOR_TLS_ST_OPEN; - return TOR_TLS_DONE; - } else - return tor_tls_get_error(tls, r, 0, "renegotiating handshake", LOG_INFO, - LD_HANDSHAKE); -} - /** Shut down an open tls connection <b>tls</b>. When finished, returns * TOR_TLS_DONE. On failure, returns TOR_TLS_ERROR, TOR_TLS_WANTREAD, * or TOR_TLS_WANTWRITE. @@ -2251,15 +2094,14 @@ log_cert_lifetime(int severity, const X509 *cert, const char *problem) * * Note that a reference is added to cert_out, so it needs to be * freed. id_cert_out doesn't. */ -static void -try_to_extract_certs_from_tls(int severity, tor_tls_t *tls, - X509 **cert_out, X509 **id_cert_out) +MOCK_IMPL(STATIC void, +try_to_extract_certs_from_tls,(int severity, tor_tls_t *tls, + X509 **cert_out, X509 **id_cert_out)) { X509 *cert = NULL, *id_cert = NULL; STACK_OF(X509) *chain = NULL; int num_in_chain, i; *cert_out = *id_cert_out = NULL; - if (!(cert = SSL_get_peer_certificate(tls->ssl))) return; *cert_out = cert; @@ -2476,114 +2318,7 @@ check_no_tls_errors_(const char *fname, int line) int tor_tls_used_v1_handshake(tor_tls_t *tls) { -#if defined(V2_HANDSHAKE_SERVER) && defined(V2_HANDSHAKE_CLIENT) return ! tls->wasV2Handshake; -#else - if (tls->isServer) { -# ifdef V2_HANDSHAKE_SERVER - return ! tls->wasV2Handshake; -# endif - } else { -# ifdef V2_HANDSHAKE_CLIENT - return ! tls->wasV2Handshake; -# endif - } - return 1; -#endif -} - -/** Return true iff <b>name</b> is a DN of a kind that could only - * occur in a v3-handshake-indicating certificate */ -static int -dn_indicates_v3_cert(X509_NAME *name) -{ -#ifdef DISABLE_V3_LINKPROTO_CLIENTSIDE - (void)name; - return 0; -#else - X509_NAME_ENTRY *entry; - int n_entries; - ASN1_OBJECT *obj; - ASN1_STRING *str; - unsigned char *s; - int len, r; - - n_entries = X509_NAME_entry_count(name); - if (n_entries != 1) - return 1; /* More than one entry in the DN. */ - entry = X509_NAME_get_entry(name, 0); - - obj = X509_NAME_ENTRY_get_object(entry); - if (OBJ_obj2nid(obj) != OBJ_txt2nid("commonName")) - return 1; /* The entry isn't a commonName. */ - - str = X509_NAME_ENTRY_get_data(entry); - len = ASN1_STRING_to_UTF8(&s, str); - if (len < 0) - return 0; - if (len < 4) { - OPENSSL_free(s); - return 1; - } - r = fast_memneq(s + len - 4, ".net", 4); - OPENSSL_free(s); - return r; -#endif -} - -/** Return true iff the peer certificate we're received on <b>tls</b> - * indicates that this connection should use the v3 (in-protocol) - * authentication handshake. - * - * Only the connection initiator should use this, and only once the initial - * handshake is done; the responder detects a v1 handshake by cipher types, - * and a v3/v2 handshake by Versions cell vs renegotiation. - */ -int -tor_tls_received_v3_certificate(tor_tls_t *tls) -{ - check_no_tls_errors(); - - X509 *cert = SSL_get_peer_certificate(tls->ssl); - EVP_PKEY *key = NULL; - X509_NAME *issuer_name, *subject_name; - int is_v3 = 0; - - if (!cert) { - log_warn(LD_BUG, "Called on a connection with no peer certificate"); - goto done; - } - - subject_name = X509_get_subject_name(cert); - issuer_name = X509_get_issuer_name(cert); - - if (X509_name_cmp(subject_name, issuer_name) == 0) { - is_v3 = 1; /* purportedly self signed */ - goto done; - } - - if (dn_indicates_v3_cert(subject_name) || - dn_indicates_v3_cert(issuer_name)) { - is_v3 = 1; /* DN is fancy */ - goto done; - } - - key = X509_get_pubkey(cert); - if (EVP_PKEY_bits(key) != 1024 || - EVP_PKEY_type(key->type) != EVP_PKEY_RSA) { - is_v3 = 1; /* Key is fancy */ - goto done; - } - - done: - tls_log_errors(tls, LOG_WARN, LD_NET, "checking for a v3 cert"); - - if (key) - EVP_PKEY_free(key); - if (cert) - X509_free(cert); - - return is_v3; } /** Return the number of server handshakes that we've noticed doing on @@ -2629,7 +2364,7 @@ SSL_get_server_random(SSL *s, uint8_t *out, size_t len) #endif #ifndef HAVE_SSL_SESSION_GET_MASTER_KEY -static size_t +STATIC size_t SSL_SESSION_get_master_key(SSL_SESSION *s, uint8_t *out, size_t len) { tor_assert(s); @@ -2652,7 +2387,6 @@ tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)) #define TLSSECRET_MAGIC "Tor V3 handshake TLS cross-certification" uint8_t buf[128]; size_t len; - tor_assert(tls); SSL *const ssl = tls->ssl; @@ -2676,12 +2410,14 @@ tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)) size_t r = SSL_get_client_random(ssl, buf, client_random_len); tor_assert(r == client_random_len); } + { size_t r = SSL_get_server_random(ssl, buf+client_random_len, server_random_len); tor_assert(r == server_random_len); } + uint8_t *master_key = tor_malloc_zero(master_key_len); { size_t r = SSL_SESSION_get_master_key(session, master_key, master_key_len); diff --git a/src/common/tortls.h b/src/common/tortls.h index 124b77160f..6a4ef9aebe 100644 --- a/src/common/tortls.h +++ b/src/common/tortls.h @@ -12,6 +12,7 @@ **/ #include "crypto.h" +#include "compat_openssl.h" #include "compat.h" #include "testsupport.h" @@ -51,6 +52,119 @@ typedef struct tor_x509_cert_t tor_x509_cert_t; case TOR_TLS_ERROR_IO #define TOR_TLS_IS_ERROR(rv) ((rv) < TOR_TLS_CLOSE) + +#ifdef TORTLS_PRIVATE +#define TOR_TLS_MAGIC 0x71571571 + +typedef enum { + TOR_TLS_ST_HANDSHAKE, TOR_TLS_ST_OPEN, TOR_TLS_ST_GOTCLOSE, + TOR_TLS_ST_SENTCLOSE, TOR_TLS_ST_CLOSED, TOR_TLS_ST_RENEGOTIATE, + TOR_TLS_ST_BUFFEREVENT +} tor_tls_state_t; +#define tor_tls_state_bitfield_t ENUM_BF(tor_tls_state_t) + +/** Holds a SSL_CTX object and related state used to configure TLS + * connections. + */ +typedef struct tor_tls_context_t { + int refcnt; + SSL_CTX *ctx; + tor_x509_cert_t *my_link_cert; + tor_x509_cert_t *my_id_cert; + tor_x509_cert_t *my_auth_cert; + crypto_pk_t *link_key; + crypto_pk_t *auth_key; +} tor_tls_context_t; + +/** Structure that we use for a single certificate. */ +struct tor_x509_cert_t { + X509 *cert; + uint8_t *encoded; + size_t encoded_len; + unsigned pkey_digests_set : 1; + digests_t cert_digests; + digests_t pkey_digests; +}; + +/** Holds a SSL object and its associated data. Members are only + * accessed from within tortls.c. + */ +struct tor_tls_t { + uint32_t magic; + tor_tls_context_t *context; /** A link to the context object for this tls. */ + SSL *ssl; /**< An OpenSSL SSL object. */ + int socket; /**< The underlying file descriptor for this TLS connection. */ + char *address; /**< An address to log when describing this connection. */ + tor_tls_state_bitfield_t state : 3; /**< The current SSL state, + * depending on which operations + * have completed successfully. */ + unsigned int isServer:1; /**< True iff this is a server-side connection */ + unsigned int wasV2Handshake:1; /**< True iff the original handshake for + * this connection used the updated version + * of the connection protocol (client sends + * different cipher list, server sends only + * one certificate). */ + /** True iff we should call negotiated_callback when we're done reading. */ + unsigned int got_renegotiate:1; + /** Return value from tor_tls_classify_client_ciphers, or 0 if we haven't + * called that function yet. */ + int8_t client_cipher_list_type; + /** Incremented every time we start the server side of a handshake. */ + uint8_t server_handshake_count; + size_t wantwrite_n; /**< 0 normally, >0 if we returned wantwrite last + * time. */ + /** Last values retrieved from BIO_number_read()/write(); see + * tor_tls_get_n_raw_bytes() for usage. + */ + unsigned long last_write_count; + unsigned long last_read_count; + /** If set, a callback to invoke whenever the client tries to renegotiate + * the handshake. */ + void (*negotiated_callback)(tor_tls_t *tls, void *arg); + /** Argument to pass to negotiated_callback. */ + void *callback_arg; +}; + +STATIC int tor_errno_to_tls_error(int e); +STATIC int tor_tls_get_error(tor_tls_t *tls, int r, int extra, + const char *doing, int severity, int domain); +STATIC tor_tls_t *tor_tls_get_by_ssl(const SSL *ssl); +STATIC void tor_tls_allocate_tor_tls_object_ex_data_index(void); +STATIC int always_accept_verify_cb(int preverify_ok, X509_STORE_CTX *x509_ctx); +STATIC int tor_tls_classify_client_ciphers(const SSL *ssl, + STACK_OF(SSL_CIPHER) *peer_ciphers); +STATIC int tor_tls_client_is_using_v2_ciphers(const SSL *ssl); +MOCK_DECL(STATIC void, try_to_extract_certs_from_tls, + (int severity, tor_tls_t *tls, X509 **cert_out, X509 **id_cert_out)); +#ifndef HAVE_SSL_SESSION_GET_MASTER_KEY +STATIC size_t SSL_SESSION_get_master_key(SSL_SESSION *s, uint8_t *out, + size_t len); +#endif +STATIC void tor_tls_debug_state_callback(const SSL *ssl, int type, int val); +STATIC void tor_tls_server_info_callback(const SSL *ssl, int type, int val); +STATIC int tor_tls_session_secret_cb(SSL *ssl, void *secret, + int *secret_len, + STACK_OF(SSL_CIPHER) *peer_ciphers, + SSL_CIPHER **cipher, void *arg); +STATIC int find_cipher_by_id(const SSL *ssl, const SSL_METHOD *m, + uint16_t cipher); +MOCK_DECL(STATIC X509*, tor_tls_create_certificate,(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime)); +STATIC tor_tls_context_t *tor_tls_context_new(crypto_pk_t *identity, + unsigned int key_lifetime, unsigned flags, int is_client); +MOCK_DECL(STATIC tor_x509_cert_t *, tor_x509_cert_new,(X509 *x509_cert)); +STATIC int tor_tls_context_init_one(tor_tls_context_t **ppcontext, + crypto_pk_t *identity, + unsigned int key_lifetime, + unsigned int flags, + int is_client); +STATIC void tls_log_errors(tor_tls_t *tls, int severity, int domain, + const char *doing); +#endif + const char *tor_tls_err_to_string(int err); void tor_tls_get_state_description(tor_tls_t *tls, char *buf, size_t sz); @@ -81,7 +195,6 @@ MOCK_DECL(int, tor_tls_read, (tor_tls_t *tls, char *cp, size_t len)); int tor_tls_write(tor_tls_t *tls, const char *cp, size_t n); int tor_tls_handshake(tor_tls_t *tls); int tor_tls_finish_handshake(tor_tls_t *tls); -int tor_tls_renegotiate(tor_tls_t *tls); void tor_tls_unblock_renegotiation(tor_tls_t *tls); void tor_tls_block_renegotiation(tor_tls_t *tls); void tor_tls_assert_renegotiation_unblocked(tor_tls_t *tls); @@ -99,7 +212,6 @@ int tor_tls_get_buffer_sizes(tor_tls_t *tls, MOCK_DECL(double, tls_get_write_overhead_ratio, (void)); int tor_tls_used_v1_handshake(tor_tls_t *tls); -int tor_tls_received_v3_certificate(tor_tls_t *tls); int tor_tls_get_num_server_handshakes(tor_tls_t *tls); int tor_tls_server_got_renegotiate(tor_tls_t *tls); MOCK_DECL(int,tor_tls_get_tlssecrets,(tor_tls_t *tls, uint8_t *secrets_out)); diff --git a/src/common/util.c b/src/common/util.c index b33c80fd45..04f48a4eee 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -207,7 +207,7 @@ tor_malloc_zero_(size_t size DMALLOC_PARAMS) #define SQRT_SIZE_MAX_P1 (((size_t)1) << (sizeof(size_t)*4)) /** Return non-zero if and only if the product of the arguments is exact. */ -static INLINE int +static inline int size_mul_check(const size_t x, const size_t y) { /* This first check is equivalent to @@ -488,42 +488,58 @@ round_to_power_of_2(uint64_t u64) } /** Return the lowest x such that x is at least <b>number</b>, and x modulo - * <b>divisor</b> == 0. */ + * <b>divisor</b> == 0. If no such x can be expressed as an unsigned, return + * UINT_MAX */ unsigned round_to_next_multiple_of(unsigned number, unsigned divisor) { + tor_assert(divisor > 0); + if (UINT_MAX - divisor + 1 < number) + return UINT_MAX; number += divisor - 1; number -= number % divisor; return number; } /** Return the lowest x such that x is at least <b>number</b>, and x modulo - * <b>divisor</b> == 0. */ + * <b>divisor</b> == 0. If no such x can be expressed as a uint32_t, return + * UINT32_MAX */ uint32_t round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor) { + tor_assert(divisor > 0); + if (UINT32_MAX - divisor + 1 < number) + return UINT32_MAX; + number += divisor - 1; number -= number % divisor; return number; } /** Return the lowest x such that x is at least <b>number</b>, and x modulo - * <b>divisor</b> == 0. */ + * <b>divisor</b> == 0. If no such x can be expressed as a uint64_t, return + * UINT64_MAX */ uint64_t round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor) { + tor_assert(divisor > 0); + if (UINT64_MAX - divisor + 1 < number) + return UINT64_MAX; number += divisor - 1; number -= number % divisor; return number; } /** Return the lowest x in [INT64_MIN, INT64_MAX] such that x is at least - * <b>number</b>, and x modulo <b>divisor</b> == 0. */ + * <b>number</b>, and x modulo <b>divisor</b> == 0. If no such x can be + * expressed as an int64_t, return INT64_MAX */ int64_t round_int64_to_next_multiple_of(int64_t number, int64_t divisor) { tor_assert(divisor > 0); - if (number >= 0 && INT64_MAX - divisor + 1 >= number) + if (INT64_MAX - divisor + 1 < number) + return INT64_MAX; + if (number >= 0) number += divisor - 1; number -= number % divisor; return number; @@ -537,33 +553,44 @@ int64_t sample_laplace_distribution(double mu, double b, double p) { double result; - tor_assert(p >= 0.0 && p < 1.0); + /* This is the "inverse cumulative distribution function" from: * http://en.wikipedia.org/wiki/Laplace_distribution */ - result = mu - b * (p > 0.5 ? 1.0 : -1.0) - * tor_mathlog(1.0 - 2.0 * fabs(p - 0.5)); - - if (result >= INT64_MAX) - return INT64_MAX; - else if (result <= INT64_MIN) + if (p <= 0.0) { + /* Avoid taking log(0.0) == -INFINITY, as some processors or compiler + * options can cause the program to trap. */ return INT64_MIN; - else - return (int64_t) result; + } + + result = mu - b * (p > 0.5 ? 1.0 : -1.0) + * tor_mathlog(1.0 - 2.0 * fabs(p - 0.5)); + + return clamp_double_to_int64(result); } -/** Add random noise between INT64_MIN and INT64_MAX coming from a - * Laplace distribution with mu = 0 and b = <b>delta_f</b>/<b>epsilon</b> - * to <b>signal</b> based on the provided <b>random</b> value in - * [0.0, 1.0[. */ +/** Add random noise between INT64_MIN and INT64_MAX coming from a Laplace + * distribution with mu = 0 and b = <b>delta_f</b>/<b>epsilon</b> to + * <b>signal</b> based on the provided <b>random</b> value in [0.0, 1.0[. + * The epsilon value must be between ]0.0, 1.0]. delta_f must be greater + * than 0. */ int64_t add_laplace_noise(int64_t signal, double random, double delta_f, double epsilon) { - int64_t noise = sample_laplace_distribution( - 0.0, /* just add noise, no further signal */ - delta_f / epsilon, random); + int64_t noise; + + /* epsilon MUST be between ]0.0, 1.0] */ + tor_assert(epsilon > 0.0 && epsilon <= 1.0); + /* delta_f MUST be greater than 0. */ + tor_assert(delta_f > 0.0); + /* Just add noise, no further signal */ + noise = sample_laplace_distribution(0.0, + delta_f / epsilon, + random); + + /* Clip (signal + noise) to [INT64_MIN, INT64_MAX] */ if (noise > 0 && INT64_MAX - noise < signal) return INT64_MAX; else if (noise < 0 && INT64_MIN - noise > signal) @@ -2116,7 +2143,7 @@ check_private_dir(const char *dirname, cpd_check_t check, return -1; } if ( (check & (CPD_GROUP_OK|CPD_GROUP_READ)) - && (st.st_gid != running_gid) ) { + && (st.st_gid != running_gid) && (st.st_gid != 0)) { struct group *gr; char *process_groupname = NULL; gr = getgrgid(running_gid); @@ -4424,7 +4451,7 @@ tor_get_exit_code(process_handle_t *process_handle, /** Helper: return the number of characters in <b>s</b> preceding the first * occurrence of <b>ch</b>. If <b>ch</b> does not occur in <b>s</b>, return * the length of <b>s</b>. Should be equivalent to strspn(s, "ch"). */ -static INLINE size_t +static inline size_t str_num_before(const char *s, char ch) { const char *cp = strchr(s, ch); @@ -5385,3 +5412,38 @@ tor_weak_random_range(tor_weak_rng_t *rng, int32_t top) return result; } +/** Cast a given double value to a int64_t. Return 0 if number is NaN. + * Returns either INT64_MIN or INT64_MAX if number is outside of the int64_t + * range. */ +int64_t +clamp_double_to_int64(double number) +{ + int exp; + + /* NaN is a special case that can't be used with the logic below. */ + if (isnan(number)) { + return 0; + } + + /* Time to validate if result can overflows a int64_t value. Fun with + * float! Find that exponent exp such that + * number == x * 2^exp + * for some x with abs(x) in [0.5, 1.0). Note that this implies that the + * magnitude of number is strictly less than 2^exp. + * + * If number is infinite, the call to frexp is legal but the contents of + * exp are unspecified. */ + frexp(number, &exp); + + /* If the magnitude of number is strictly less than 2^63, the truncated + * version of number is guaranteed to be representable. The only + * representable integer for which this is not the case is INT64_MIN, but + * it is covered by the logic below. */ + if (isfinite(number) && exp <= 63) { + return number; + } + + /* Handle infinities and finite numbers with magnitude >= 2^63. */ + return signbit(number) ? INT64_MIN : INT64_MAX; +} + diff --git a/src/common/util.h b/src/common/util.h index 8bb4505e86..165bc0dcb3 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -185,6 +185,7 @@ int64_t sample_laplace_distribution(double mu, double b, double p); int64_t add_laplace_noise(int64_t signal, double random, double delta_f, double epsilon); int n_bits_set_u8(uint8_t v); +int64_t clamp_double_to_int64(double number); /* Compute the CEIL of <b>a</b> divided by <b>b</b>, for nonnegative <b>a</b> * and positive <b>b</b>. Works on integer types only. Not defined if a+b can diff --git a/src/common/util_format.c b/src/common/util_format.c index dc544a6c2e..8d99138506 100644 --- a/src/common/util_format.c +++ b/src/common/util_format.c @@ -465,7 +465,7 @@ base16_encode(char *dest, size_t destlen, const char *src, size_t srclen) } /** Helper: given a hex digit, return its value, or -1 if it isn't hex. */ -static INLINE int +static inline int hex_decode_digit_(char c) { switch (c) { diff --git a/src/common/util_process.c b/src/common/util_process.c index 849a5c0b63..1e3b02cc9a 100644 --- a/src/common/util_process.c +++ b/src/common/util_process.c @@ -45,13 +45,13 @@ struct waitpid_callback_t { unsigned running; }; -static INLINE unsigned int +static inline unsigned int process_map_entry_hash_(const waitpid_callback_t *ent) { return (unsigned) ent->pid; } -static INLINE unsigned int +static inline unsigned int process_map_entries_eq_(const waitpid_callback_t *a, const waitpid_callback_t *b) { diff --git a/src/ext/README b/src/ext/README index 5501aba758..7ce1bc3b74 100644 --- a/src/ext/README +++ b/src/ext/README @@ -65,6 +65,10 @@ ed25519/donna/* Andrew Moon's semi-portable ed25519-donna implementation of ed25519. Public domain. +keccak-tiny/ + + David Leon Gil's portable Keccak implementation. CC0. + readpassphrase.[ch] Portable readpassphrase implementation from OpenSSH portable, version diff --git a/src/ext/csiphash.c b/src/ext/csiphash.c index 27c5358ebe..b60f73a7ff 100644 --- a/src/ext/csiphash.c +++ b/src/ext/csiphash.c @@ -97,65 +97,48 @@ #endif uint64_t siphash24(const void *src, unsigned long src_sz, const struct sipkey *key) { + const uint8_t *m = src; uint64_t k0 = key->k0; uint64_t k1 = key->k1; - uint64_t b = (uint64_t)src_sz << 56; -#ifdef UNALIGNED_OK - const uint64_t *in = (uint64_t*)src; -#else - /* On platforms where alignment matters, if 'in' is a pointer to a - * datatype that must be aligned, the compiler is allowed to - * generate code that assumes that it is aligned as such. - */ - const uint8_t *in = (uint8_t *)src; -#endif - - uint64_t t; - uint8_t *pt, *m; + uint64_t last7 = (uint64_t)(src_sz & 0xff) << 56; + size_t i, blocks; uint64_t v0 = k0 ^ 0x736f6d6570736575ULL; uint64_t v1 = k1 ^ 0x646f72616e646f6dULL; uint64_t v2 = k0 ^ 0x6c7967656e657261ULL; uint64_t v3 = k1 ^ 0x7465646279746573ULL; - while (src_sz >= 8) { + for (i = 0, blocks = (src_sz & ~7); i < blocks; i+= 8) { #ifdef UNALIGNED_OK - uint64_t mi = _le64toh(*in); - in += 1; + uint64_t mi = _le64toh(*(m + i)); #else uint64_t mi; - memcpy(&mi, in, 8); + memcpy(&mi, m + i, 8); mi = _le64toh(mi); - in += 8; #endif - src_sz -= 8; v3 ^= mi; DOUBLE_ROUND(v0,v1,v2,v3); v0 ^= mi; } - t = 0; pt = (uint8_t*)&t; m = (uint8_t*)in; - switch (src_sz) { - case 7: pt[6] = m[6]; - case 6: pt[5] = m[5]; - case 5: pt[4] = m[4]; -#ifdef UNALIGNED_OK - case 4: *((uint32_t*)&pt[0]) = *((uint32_t*)&m[0]); break; -#else - case 4: pt[3] = m[3]; -#endif - case 3: pt[2] = m[2]; - case 2: pt[1] = m[1]; - case 1: pt[0] = m[0]; + switch (src_sz - blocks) { + case 7: last7 |= (uint64_t)m[i + 6] << 48; + case 6: last7 |= (uint64_t)m[i + 5] << 40; + case 5: last7 |= (uint64_t)m[i + 4] << 32; + case 4: last7 |= (uint64_t)m[i + 3] << 24; + case 3: last7 |= (uint64_t)m[i + 2] << 16; + case 2: last7 |= (uint64_t)m[i + 1] << 8; + case 1: last7 |= (uint64_t)m[i + 0] ; + case 0: + default:; } - b |= _le64toh(t); - - v3 ^= b; + v3 ^= last7; DOUBLE_ROUND(v0,v1,v2,v3); - v0 ^= b; v2 ^= 0xff; + v0 ^= last7; + v2 ^= 0xff; DOUBLE_ROUND(v0,v1,v2,v3); DOUBLE_ROUND(v0,v1,v2,v3); - return (v0 ^ v1) ^ (v2 ^ v3); + return v0 ^ v1 ^ v2 ^ v3; } diff --git a/src/ext/ed25519/donna/ed25519_tor.c b/src/ext/ed25519/donna/ed25519_tor.c index 12493f7d14..ac726ba045 100644 --- a/src/ext/ed25519/donna/ed25519_tor.c +++ b/src/ext/ed25519/donna/ed25519_tor.c @@ -148,8 +148,7 @@ ed25519_donna_seckey(unsigned char *sk) { ed25519_secret_key seed; - if (crypto_strongest_rand(seed, 32)) - return -1; + crypto_strongest_rand(seed, 32); ed25519_extsk(sk, seed); diff --git a/src/ext/ed25519/ref10/randombytes.h b/src/ext/ed25519/ref10/randombytes.h index fc709fcefc..8bf31631f0 100644 --- a/src/ext/ed25519/ref10/randombytes.h +++ b/src/ext/ed25519/ref10/randombytes.h @@ -1,4 +1,4 @@ /* Added for Tor. */ #include "crypto.h" #define randombytes(b, n) \ - (crypto_strongest_rand((b), (n))) + (crypto_strongest_rand((b), (n)), 0) diff --git a/src/ext/eventdns.c b/src/ext/eventdns.c index a0c7ff29fa..37d8a7a3df 100644 --- a/src/ext/eventdns.c +++ b/src/ext/eventdns.c @@ -805,7 +805,7 @@ reply_handle(struct evdns_request *const req, u16 flags, u32 ttl, struct reply * } } -static INLINE int +static inline int name_parse(u8 *packet, int length, int *idx, char *name_out, size_t name_out_len) { int name_end = -1; int j = *idx; diff --git a/src/ext/ht.h b/src/ext/ht.h index 19a67a6a41..28d1fe49d5 100644 --- a/src/ext/ht.h +++ b/src/ext/ht.h @@ -61,7 +61,7 @@ #define HT_INIT(name, head) name##_HT_INIT(head) #define HT_REP_IS_BAD_(name, head) name##_HT_REP_IS_BAD_(head) /* Helper: */ -static INLINE unsigned +static inline unsigned ht_improve_hash(unsigned h) { /* Aim to protect against poor hash functions by adding logic here @@ -75,7 +75,7 @@ ht_improve_hash(unsigned h) #if 0 /** Basic string hash function, from Java standard String.hashCode(). */ -static INLINE unsigned +static inline unsigned ht_string_hash(const char *s) { unsigned h = 0; @@ -90,7 +90,7 @@ ht_string_hash(const char *s) #if 0 /** Basic string hash function, from Python's str.__hash__() */ -static INLINE unsigned +static inline unsigned ht_string_hash(const char *s) { unsigned h; @@ -143,7 +143,7 @@ ht_string_hash(const char *s) int name##_HT_GROW(struct name *ht, unsigned min_capacity); \ void name##_HT_CLEAR(struct name *ht); \ int name##_HT_REP_IS_BAD_(const struct name *ht); \ - static INLINE void \ + static inline void \ name##_HT_INIT(struct name *head) { \ head->hth_table_length = 0; \ head->hth_table = NULL; \ @@ -153,7 +153,7 @@ ht_string_hash(const char *s) } \ /* Helper: returns a pointer to the right location in the table \ * 'head' to find or insert the element 'elm'. */ \ - static INLINE struct type ** \ + static inline struct type ** \ name##_HT_FIND_P_(struct name *head, struct type *elm) \ { \ struct type **p; \ @@ -169,7 +169,7 @@ ht_string_hash(const char *s) } \ /* Return a pointer to the element in the table 'head' matching 'elm', \ * or NULL if no such element exists */ \ - ATTR_UNUSED static INLINE struct type * \ + ATTR_UNUSED static inline struct type * \ name##_HT_FIND(const struct name *head, struct type *elm) \ { \ struct type **p; \ @@ -180,7 +180,7 @@ ht_string_hash(const char *s) } \ /* Insert the element 'elm' into the table 'head'. Do not call this \ * function if the table might already contain a matching element. */ \ - ATTR_UNUSED static INLINE void \ + ATTR_UNUSED static inline void \ name##_HT_INSERT(struct name *head, struct type *elm) \ { \ struct type **p; \ @@ -195,7 +195,7 @@ ht_string_hash(const char *s) /* Insert the element 'elm' into the table 'head'. If there already \ * a matching element in the table, replace that element and return \ * it. */ \ - ATTR_UNUSED static INLINE struct type * \ + ATTR_UNUSED static inline struct type * \ name##_HT_REPLACE(struct name *head, struct type *elm) \ { \ struct type **p, *r; \ @@ -216,7 +216,7 @@ ht_string_hash(const char *s) } \ /* Remove any element matching 'elm' from the table 'head'. If such \ * an element is found, return it; otherwise return NULL. */ \ - ATTR_UNUSED static INLINE struct type * \ + ATTR_UNUSED static inline struct type * \ name##_HT_REMOVE(struct name *head, struct type *elm) \ { \ struct type **p, *r; \ @@ -234,7 +234,7 @@ ht_string_hash(const char *s) * using 'data' as its second argument. If the function returns \ * nonzero, remove the most recently examined element before invoking \ * the function again. */ \ - ATTR_UNUSED static INLINE void \ + ATTR_UNUSED static inline void \ name##_HT_FOREACH_FN(struct name *head, \ int (*fn)(struct type *, void *), \ void *data) \ @@ -260,7 +260,7 @@ ht_string_hash(const char *s) /* Return a pointer to the first element in the table 'head', under \ * an arbitrary order. This order is stable under remove operations, \ * but not under others. If the table is empty, return NULL. */ \ - ATTR_UNUSED static INLINE struct type ** \ + ATTR_UNUSED static inline struct type ** \ name##_HT_START(struct name *head) \ { \ unsigned b = 0; \ @@ -279,7 +279,7 @@ ht_string_hash(const char *s) * NULL. If 'elm' is to be removed from the table, you must call \ * this function for the next value before you remove it. \ */ \ - ATTR_UNUSED static INLINE struct type ** \ + ATTR_UNUSED static inline struct type ** \ name##_HT_NEXT(struct name *head, struct type **elm) \ { \ if ((*elm)->field.hte_next) { \ @@ -299,7 +299,7 @@ ht_string_hash(const char *s) return NULL; \ } \ } \ - ATTR_UNUSED static INLINE struct type ** \ + ATTR_UNUSED static inline struct type ** \ name##_HT_NEXT_RMV(struct name *head, struct type **elm) \ { \ unsigned h = HT_ELT_HASH_(*elm, field, hashfn); \ diff --git a/src/ext/include.am b/src/ext/include.am index 47d4c03d0b..bf678f2c9d 100644 --- a/src/ext/include.am +++ b/src/ext/include.am @@ -135,3 +135,16 @@ noinst_HEADERS += $(ED25519_DONNA_HDRS) LIBED25519_DONNA=src/ext/ed25519/donna/libed25519_donna.a noinst_LIBRARIES += $(LIBED25519_DONNA) +src_ext_keccak_tiny_libkeccak_tiny_a_CFLAGS= + +src_ext_keccak_tiny_libkeccak_tiny_a_SOURCES= \ + src/ext/keccak-tiny/keccak-tiny-unrolled.c + +LIBKECCAK_TINY_HDRS = \ + src/ext/keccak-tiny/keccak-tiny.h + +noinst_HEADERS += $(LIBKECCAK_TINY_HDRS) + +LIBKECCAK_TINY=src/ext/keccak-tiny/libkeccak-tiny.a +noinst_LIBRARIES += $(LIBKECCAK_TINY) + diff --git a/src/ext/keccak-tiny/README.markdown b/src/ext/keccak-tiny/README.markdown new file mode 100644 index 0000000000..784d6f6bdb --- /dev/null +++ b/src/ext/keccak-tiny/README.markdown @@ -0,0 +1,82 @@ +# libkeccak-tiny + +An implementation of the FIPS-202-defined SHA-3 and SHAKE functions +in 120 cloc (156 lines). One C file, one header. + +The `Keccak-f[1600]` permutation is fully unrolled; it's nearly as fast +as the Keccak team's optimized permutation. + +## Building + + > clang -O3 -march=native -std=c11 -Wextra -dynamic -shared keccak-tiny.c -o libkeccak-tiny.dylib + +If you don't have a modern libc that includes the `memset_s` function, +you can just add `-D"memset_s(W,WL,V,OL)=memset(W,V,OL)` to the command +line. + +## Using + +Build the library, include the header, and do, e.g., + + shake256(out, 256, in, inlen); + +That's it. + +(Note: You can request less output from the fixed-output-length +functions, but not more.) + +## TweetShake + +The relevant tweets: + +```C +// @hashbreaker Inspired by TweetNaCl! +// Keccak and SHA-3 are supposedly hard to implement. So, how many tweets does it take to get to the center of a sponge...? +#define decshake(bits) int shake##bits(unsigned char* o, unsigned long, unsigned char*, unsigned long); /*begin keccak.h*/ +#define decsha3(bits) int sha3_##bits(unsigned char*,unsigned long,unsigned char*,unsigned long); +decshake(128) decshake(256) decsha3(224) decsha3(256) decsha3(384) decsha3(512) /*end keccak.h*/ +#define K static const /* Keccak constants: rho rotations, pi lanes, and iota RCs */ /*begin keccak.c*/ +typedef unsigned char byte;typedef byte*bytes;typedef unsigned long z;typedef unsigned long long u8;K u8 V=1ULL<<63;K u8 W=1ULL<<31;/*!gcc*/ +#define V (1ULL<<63) +#define W (1ULL<31) +K byte rho[24]={1,3,6,10,15,21,28,36,45,55,2,14,27,41,56,8,25,43,62,18,39,61,20,44};K u8 RC[24]={1,0x8082,V|0x808a,V|W|0x8000,0x808b,W|1,V|W +|0x8081,V|0x8009,138,136,W|0x8009,W|10,W|0x808b,V|0x8b,V|0x8089,V|0x8003,V|0x8002,V|0x80,0x800a,V|W|0xa,V|W|0x8081,V|0x8080,W|1,V|W|0x8008}; +K byte pi[25]={10,7,11,17,18,3,5,16,8,21,24,4,15,23,19,13,12,2,20,14,22,9,6,1}; /**helpers:*/static inline z min(z a,z b){return (a<b)?a:b;} +#define ROL(x, s) /* rotary shift */ (((x) << s) | ((x) >> (64-s))) /**macros to fully unroll the Keccak-f[1600] permutation:*/ +#define R24(e) /* repeat 24 times */ e e e e e e e e e e e e e e e e e e e e e e e e +#define L5(v,s,e) /* 5-unroll a loop */ v=0; e; v+=s; e; v+=s; e; v+=s; e; v+=s; e; v+=s; /**the permutation:*/ +static inline void keccakf(u8* a){u8 b[5]={0};u8 t=0;byte x,y,i=0; /*24 rounds:*/R24( L5(x,1,b[x]=0;L5(y,5, /*parity*/ b[x] ^= a[x+y])) +L5(x,1,L5(y,5,/*theta*/a[y+x] ^= b[(x+4)%5] ^ ROL(b[(x+1)%5],1))) t=a[1];x=0;R24(b[0]=a[pi[x]];/*rho*/a[pi[x]]=ROL(t, rho[x]);t=b[0];x++;) +L5(y,5,L5(x,1, /*chi*/ b[x] = a[y+x]) L5(x,1, a[y+x] = b[x] ^ ~b[(x+1)%5] & b[(x+2)%5])) /*iota*/ a[0] ^= RC[i]; i++; )} /**keccak-f!**/ +#define FOR(i, ST, L, S) /*obvious*/ do { for (z i = 0; i < L; i += ST) { S; } } while (0) /**now, the sponge construction in hash mode**/ +#define appl(NAME, S) /*macro to define array comprehensions*/ static inline void NAME(bytes dst, bytes src, z len) { FOR(i, 1, len, S); } +/*helpers:*/ static inline void clear(bytes a) { FOR(i,1,200,a[i]=0); } appl(xorin, dst[i] ^= src[i]) appl(set, src[i] = dst[i]) +#define foldP(I, L, F) /* macro to fold app P F */ while (L >= r) { /*apply F*/ F(a, I, r); /*permute*/ keccakf(A); I += r; L -= r; } +static inline int hash(bytes o,z olen,bytes in,z ilen,z r,byte D){ if((o == (void*)0)||((in == (void*)0)&&ilen != 0)||(r >= 200))return -1; +/*absorb*/u8 A[25]={0};bytes a=(bytes)A;/*full blocks*/foldP(in,ilen,xorin);/*last block*/xorin(a,in,ilen);/**ds+padstart*/a[ilen]^=D; +/*padend:*/a[r-1]^=0x80; /**permute**/keccakf(A); /**squeeze:**/foldP(o,olen,set);/*last bytes*/set(a,o,olen);/*done!*/clear(a);return 0;} +#define defshake(bits) int shake##bits(bytes o, z olen, bytes in, z ilen) {return hash(o,olen,in,ilen,200-(bits/4),0x1f);} +#define defsha3(bits) int sha3_##bits(bytes o,z olen,bytes in,z ilen) {return hash(o,min(olen,200-(bits/4)),in,ilen,200-(bits/4),0x06);} +/*define the SHA3 and SHAKE instances:*/defshake(128) defshake(256) defsha3(224) defsha3(256) defsha3(384) defsha3(512)/*end keccak.c*/ +// ...chomp. 24 kinda legible tweets (3232 bytes). And a simple interface: shake256(digest, digestlen, in, inlen) +// Clang recommended. GCC users will need to insert "#define V (1ULL<<63)" and "#define W (1ULL<31)" at the point marked "/*!gcc*/" +// If you're using as a prefix MAC, you MUST replace the body of "clear" with "memset_s(a, 200, 0, 200)" to avoid misoptimization. +// @everyone_who_is_still_using_sha1 Please stop using SHA-1. +// Oh, one more thing: a C11-threaded, memmapped shake256sum in 10 tweets. (Your libc may need a shim for C11 thread support.) +// echo -n string stdio stdint fcntl sys/mman sys/stat sys/types unistd threads|tr ' ' \\n|xargs -n1 -I_ echo '#include <_.h>' +#include "kcksum_tweet.h" +#define E(LABEL, MSG) if (err != 0) { strerror_r(err, serr, 1024); fprintf(stderr, "%s: '%s' %s\n", serr, fn, MSG); goto LABEL;} +static mtx_t iomtx;void h(void* v);void h(void* v){char* fn=(char*)v;int err=0;char serr[1024]={0};/*open file*/int fd=open(fn, O_RDONLY); +err=!fd;E(ret,"couldn't be opened.");/*stat it*/struct stat stat;err=fstat(fd,&stat);E(close,"doesn't exist.");err=!!(stat.st_mode&S_IFDIR); +E(close,"not a regular file.");z length=(size_t)stat.st_size;/*mmap the file*/bytes in=length?mmap(0,length,PROT_READ,MAP_SHARED,fd,0):NULL; +if(length&&(in==MAP_FAILED)){E(close,"mmap-ing failed.");}byte out[64]={0};/*hash it*/shake256(out,64,in,length);length&&munmap(in,length); +/*lock io*/mtx_lock(&iomtx);printf("SHAKE256('%s') = ", fn);FOR(i,1,64,printf("%02x",out[i]));printf("\n");mtx_unlock(&iomtx);/*unlock io*/ +close:close(fd);ret:thrd_exit(err);}int main(int argc,char** argv){int err=0; mtx_init(&iomtx, mtx_plain); thrd_t t[4]; int res[4],i,j,k; +for(i=1;i<argc;i+=4){for(j=0;j<4;j++){if((j+i)==argc){/*out of files*/goto join;} /*spawn*/ thrd_create(t + j,h,argv[i + j]);} +join: for (k = 0; k < j; k++) { /*wait*/ err |= thrd_join(t[k], res + k); err |= res[k];} } mtx_destroy(&iomtx); return err; } /* done! */ +``` + + +## License + +[CC0](http://creativecommons.org/publicdomain/zero/1.0/) diff --git a/src/ext/keccak-tiny/do.sh b/src/ext/keccak-tiny/do.sh new file mode 100644 index 0000000000..cf99f249e7 --- /dev/null +++ b/src/ext/keccak-tiny/do.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env sh +cc=$(which clang-3.6||which gcc-4.9||which clang||||which gcc) +so=$(test -f /etc/asl.conf && printf dylib|| printf so) +$cc "-Dinline=__attribute__((__always_inline__))" -O3 -march=native -std=c11 -Wextra -Wpedantic -Wall -dynamic -shared keccak-tiny.c -o libkeccak-tiny.$so +$cc -Os -march=native -std=c11 -Wextra -Wpedantic -Wall -dynamic -shared keccak-tiny.c -o libkeccak-tiny-small.$so diff --git a/src/ext/keccak-tiny/keccak-tiny-unrolled.c b/src/ext/keccak-tiny/keccak-tiny-unrolled.c new file mode 100644 index 0000000000..4b4f51c7cf --- /dev/null +++ b/src/ext/keccak-tiny/keccak-tiny-unrolled.c @@ -0,0 +1,368 @@ +/** libkeccak-tiny + * + * A single-file implementation of SHA-3 and SHAKE. + * + * Implementor: David Leon Gil + * License: CC0, attribution kindly requested. Blame taken too, + * but not liability. + */ +#include "keccak-tiny.h" + +#include <string.h> +#include "crypto.h" + +/******** The Keccak-f[1600] permutation ********/ + +/*** Constants. ***/ +static const uint8_t rho[24] = \ + { 1, 3, 6, 10, 15, 21, + 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, + 62, 18, 39, 61, 20, 44}; +static const uint8_t pi[24] = \ + {10, 7, 11, 17, 18, 3, + 5, 16, 8, 21, 24, 4, + 15, 23, 19, 13, 12, 2, + 20, 14, 22, 9, 6, 1}; +static const uint64_t RC[24] = \ + {1ULL, 0x8082ULL, 0x800000000000808aULL, 0x8000000080008000ULL, + 0x808bULL, 0x80000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, + 0x8aULL, 0x88ULL, 0x80008009ULL, 0x8000000aULL, + 0x8000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, 0x8000000000008003ULL, + 0x8000000000008002ULL, 0x8000000000000080ULL, 0x800aULL, 0x800000008000000aULL, + 0x8000000080008081ULL, 0x8000000000008080ULL, 0x80000001ULL, 0x8000000080008008ULL}; + +/*** Helper macros to unroll the permutation. ***/ +#define rol(x, s) (((x) << s) | ((x) >> (64 - s))) +#define REPEAT6(e) e e e e e e +#define REPEAT24(e) REPEAT6(e e e e) +#define REPEAT5(e) e e e e e +#define FOR5(v, s, e) \ + v = 0; \ + REPEAT5(e; v += s;) + +/*** Keccak-f[1600] ***/ +static inline void keccakf(void* state) { + uint64_t* a = (uint64_t*)state; + uint64_t b[5] = {0}; + uint64_t t = 0; + uint8_t x, y, i = 0; + + REPEAT24( + // Theta + FOR5(x, 1, + b[x] = 0; + FOR5(y, 5, + b[x] ^= a[x + y]; )) + FOR5(x, 1, + FOR5(y, 5, + a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); )) + // Rho and pi + t = a[1]; + x = 0; + REPEAT24(b[0] = a[pi[x]]; + a[pi[x]] = rol(t, rho[x]); + t = b[0]; + x++; ) + // Chi + FOR5(y, + 5, + FOR5(x, 1, + b[x] = a[y + x];) + FOR5(x, 1, + a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]); )) + // Iota + a[0] ^= RC[i]; + i++; ) +} + +/******** The FIPS202-defined functions. ********/ + +/*** Some helper macros. ***/ + +#define _(S) do { S } while (0) +#define FOR(i, ST, L, S) \ + _(for (size_t i = 0; i < L; i += ST) { S; }) +#define mkapply_ds(NAME, S) \ + static inline void NAME(uint8_t* dst, \ + const uint8_t* src, \ + size_t len) { \ + FOR(i, 1, len, S); \ + } +#define mkapply_sd(NAME, S) \ + static inline void NAME(const uint8_t* src, \ + uint8_t* dst, \ + size_t len) { \ + FOR(i, 1, len, S); \ + } + +mkapply_ds(xorin, dst[i] ^= src[i]) // xorin +mkapply_sd(setout, dst[i] = src[i]) // setout + +#define P keccakf +#define Plen KECCAK_MAX_RATE + +#define KECCAK_DELIM_DIGEST 0x06 +#define KECCAK_DELIM_XOF 0x1f + +// Fold P*F over the full blocks of an input. +#define foldP(I, L, F) \ + while (L >= s->rate) { \ + F(s->a, I, s->rate); \ + P(s->a); \ + I += s->rate; \ + L -= s->rate; \ + } + +static inline void +keccak_absorb_blocks(keccak_state *s, const uint8_t *buf, size_t nr_blocks) +{ + size_t blen = nr_blocks * s->rate; + foldP(buf, blen, xorin); +} + +static int +keccak_update(keccak_state *s, const uint8_t *buf, size_t len) +{ + if (s->finalized) + return -1; + if ((buf == NULL) && len != 0) + return -1; + + size_t remaining = len; + while (remaining > 0) { + if (s->offset == 0) { + const size_t blocks = remaining / s->rate; + size_t direct_bytes = blocks * s->rate; + if (direct_bytes > 0) { + keccak_absorb_blocks(s, buf, blocks); + remaining -= direct_bytes; + buf += direct_bytes; + } + } + + const size_t buf_avail = s->rate - s->offset; + const size_t buf_bytes = (buf_avail > remaining) ? remaining : buf_avail; + if (buf_bytes > 0) { + memcpy(&s->block[s->offset], buf, buf_bytes); + s->offset += buf_bytes; + remaining -= buf_bytes; + buf += buf_bytes; + } + if (s->offset == s->rate) { + keccak_absorb_blocks(s, s->block, 1); + s->offset = 0; + } + } + return 0; +} + +static void +keccak_finalize(keccak_state *s) +{ + // Xor in the DS and pad frame. + s->a[s->offset] ^= s->delim; + s->a[s->rate - 1] ^= 0x80; + // Xor in the last block. + xorin(s->a, s->block, s->offset); + + memwipe(s->block, 0, sizeof(s->block)); + s->finalized = 1; + s->offset = s->rate; +} + +static inline void +keccak_squeeze_blocks(keccak_state *s, uint8_t *out, size_t nr_blocks) +{ + for (size_t n = 0; n < nr_blocks; n++) { + keccakf(s->a); + setout(s->a, out, s->rate); + out += s->rate; + } +} + +static int +keccak_squeeze(keccak_state *s, uint8_t *out, size_t outlen) +{ + if (!s->finalized) + return -1; + + size_t remaining = outlen; + while (remaining > 0) { + if (s->offset == s->rate) { + const size_t blocks = remaining / s->rate; + const size_t direct_bytes = blocks * s->rate; + if (blocks > 0) { + keccak_squeeze_blocks(s, out, blocks); + out += direct_bytes; + remaining -= direct_bytes; + } + + if (remaining > 0) { + keccak_squeeze_blocks(s, s->block, 1); + s->offset = 0; + } + } + + const size_t buf_bytes = s->rate - s->offset; + const size_t indirect_bytes = (buf_bytes > remaining) ? remaining : buf_bytes; + if (indirect_bytes > 0) { + memcpy(out, &s->block[s->offset], indirect_bytes); + out += indirect_bytes; + s->offset += indirect_bytes; + remaining -= indirect_bytes; + } + } + return 0; +} + +int +keccak_digest_init(keccak_state *s, size_t bits) +{ + if (s == NULL) + return -1; + if (bits != 224 && bits != 256 && bits != 384 && bits != 512) + return -1; + + keccak_cleanse(s); + s->rate = KECCAK_RATE(bits); + s->delim = KECCAK_DELIM_DIGEST; + return 0; +} + +int +keccak_digest_update(keccak_state *s, const uint8_t *buf, size_t len) +{ + if (s == NULL) + return -1; + if (s->delim != KECCAK_DELIM_DIGEST) + return -1; + + return keccak_update(s, buf, len); +} + +int +keccak_digest_sum(const keccak_state *s, uint8_t *out, size_t outlen) +{ + if (s == NULL) + return -1; + if (s->delim != KECCAK_DELIM_DIGEST) + return -1; + if (out == NULL || outlen > 4 * (KECCAK_MAX_RATE - s->rate) / 8) + return -1; + + // Work in a copy so that incremental/rolling hashes are easy. + keccak_state s_tmp; + keccak_clone(&s_tmp, s); + keccak_finalize(&s_tmp); + int ret = keccak_squeeze(&s_tmp, out, outlen); + keccak_cleanse(&s_tmp); + return ret; +} + +int +keccak_xof_init(keccak_state *s, size_t bits) +{ + if (s == NULL) + return -1; + if (bits != 128 && bits != 256) + return -1; + + keccak_cleanse(s); + s->rate = KECCAK_RATE(bits); + s->delim = KECCAK_DELIM_XOF; + return 0; +} + +int +keccak_xof_absorb(keccak_state *s, const uint8_t *buf, size_t len) +{ + if (s == NULL) + return -1; + if (s->delim != KECCAK_DELIM_XOF) + return -1; + + return keccak_update(s, buf, len); +} + +int +keccak_xof_squeeze(keccak_state *s, uint8_t *out, size_t outlen) +{ + if (s == NULL) + return -1; + if (s->delim != KECCAK_DELIM_XOF) + return -1; + + if (!s->finalized) + keccak_finalize(s); + + return keccak_squeeze(s, out, outlen); +} + +void +keccak_clone(keccak_state *out, const keccak_state *in) +{ + memcpy(out, in, sizeof(keccak_state)); +} + +void +keccak_cleanse(keccak_state *s) +{ + memwipe(s, 0, sizeof(keccak_state)); +} + +/** The sponge-based hash construction. **/ +static inline int hash(uint8_t* out, size_t outlen, + const uint8_t* in, size_t inlen, + size_t bits, uint8_t delim) { + if ((out == NULL) || ((in == NULL) && inlen != 0)) { + return -1; + } + + int ret = 0; + keccak_state s; + + switch (delim) { + case KECCAK_DELIM_DIGEST: + ret |= keccak_digest_init(&s, bits); + ret |= keccak_digest_update(&s, in, inlen); + // Use the internal API instead of sum to avoid the memcpy. + keccak_finalize(&s); + ret |= keccak_squeeze(&s, out, outlen); + break; + case KECCAK_DELIM_XOF: + ret |= keccak_xof_init(&s, bits); + ret |= keccak_xof_absorb(&s, in, inlen); + ret |= keccak_xof_squeeze(&s, out, outlen); + break; + default: + return -1; + } + keccak_cleanse(&s); + return ret; +} + +/*** Helper macros to define SHA3 and SHAKE instances. ***/ +#define defshake(bits) \ + int shake##bits(uint8_t* out, size_t outlen, \ + const uint8_t* in, size_t inlen) { \ + return hash(out, outlen, in, inlen, bits, KECCAK_DELIM_XOF); \ + } +#define defsha3(bits) \ + int sha3_##bits(uint8_t* out, size_t outlen, \ + const uint8_t* in, size_t inlen) { \ + if (outlen > (bits/8)) { \ + return -1; \ + } \ + return hash(out, outlen, in, inlen, bits, KECCAK_DELIM_DIGEST); \ + } + +/*** FIPS202 SHAKE VOFs ***/ +defshake(128) +defshake(256) + +/*** FIPS202 SHA3 FOFs ***/ +defsha3(224) +defsha3(256) +defsha3(384) +defsha3(512) diff --git a/src/ext/keccak-tiny/keccak-tiny.c b/src/ext/keccak-tiny/keccak-tiny.c new file mode 100644 index 0000000000..76d89fa78c --- /dev/null +++ b/src/ext/keccak-tiny/keccak-tiny.c @@ -0,0 +1,163 @@ +/** libkeccak-tiny + * + * A single-file implementation of SHA-3 and SHAKE. + * + * Implementor: David Leon Gil + * License: CC0, attribution kindly requested. Blame taken too, + * but not liability. + */ +#include "keccak-tiny.h" + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/******** The Keccak-f[1600] permutation ********/ + +/*** Constants. ***/ +static const uint8_t rho[24] = \ + { 1, 3, 6, 10, 15, 21, + 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, + 62, 18, 39, 61, 20, 44}; +static const uint8_t pi[24] = \ + {10, 7, 11, 17, 18, 3, + 5, 16, 8, 21, 24, 4, + 15, 23, 19, 13, 12, 2, + 20, 14, 22, 9, 6, 1}; +static const uint64_t RC[24] = \ + {1ULL, 0x8082ULL, 0x800000000000808aULL, 0x8000000080008000ULL, + 0x808bULL, 0x80000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, + 0x8aULL, 0x88ULL, 0x80008009ULL, 0x8000000aULL, + 0x8000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, 0x8000000000008003ULL, + 0x8000000000008002ULL, 0x8000000000000080ULL, 0x800aULL, 0x800000008000000aULL, + 0x8000000080008081ULL, 0x8000000000008080ULL, 0x80000001ULL, 0x8000000080008008ULL}; + +/*** Helper macros to unroll the permutation. ***/ +#define rol(x, s) (((x) << s) | ((x) >> (64 - s))) +#define REPEAT6(e) e e e e e e +#define REPEAT24(e) REPEAT6(e e e e) +#define REPEAT5(e) e e e e e +#define FOR5(v, s, e) \ + v = 0; \ + REPEAT5(e; v += s;) + +/*** Keccak-f[1600] ***/ +static inline void keccakf(void* state) { + uint64_t* a = (uint64_t*)state; + uint64_t b[5] = {0}; + uint64_t t = 0; + uint8_t x, y; + + for (int i = 0; i < 24; i++) { + // Theta + FOR5(x, 1, + b[x] = 0; + FOR5(y, 5, + b[x] ^= a[x + y]; )) + FOR5(x, 1, + FOR5(y, 5, + a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); )) + // Rho and pi + t = a[1]; + x = 0; + REPEAT24(b[0] = a[pi[x]]; + a[pi[x]] = rol(t, rho[x]); + t = b[0]; + x++; ) + // Chi + FOR5(y, + 5, + FOR5(x, 1, + b[x] = a[y + x];) + FOR5(x, 1, + a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]); )) + // Iota + a[0] ^= RC[i]; + } +} + +/******** The FIPS202-defined functions. ********/ + +/*** Some helper macros. ***/ + +#define _(S) do { S } while (0) +#define FOR(i, ST, L, S) \ + _(for (size_t i = 0; i < L; i += ST) { S; }) +#define mkapply_ds(NAME, S) \ + static inline void NAME(uint8_t* dst, \ + const uint8_t* src, \ + size_t len) { \ + FOR(i, 1, len, S); \ + } +#define mkapply_sd(NAME, S) \ + static inline void NAME(const uint8_t* src, \ + uint8_t* dst, \ + size_t len) { \ + FOR(i, 1, len, S); \ + } + +mkapply_ds(xorin, dst[i] ^= src[i]) // xorin +mkapply_sd(setout, dst[i] = src[i]) // setout + +#define P keccakf +#define Plen 200 + +// Fold P*F over the full blocks of an input. +#define foldP(I, L, F) \ + while (L >= rate) { \ + F(a, I, rate); \ + P(a); \ + I += rate; \ + L -= rate; \ + } + +/** The sponge-based hash construction. **/ +static inline int hash(uint8_t* out, size_t outlen, + const uint8_t* in, size_t inlen, + size_t rate, uint8_t delim) { + if ((out == NULL) || ((in == NULL) && inlen != 0) || (rate >= Plen)) { + return -1; + } + uint8_t a[Plen] = {0}; + // Absorb input. + foldP(in, inlen, xorin); + // Xor in the DS and pad frame. + a[inlen] ^= delim; + a[rate - 1] ^= 0x80; + // Xor in the last block. + xorin(a, in, inlen); + // Apply P + P(a); + // Squeeze output. + foldP(out, outlen, setout); + setout(a, out, outlen); + memset_s(a, 200, 0, 200); + return 0; +} + +/*** Helper macros to define SHA3 and SHAKE instances. ***/ +#define defshake(bits) \ + int shake##bits(uint8_t* out, size_t outlen, \ + const uint8_t* in, size_t inlen) { \ + return hash(out, outlen, in, inlen, 200 - (bits / 4), 0x1f); \ + } +#define defsha3(bits) \ + int sha3_##bits(uint8_t* out, size_t outlen, \ + const uint8_t* in, size_t inlen) { \ + if (outlen > (bits/8)) { \ + return -1; \ + } \ + return hash(out, outlen, in, inlen, 200 - (bits / 4), 0x06); \ + } + +/*** FIPS202 SHAKE VOFs ***/ +defshake(128) +defshake(256) + +/*** FIPS202 SHA3 FOFs ***/ +defsha3(224) +defsha3(256) +defsha3(384) +defsha3(512) diff --git a/src/ext/keccak-tiny/keccak-tiny.h b/src/ext/keccak-tiny/keccak-tiny.h new file mode 100644 index 0000000000..7efea2319e --- /dev/null +++ b/src/ext/keccak-tiny/keccak-tiny.h @@ -0,0 +1,66 @@ +#ifndef KECCAK_FIPS202_H +#define KECCAK_FIPS202_H + +#include <stddef.h> +#include "torint.h" + +#define KECCAK_MAX_RATE 200 + +/* Calculate the rate (block size) from the security target. */ +#define KECCAK_RATE(bits) (KECCAK_MAX_RATE - (bits / 4)) + +/* The internal structure of a FIPS202 hash/xof instance. Most callers + * should treat this as an opaque structure. + */ +typedef struct keccak_state { + uint8_t a[KECCAK_MAX_RATE]; + size_t rate; + uint8_t delim; + + uint8_t block[KECCAK_MAX_RATE]; + size_t offset; + + uint8_t finalized : 1; +} keccak_state; + +/* Initialize a Keccak instance suitable for SHA-3 hash functions. */ +int keccak_digest_init(keccak_state *s, size_t bits); + +/* Feed more data into the SHA-3 hash instance. */ +int keccak_digest_update(keccak_state *s, const uint8_t *buf, size_t len); + +/* Calculate the SHA-3 hash digest. The state is unmodified to support + * calculating multiple/rolling digests. + */ +int keccak_digest_sum(const keccak_state *s, uint8_t *out, size_t outlen); + +/* Initialize a Keccak instance suitable for XOFs (SHAKE-128/256). */ +int keccak_xof_init(keccak_state *s, size_t bits); + +/* Absorb more data into the XOF. Must not be called after a squeeze call. */ +int keccak_xof_absorb(keccak_state *s, const uint8_t *buf, size_t len); + +/* Squeeze data out of the XOF. Must not attempt to absorb additional data, + * after a squeeze has been called. + */ +int keccak_xof_squeeze(keccak_state *s, uint8_t *out, size_t outlen); + +/* Clone an existing hash/XOF instance. */ +void keccak_clone(keccak_state *out, const keccak_state *in); + +/* Cleanse sensitive data from a given hash instance. */ +void keccak_cleanse(keccak_state *s); + +#define decshake(bits) \ + int shake##bits(uint8_t*, size_t, const uint8_t*, size_t); + +#define decsha3(bits) \ + int sha3_##bits(uint8_t*, size_t, const uint8_t*, size_t); + +decshake(128) +decshake(256) +decsha3(224) +decsha3(256) +decsha3(384) +decsha3(512) +#endif diff --git a/src/or/buffers.c b/src/or/buffers.c index cc2f6f409b..cdb499b8da 100644 --- a/src/or/buffers.c +++ b/src/or/buffers.c @@ -78,7 +78,7 @@ static int parse_socks_client(const uint8_t *data, size_t datalen, /** 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 * +static inline char * CHUNK_WRITE_PTR(chunk_t *chunk) { return chunk->data + chunk->datalen; @@ -86,7 +86,7 @@ CHUNK_WRITE_PTR(chunk_t *chunk) /** Return the number of bytes that can be written onto <b>chunk</b> without * running out of space. */ -static INLINE size_t +static inline size_t CHUNK_REMAINING_CAPACITY(const chunk_t *chunk) { return (chunk->mem + chunk->memlen) - (chunk->data + chunk->datalen); @@ -94,7 +94,7 @@ CHUNK_REMAINING_CAPACITY(const chunk_t *chunk) /** 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 +static inline void chunk_repack(chunk_t *chunk) { if (chunk->datalen && chunk->data != &chunk->mem[0]) { @@ -118,7 +118,7 @@ chunk_free_unchecked(chunk_t *chunk) total_bytes_allocated_in_chunks -= CHUNK_ALLOC_SIZE(chunk->memlen); tor_free(chunk); } -static INLINE chunk_t * +static inline chunk_t * chunk_new_with_alloc_size(size_t alloc) { chunk_t *ch; @@ -136,7 +136,7 @@ chunk_new_with_alloc_size(size_t alloc) /** 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 * +static inline chunk_t * chunk_grow(chunk_t *chunk, size_t sz) { off_t offset; @@ -165,7 +165,7 @@ chunk_grow(chunk_t *chunk, size_t sz) /** Return the allocation size we'd like to use to hold <b>target</b> * bytes. */ -static INLINE size_t +static inline size_t preferred_chunk_size(size_t target) { size_t sz = MIN_CHUNK_ALLOC; @@ -255,7 +255,7 @@ buf_get_first_chunk_data(const buf_t *buf, const char **cp, size_t *sz) #endif /** Remove the first <b>n</b> bytes from buf. */ -static INLINE void +static inline void buf_remove_from_front(buf_t *buf, size_t n) { tor_assert(buf->datalen >= n); @@ -452,7 +452,7 @@ buf_get_total_allocation(void) * <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 +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) { @@ -488,7 +488,7 @@ read_to_chunk(buf_t *buf, chunk_t *chunk, tor_socket_t fd, size_t at_most, /** As read_to_chunk(), but return (negative) error code on error, blocking, * or TLS, and the number of bytes read otherwise. */ -static INLINE int +static inline int read_to_chunk_tls(buf_t *buf, chunk_t *chunk, tor_tls_t *tls, size_t at_most) { @@ -611,7 +611,7 @@ read_to_buf_tls(tor_tls_t *tls, size_t at_most, buf_t *buf) * 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 +static inline int flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, size_t *buf_flushlen) { @@ -646,7 +646,7 @@ flush_chunk(tor_socket_t s, buf_t *buf, chunk_t *chunk, size_t sz, * 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 +static inline int flush_chunk_tls(tor_tls_t *tls, buf_t *buf, chunk_t *chunk, size_t sz, size_t *buf_flushlen) { @@ -797,7 +797,7 @@ write_to_buf(const char *string, size_t string_len, buf_t *buf) /** Helper: copy the first <b>string_len</b> bytes from <b>buf</b> * onto <b>string</b>. */ -static INLINE void +static inline void peek_from_buf(char *string, size_t string_len, const buf_t *buf) { chunk_t *chunk; @@ -842,7 +842,7 @@ fetch_from_buf(char *string, size_t string_len, buf_t *buf) /** 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 +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 @@ -1083,7 +1083,7 @@ buf_find_pos_of_char(char ch, buf_pos_t *out) /** 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 +static inline int buf_pos_inc(buf_pos_t *pos) { ++pos->pos; @@ -1945,7 +1945,7 @@ parse_socks(const char *data, size_t datalen, socks_request_t *req, log_warn(LD_PROTOCOL, "Your application (using socks4 to port %d) gave Tor " "a malformed hostname: %s. Rejecting the connection.", - req->port, escaped(req->address)); + req->port, escaped_safe_str_client(req->address)); return -1; } if (authend != authstart) { diff --git a/src/or/channel.c b/src/or/channel.c index 21522a5303..46e833854b 100644 --- a/src/or/channel.c +++ b/src/or/channel.c @@ -127,13 +127,13 @@ typedef struct channel_idmap_entry_s { TOR_LIST_HEAD(channel_list_s, channel_s) channel_list; } channel_idmap_entry_t; -static INLINE unsigned +static inline unsigned channel_idmap_hash(const channel_idmap_entry_t *ent) { return (unsigned) siphash24g(ent->digest, DIGEST_LEN); } -static INLINE int +static inline int channel_idmap_eq(const channel_idmap_entry_t *a, const channel_idmap_entry_t *b) { diff --git a/src/or/channel.h b/src/or/channel.h index 2b38ca7e19..5fa2aa8ab7 100644 --- a/src/or/channel.h +++ b/src/or/channel.h @@ -531,7 +531,7 @@ channel_t * channel_next_with_digest(channel_t *chan); CHANNEL_IS_OPEN(chan) || \ CHANNEL_IS_MAINT(chan)) -static INLINE int +static inline int channel_is_in_state(channel_t *chan, channel_state_t state) { return chan->state == state; diff --git a/src/or/channeltls.c b/src/or/channeltls.c index c90f569233..f0333e8da8 100644 --- a/src/or/channeltls.c +++ b/src/or/channeltls.c @@ -1663,30 +1663,9 @@ channel_tls_process_netinfo_cell(cell_t *cell, channel_tls_t *chan) #define NETINFO_NOTICE_SKEW 3600 if (labs(apparent_skew) > NETINFO_NOTICE_SKEW && router_get_by_id_digest(chan->conn->identity_digest)) { - char dbuf[64]; - int severity; - /*XXXX be smarter about when everybody says we are skewed. */ - if (router_digest_is_trusted_dir(chan->conn->identity_digest)) - severity = LOG_WARN; - else - severity = LOG_INFO; - format_time_interval(dbuf, sizeof(dbuf), apparent_skew); - log_fn(severity, LD_GENERAL, - "Received NETINFO cell with skewed time from " - "server at %s:%d. It seems that our clock is %s by %s, or " - "that theirs is %s. Tor requires an accurate clock to work: " - "please check your time and date settings.", - chan->conn->base_.address, - (int)(chan->conn->base_.port), - apparent_skew > 0 ? "ahead" : "behind", - dbuf, - apparent_skew > 0 ? "behind" : "ahead"); - if (severity == LOG_WARN) /* only tell the controller if an authority */ - control_event_general_status(LOG_WARN, - "CLOCK_SKEW SKEW=%ld SOURCE=OR:%s:%d", - apparent_skew, - chan->conn->base_.address, - chan->conn->base_.port); + int trusted = router_digest_is_trusted_dir(chan->conn->identity_digest); + clock_skew_warning(TO_CONN(chan->conn), apparent_skew, trusted, LD_GENERAL, + "NETINFO cell", "OR"); } /* XXX maybe act on my_apparent_addr, if the source is sufficiently diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 0688398f6d..719d27caa9 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -498,6 +498,14 @@ circuit_handle_first_hop(origin_circuit_t *circ) tor_assert(firsthop); tor_assert(firsthop->extend_info); + /* XX/teor - does tor ever need build a circuit directly to itself? */ + if (tor_addr_is_internal(&firsthop->extend_info->addr, 0) && + !get_options()->ExtendAllowPrivateAddresses) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Client asked me to connect directly to a private address"); + return -END_CIRC_REASON_TORPROTOCOL; + } + /* now see if we're already connected to the first OR in 'route' */ log_debug(LD_CIRC,"Looking for firsthop '%s'", fmt_addrport(&firsthop->extend_info->addr, @@ -737,7 +745,7 @@ inform_testing_reachability(void) /** Return true iff we should send a create_fast cell to start building a given * circuit */ -static INLINE int +static inline int should_use_create_fast_for_circuit(origin_circuit_t *circ) { const or_options_t *options = get_options(); diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 716024df6a..dcbeb1e2bb 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -44,11 +44,16 @@ static smartlist_t *global_circuitlist = NULL; /** A list of all the circuits in CIRCUIT_STATE_CHAN_WAIT. */ static smartlist_t *circuits_pending_chans = NULL; +/** A list of all the circuits that have been marked with + * circuit_mark_for_close and which are waiting for circuit_about_to_free. */ +static smartlist_t *circuits_pending_close = NULL; + static void circuit_free_cpath_node(crypt_path_t *victim); static void cpath_ref_decref(crypt_path_reference_t *cpath_ref); //static void circuit_set_rend_token(or_circuit_t *circ, int is_rend_circ, // const uint8_t *token); static void circuit_clear_rend_token(or_circuit_t *circ); +static void circuit_about_to_free(circuit_t *circ); /********* END VARIABLES ************/ @@ -66,7 +71,7 @@ typedef struct chan_circid_circuit_map_t { /** Helper for hash tables: compare the channel and circuit ID for a and * b, and return less than, equal to, or greater than zero appropriately. */ -static INLINE int +static inline int chan_circid_entries_eq_(chan_circid_circuit_map_t *a, chan_circid_circuit_map_t *b) { @@ -75,7 +80,7 @@ chan_circid_entries_eq_(chan_circid_circuit_map_t *a, /** Helper: return a hash based on circuit ID and the pointer value of * chan in <b>a</b>. */ -static INLINE unsigned int +static inline unsigned int chan_circid_entry_hash_(chan_circid_circuit_map_t *a) { /* Try to squeze the siphash input into 8 bytes to save any extra siphash @@ -451,16 +456,27 @@ circuit_count_pending_on_channel(channel_t *chan) void circuit_close_all_marked(void) { + if (circuits_pending_close == NULL) + return; + smartlist_t *lst = circuit_get_global_list(); - SMARTLIST_FOREACH_BEGIN(lst, circuit_t *, circ) { - /* Fix up index if SMARTLIST_DEL_CURRENT just moved this one. */ - circ->global_circuitlist_idx = circ_sl_idx; - if (circ->marked_for_close) { - circ->global_circuitlist_idx = -1; - circuit_free(circ); - SMARTLIST_DEL_CURRENT(lst, circ); + SMARTLIST_FOREACH_BEGIN(circuits_pending_close, circuit_t *, circ) { + tor_assert(circ->marked_for_close); + + /* Remove it from the circuit list. */ + int idx = circ->global_circuitlist_idx; + smartlist_del(lst, idx); + if (idx < smartlist_len(lst)) { + circuit_t *replacement = smartlist_get(lst, idx); + replacement->global_circuitlist_idx = idx; } + circ->global_circuitlist_idx = -1; + + circuit_about_to_free(circ); + circuit_free(circ); } SMARTLIST_FOREACH_END(circ); + + smartlist_clear(circuits_pending_close); } /** Return the head of the global linked list of circuits. */ @@ -895,6 +911,9 @@ circuit_free_all(void) smartlist_free(circuits_pending_chans); circuits_pending_chans = NULL; + smartlist_free(circuits_pending_close); + circuits_pending_close = NULL; + { chan_circid_circuit_map_t **elt, **next, *c; for (elt = HT_START(chan_circid_map, &chan_circid_map); @@ -1030,7 +1049,7 @@ circuit_get_by_global_id(uint32_t id) * If <b>found_entry_out</b> is provided, set it to true if we have a * placeholder entry for circid/chan, and leave it unset otherwise. */ -static INLINE circuit_t * +static inline circuit_t * circuit_get_by_circid_channel_impl(circid_t circ_id, channel_t *chan, int *found_entry_out) { @@ -1703,6 +1722,39 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, reason = END_CIRC_REASON_NONE; } + circ->marked_for_close = line; + circ->marked_for_close_file = file; + circ->marked_for_close_reason = reason; + circ->marked_for_close_orig_reason = orig_reason; + + if (!CIRCUIT_IS_ORIGIN(circ)) { + or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); + if (or_circ->rend_splice) { + if (!or_circ->rend_splice->base_.marked_for_close) { + /* do this after marking this circuit, to avoid infinite recursion. */ + circuit_mark_for_close(TO_CIRCUIT(or_circ->rend_splice), reason); + } + or_circ->rend_splice = NULL; + } + } + + if (circuits_pending_close == NULL) + circuits_pending_close = smartlist_new(); + + smartlist_add(circuits_pending_close, circ); +} + +/** Called immediately before freeing a marked circuit <b>circ</b>. + * Disconnects the circuit from other data structures, launches events + * as appropriate, and performs other housekeeping. + */ +static void +circuit_about_to_free(circuit_t *circ) +{ + + int reason = circ->marked_for_close_reason; + int orig_reason = circ->marked_for_close_orig_reason; + if (circ->state == CIRCUIT_STATE_ONIONSKIN_PENDING) { onion_pending_remove(TO_OR_CIRCUIT(circ)); } @@ -1726,6 +1778,7 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, (circ->state == CIRCUIT_STATE_OPEN)?CIRC_EVENT_CLOSED:CIRC_EVENT_FAILED, orig_reason); } + if (circ->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) { origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ); int timed_out = (reason == END_CIRC_REASON_TIMEOUT); @@ -1810,20 +1863,6 @@ circuit_mark_for_close_, (circuit_t *circ, int reason, int line, connection_edge_destroy(circ->n_circ_id, conn); ocirc->p_streams = NULL; } - - circ->marked_for_close = line; - circ->marked_for_close_file = file; - - if (!CIRCUIT_IS_ORIGIN(circ)) { - or_circuit_t *or_circ = TO_OR_CIRCUIT(circ); - if (or_circ->rend_splice) { - if (!or_circ->rend_splice->base_.marked_for_close) { - /* do this after marking this circuit, to avoid infinite recursion. */ - circuit_mark_for_close(TO_CIRCUIT(or_circ->rend_splice), reason); - } - or_circ->rend_splice = NULL; - } - } } /** Given a marked circuit <b>circ</b>, aggressively free its cell queues to diff --git a/src/or/circuitmux.c b/src/or/circuitmux.c index a77bffac90..94d1eb66e3 100644 --- a/src/or/circuitmux.c +++ b/src/or/circuitmux.c @@ -186,10 +186,10 @@ struct chanid_circid_muxinfo_t { * Static function declarations */ -static INLINE int +static inline int chanid_circid_entries_eq(chanid_circid_muxinfo_t *a, chanid_circid_muxinfo_t *b); -static INLINE unsigned int +static inline unsigned int chanid_circid_entry_hash(chanid_circid_muxinfo_t *a); static chanid_circid_muxinfo_t * circuitmux_find_map_entry(circuitmux_t *cmux, circuit_t *circ); @@ -199,12 +199,12 @@ circuitmux_make_circuit_active(circuitmux_t *cmux, circuit_t *circ, static void circuitmux_make_circuit_inactive(circuitmux_t *cmux, circuit_t *circ, cell_direction_t direction); -static INLINE void +static inline void circuitmux_move_active_circ_to_tail(circuitmux_t *cmux, circuit_t *circ, cell_direction_t direction); -static INLINE circuit_t ** +static inline circuit_t ** circuitmux_next_active_circ_p(circuitmux_t *cmux, circuit_t *circ); -static INLINE circuit_t ** +static inline circuit_t ** circuitmux_prev_active_circ_p(circuitmux_t *cmux, circuit_t *circ); static void circuitmux_assert_okay_pass_one(circuitmux_t *cmux); static void circuitmux_assert_okay_pass_two(circuitmux_t *cmux); @@ -226,7 +226,7 @@ static int64_t global_destroy_ctr = 0; * used by circuitmux_notify_xmit_cells(). */ -static INLINE void +static inline void circuitmux_move_active_circ_to_tail(circuitmux_t *cmux, circuit_t *circ, cell_direction_t direction) { @@ -306,7 +306,7 @@ circuitmux_move_active_circ_to_tail(circuitmux_t *cmux, circuit_t *circ, circuitmux_assert_okay_paranoid(cmux); } -static INLINE circuit_t ** +static inline circuit_t ** circuitmux_next_active_circ_p(circuitmux_t *cmux, circuit_t *circ) { tor_assert(cmux); @@ -319,7 +319,7 @@ circuitmux_next_active_circ_p(circuitmux_t *cmux, circuit_t *circ) } } -static INLINE circuit_t ** +static inline circuit_t ** circuitmux_prev_active_circ_p(circuitmux_t *cmux, circuit_t *circ) { tor_assert(cmux); @@ -338,7 +338,7 @@ circuitmux_prev_active_circ_p(circuitmux_t *cmux, circuit_t *circ) * than zero appropriately. */ -static INLINE int +static inline int chanid_circid_entries_eq(chanid_circid_muxinfo_t *a, chanid_circid_muxinfo_t *b) { @@ -349,7 +349,7 @@ chanid_circid_entries_eq(chanid_circid_muxinfo_t *a, * Helper: return a hash based on circuit ID and channel ID in a. */ -static INLINE unsigned int +static inline unsigned int chanid_circid_entry_hash(chanid_circid_muxinfo_t *a) { return (((unsigned int)(a->circ_id) << 8) ^ diff --git a/src/or/circuitmux_ewma.c b/src/or/circuitmux_ewma.c index 1c0318de06..0c61fb2ec4 100644 --- a/src/or/circuitmux_ewma.c +++ b/src/or/circuitmux_ewma.c @@ -115,7 +115,7 @@ TO_EWMA_POL_CIRC_DATA(circuitmux_policy_circ_data_t *); * if the cast is impossible. */ -static INLINE ewma_policy_data_t * +static inline ewma_policy_data_t * TO_EWMA_POL_DATA(circuitmux_policy_data_t *pol) { if (!pol) return NULL; @@ -130,7 +130,7 @@ TO_EWMA_POL_DATA(circuitmux_policy_data_t *pol) * and assert if the cast is impossible. */ -static INLINE ewma_policy_circ_data_t * +static inline ewma_policy_circ_data_t * TO_EWMA_POL_CIRC_DATA(circuitmux_policy_circ_data_t *pol) { if (!pol) return NULL; @@ -147,7 +147,7 @@ static int compare_cell_ewma_counts(const void *p1, const void *p2); static unsigned cell_ewma_tick_from_timeval(const struct timeval *now, double *remainder_out); static circuit_t * cell_ewma_to_circuit(cell_ewma_t *ewma); -static INLINE double get_scale_factor(unsigned from_tick, unsigned to_tick); +static inline double get_scale_factor(unsigned from_tick, unsigned to_tick); static cell_ewma_t * pop_first_cell_ewma(ewma_policy_data_t *pol); static void remove_cell_ewma(ewma_policy_data_t *pol, cell_ewma_t *ewma); static void scale_single_cell_ewma(cell_ewma_t *ewma, unsigned cur_tick); @@ -644,7 +644,7 @@ cell_ewma_set_scale_factor(const or_options_t *options, /** Return the multiplier necessary to convert the value of a cell sent in * 'from_tick' to one sent in 'to_tick'. */ -static INLINE double +static inline double get_scale_factor(unsigned from_tick, unsigned to_tick) { /* This math can wrap around, but that's okay: unsigned overflow is diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 00340fd689..e742a5614f 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1123,7 +1123,7 @@ circuit_build_needed_circs(time_t now) * don't require an exit circuit, review in #13814. * This allows HSs to function in a consensus without exits. */ if (router_have_consensus_path() != CONSENSUS_PATH_UNKNOWN) - connection_ap_attach_pending(); + connection_ap_rescan_and_attach_pending(); /* make sure any hidden services have enough intro points * HS intro point streams only require an internal circuit */ @@ -1475,7 +1475,7 @@ circuit_has_opened(origin_circuit_t *circ) case CIRCUIT_PURPOSE_C_ESTABLISH_REND: rend_client_rendcirc_has_opened(circ); /* Start building an intro circ if we don't have one yet. */ - connection_ap_attach_pending(); + connection_ap_attach_pending(1); /* This isn't a call to circuit_try_attaching_streams because a * circuit in _C_ESTABLISH_REND state isn't connected to its * hidden service yet, thus we can't attach streams to it yet, @@ -1537,14 +1537,14 @@ void circuit_try_attaching_streams(origin_circuit_t *circ) { /* Attach streams to this circuit if we can. */ - connection_ap_attach_pending(); + connection_ap_attach_pending(1); /* The call to circuit_try_clearing_isolation_state here will do * nothing and return 0 if we didn't attach any streams to circ * above. */ if (circuit_try_clearing_isolation_state(circ)) { /* Maybe *now* we can attach some streams to this circuit. */ - connection_ap_attach_pending(); + connection_ap_attach_pending(1); } } @@ -1986,6 +1986,7 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, "No intro points for '%s': re-fetching service descriptor.", safe_str_client(rend_data->onion_address)); rend_client_refetch_v2_renddesc(rend_data); + connection_ap_mark_as_non_pending_circuit(conn); ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_RENDDESC_WAIT; return 0; } diff --git a/src/or/config.c b/src/or/config.c index 3b35b5a8d7..b7744bab6a 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -212,6 +212,7 @@ static config_var_t option_vars_[] = { V(CookieAuthFile, STRING, NULL), V(CountPrivateBandwidth, BOOL, "0"), V(DataDirectory, FILENAME, NULL), + V(DataDirectoryGroupReadable, BOOL, "0"), V(DisableNetwork, BOOL, "0"), V(DirAllowPrivateAddresses, BOOL, "0"), V(TestingAuthDirTimeToLearnReachability, INTERVAL, "30 minutes"), @@ -221,6 +222,7 @@ static config_var_t option_vars_[] = { V(DirPortFrontPage, FILENAME, NULL), VAR("DirReqStatistics", BOOL, DirReqStatistics_option, "1"), VAR("DirAuthority", LINELIST, DirAuthorities, NULL), + V(DirCache, BOOL, "1"), V(DirAuthorityFallbackRate, DOUBLE, "1.0"), V(DisableAllSwap, BOOL, "0"), V(DisableDebuggerAttachment, BOOL, "1"), @@ -251,6 +253,7 @@ static config_var_t option_vars_[] = { V(ExtORPortCookieAuthFileGroupReadable, BOOL, "0"), V(ExtraInfoStatistics, BOOL, "1"), V(FallbackDir, LINELIST, NULL), + V(UseDefaultFallbackDirs, BOOL, "1"), OBSOLETE("FallbackNetworkstatusFile"), V(FascistFirewall, BOOL, "0"), @@ -308,10 +311,12 @@ static config_var_t option_vars_[] = { V(Socks5ProxyUsername, STRING, NULL), V(Socks5ProxyPassword, STRING, NULL), V(KeepalivePeriod, INTERVAL, "5 minutes"), + V(KeepBindCapabilities, AUTOBOOL, "auto"), VAR("Log", LINELIST, Logs, NULL), V(LogMessageDomains, BOOL, "0"), V(LogTimeGranularity, MSEC_INTERVAL, "1 second"), V(TruncateLogFile, BOOL, "0"), + V(SyslogIdentityTag, STRING, NULL), V(LongLivedPorts, CSV, "21,22,706,1863,5050,5190,5222,5223,6523,6667,6697,8300"), VAR("MapAddress", LINELIST, AddressMap, NULL), @@ -473,10 +478,40 @@ static config_var_t option_vars_[] = { V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 60, " "300, 600, 1800, 3600, 3600, 3600, " "10800, 21600, 43200"), + /* With the TestingClientBootstrapConsensus*Download* below: + * Clients with only authorities will try: + * - 3 authorities over 10 seconds, then wait 60 minutes. + * Clients with authorities and fallbacks will try: + * - 2 authorities and 4 fallbacks over 21 seconds, then wait 60 minutes. + * Clients will also retry when an application request arrives. + * After a number of failed reqests, clients retry every 3 days + 1 hour. + * + * Clients used to try 2 authorities over 10 seconds, then wait for + * 60 minutes or an application request. + * + * When clients have authorities and fallbacks available, they use these + * schedules: (we stagger the times to avoid thundering herds) */ + V(TestingClientBootstrapConsensusAuthorityDownloadSchedule, CSV_INTERVAL, + "10, 11, 3600, 10800, 25200, 54000, 111600, 262800" /* 3 days + 1 hour */), + V(TestingClientBootstrapConsensusFallbackDownloadSchedule, CSV_INTERVAL, + "0, 1, 4, 11, 3600, 10800, 25200, 54000, 111600, 262800"), + /* When clients only have authorities available, they use this schedule: */ + V(TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule, CSV_INTERVAL, + "0, 3, 7, 3600, 10800, 25200, 54000, 111600, 262800"), + /* We don't want to overwhelm slow networks (or mirrors whose replies are + * blocked), but we also don't want to fail if only some mirrors are + * blackholed. Clients will try 3 directories simultaneously. + * (Relays never use simultaneous connections.) */ + V(TestingClientBootstrapConsensusMaxInProgressTries, UINT, "3"), V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "3600, 900, 900, 3600"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "10 minutes"), V(TestingDirConnectionMaxStall, INTERVAL, "5 minutes"), V(TestingConsensusMaxDownloadTries, UINT, "8"), + /* Since we try connections rapidly and simultaneously, we can afford + * to give up earlier. (This protects against overloading directories.) */ + V(TestingClientBootstrapConsensusMaxDownloadTries, UINT, "7"), + /* We want to give up much earlier if we're only using authorities. */ + V(TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries, UINT, "4"), V(TestingDescriptorMaxDownloadTries, UINT, "8"), V(TestingMicrodescMaxDownloadTries, UINT, "8"), V(TestingCertMaxDownloadTries, UINT, "8"), @@ -523,10 +558,18 @@ static const config_var_t testing_tor_network_defaults[] = { "15, 20, 30, 60"), V(TestingClientConsensusDownloadSchedule, CSV_INTERVAL, "0, 0, 5, 10, " "15, 20, 30, 60"), + V(TestingClientBootstrapConsensusAuthorityDownloadSchedule, CSV_INTERVAL, + "0, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), + V(TestingClientBootstrapConsensusFallbackDownloadSchedule, CSV_INTERVAL, + "0, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), + V(TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule, CSV_INTERVAL, + "0, 1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 16, 32, 60"), V(TestingBridgeDownloadSchedule, CSV_INTERVAL, "60, 30, 30, 60"), V(TestingClientMaxIntervalWithoutRequest, INTERVAL, "5 seconds"), V(TestingDirConnectionMaxStall, INTERVAL, "30 seconds"), V(TestingConsensusMaxDownloadTries, UINT, "80"), + V(TestingClientBootstrapConsensusMaxDownloadTries, UINT, "80"), + V(TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries, UINT, "80"), V(TestingDescriptorMaxDownloadTries, UINT, "80"), V(TestingMicrodescMaxDownloadTries, UINT, "80"), V(TestingCertMaxDownloadTries, UINT, "80"), @@ -558,15 +601,12 @@ static int options_transition_affects_descriptor( static int check_nickname_list(char **lst, const char *name, char **msg); static char *get_bindaddr_from_transport_listen_line(const char *line, const char *transport); -static int parse_dir_authority_line(const char *line, - dirinfo_type_t required_type, - int validate_only); -static void port_cfg_free(port_cfg_t *port); static int parse_ports(or_options_t *options, int validate_only, char **msg_out, int *n_ports_out, int *world_writable_control_socket); static int check_server_ports(const smartlist_t *ports, - const or_options_t *options); + const or_options_t *options, + int *num_low_ports_out); static int validate_data_directory(or_options_t *options); static int write_configuration_file(const char *fname, @@ -625,8 +665,8 @@ static char *global_dirfrontpagecontents = NULL; static smartlist_t *configured_ports = NULL; /** Return the contents of our frontpage string, or NULL if not configured. */ -const char * -get_dirportfrontpage(void) +MOCK_IMPL(const char*, +get_dirportfrontpage, (void)) { return global_dirfrontpagecontents; } @@ -865,6 +905,7 @@ static const char *default_authorities[] = { "128.31.0.39:9131 9695 DFC3 5FFE B861 329B 9F1A B04C 4639 7020 CE31", "tor26 orport=443 " "v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 " + "ipv6=[2001:858:2:2:aabb:0:563b:1526]:443 " "86.59.21.38:80 847B 1F85 0344 D787 6491 A548 92F9 0493 4E4E B85D", "dizum orport=443 " "v3ident=E8A9C45EDE6D711294FADF8E7951F4DE6CA56B58 " @@ -873,30 +914,43 @@ static const char *default_authorities[] = { "82.94.251.203:80 4A0C CD2D DC79 9508 3D73 F5D6 6710 0C8A 5831 F16D", "gabelmoo orport=443 " "v3ident=ED03BB616EB2F60BEC80151114BB25CEF515B226 " + "ipv6=[2001:638:a000:4140::ffff:189]:443 " "131.188.40.189:80 F204 4413 DAC2 E02E 3D6B CF47 35A1 9BCA 1DE9 7281", "dannenberg orport=443 " "v3ident=0232AF901C31A04EE9848595AF9BB7620D4C5B2E " "193.23.244.244:80 7BE6 83E6 5D48 1413 21C5 ED92 F075 C553 64AC 7123", "urras orport=80 " "v3ident=80550987E1D626E3EBA5E5E75A458DE0626D088C " - "208.83.223.34:443 0AD3 FA88 4D18 F89E EA2D 89C0 1937 9E0E 7FD9 4417", + "208.83.223.34:443 0AD3 FA88 4D18 F89E EA2D 89C0 1937 9E0E 7FD9 4417" + /* XX/teor - urras may have an IPv6 address, but it's not in urras' + * descriptor as of 11 Dec 2015. See #17813. */, "maatuska orport=80 " "v3ident=49015F787433103580E3B66A1707A00E60F2D15B " + "ipv6=[2001:67c:289c::9]:80 " "171.25.193.9:443 BD6A 8292 55CB 08E6 6FBE 7D37 4836 3586 E46B 3810", "Faravahar orport=443 " "v3ident=EFCBE720AB3A82B99F9E953CD5BF50F7EEFC7B97 " "154.35.175.225:80 CF6D 0AAF B385 BE71 B8E1 11FC 5CFF 4B47 9237 33BC", "longclaw orport=443 " "v3ident=23D15D965BC35114467363C165C4F724B64B4F66 " + "ipv6=[2620:13:4000:8000:60:f3ff:fea1:7cff]:443 " "199.254.238.52:80 74A9 1064 6BCE EFBC D2E8 74FC 1DC9 9743 0F96 8145", NULL }; +/** List of fallback directory authorities. The list is generated by opt-in of + * relays that meet certain stability criteria. + */ +static const char *default_fallbacks[] = { +#include "fallback_dirs.inc" + NULL +}; + /** Add the default directory authorities directly into the trusted dir list, * but only add them insofar as they share bits with <b>type</b>. * Each authority's bits are restricted to the bits shared with <b>type</b>. * If <b>type</b> is ALL_DIRINFO or NO_DIRINFO (zero), add all authorities. */ -static void +STATIC void add_default_trusted_dir_authorities(dirinfo_type_t type) { int i; @@ -914,13 +968,10 @@ MOCK_IMPL(void, add_default_fallback_dir_servers,(void)) { int i; - const char *fallback[] = { - NULL - }; - for (i=0; fallback[i]; i++) { - if (parse_dir_fallback_line(fallback[i], 0)<0) { + for (i=0; default_fallbacks[i]; i++) { + if (parse_dir_fallback_line(default_fallbacks[i], 0)<0) { log_err(LD_BUG, "Couldn't parse internal FallbackDir line %s", - fallback[i]); + default_fallbacks[i]); } } } @@ -990,6 +1041,7 @@ consider_adding_dir_servers(const or_options_t *options, !smartlist_len(router_get_fallback_dir_servers()) || !old_options || !config_lines_eq(options->DirAuthorities, old_options->DirAuthorities) || !config_lines_eq(options->FallbackDir, old_options->FallbackDir) || + (options->UseDefaultFallbackDirs != old_options->UseDefaultFallbackDirs) || !config_lines_eq(options->AlternateBridgeAuthority, old_options->AlternateBridgeAuthority) || !config_lines_eq(options->AlternateDirAuthority, @@ -1018,8 +1070,8 @@ consider_adding_dir_servers(const or_options_t *options, type |= V3_DIRINFO | EXTRAINFO_DIRINFO | MICRODESC_DIRINFO; /* Only add the default fallback directories when the DirAuthorities, * AlternateDirAuthority, and FallbackDir directory config options - * are set to their defaults. */ - if (!options->FallbackDir) { + * are set to their defaults, and when UseDefaultFallbackDirs is 1. */ + if (!options->FallbackDir && options->UseDefaultFallbackDirs) { add_default_fallback_dir_servers(); } } @@ -1044,6 +1096,9 @@ consider_adding_dir_servers(const or_options_t *options, return 0; } +/* Helps determine flags to pass to switch_id. */ +static int have_low_ports = -1; + /** Fetch the active option list, and take actions based on it. All of the * things we do should survive being done repeatedly. If present, * <b>old_options</b> contains the previous value of the options. @@ -1178,7 +1233,16 @@ options_act_reversible(const or_options_t *old_options, char **msg) /* Setuid/setgid as appropriate */ if (options->User) { - if (switch_id(options->User) != 0) { + tor_assert(have_low_ports != -1); + unsigned switch_id_flags = 0; + if (options->KeepBindCapabilities == 1) { + switch_id_flags |= SWITCH_ID_KEEP_BINDLOW; + switch_id_flags |= SWITCH_ID_WARN_IF_NO_CAPS; + } + if (options->KeepBindCapabilities == -1 && have_low_ports) { + switch_id_flags |= SWITCH_ID_KEEP_BINDLOW; + } + if (switch_id(options->User, switch_id_flags) != 0) { /* No need to roll back, since you can't change the value. */ *msg = tor_strdup("Problem with User value. See logs for details."); goto done; @@ -1186,16 +1250,30 @@ options_act_reversible(const or_options_t *old_options, char **msg) } /* Ensure data directory is private; create if possible. */ + cpd_check_t cpd_opts = running_tor ? CPD_CREATE : CPD_CHECK; + if (options->DataDirectoryGroupReadable) + cpd_opts |= CPD_GROUP_READ; if (check_private_dir(options->DataDirectory, - running_tor ? CPD_CREATE : CPD_CHECK, + cpd_opts, options->User)<0) { tor_asprintf(msg, "Couldn't access/create private data directory \"%s\"", options->DataDirectory); + goto done; /* No need to roll back, since you can't change the value. */ } +#ifndef _WIN32 + if (options->DataDirectoryGroupReadable) { + /* Only new dirs created get new opts, also enforce group read. */ + if (chmod(options->DataDirectory, 0750)) { + log_warn(LD_FS,"Unable to make %s group-readable: %s", + options->DataDirectory, strerror(errno)); + } + } +#endif + /* Bail out at this point if we're not going to be a client or server: * we don't run Tor itself. */ if (!running_tor) @@ -3380,8 +3458,30 @@ options_validate(or_options_t *old_options, or_options_t *options, options->AccountingRule = ACCT_SUM; else if (!strcmp(options->AccountingRule_option, "max")) options->AccountingRule = ACCT_MAX; + else if (!strcmp(options->AccountingRule_option, "in")) + options->AccountingRule = ACCT_IN; + else if (!strcmp(options->AccountingRule_option, "out")) + options->AccountingRule = ACCT_OUT; else - REJECT("AccountingRule must be 'sum' or 'max'"); + REJECT("AccountingRule must be 'sum', 'max', 'in', or 'out'"); + } + + if (options->DirPort_set && !options->DirCache) { + REJECT("DirPort configured but DirCache disabled. DirPort requires " + "DirCache."); + } + + if (options->BridgeRelay && !options->DirCache) { + REJECT("We're a bridge but DirCache is disabled. BridgeRelay requires " + "DirCache."); + } + + if (server_mode(options)) { + char *msg = NULL; + if (have_enough_mem_for_dircache(options, 0, &msg)) { + log_warn(LD_CONFIG, "%s", msg); + tor_free(msg); + } } if (options->HTTPProxy) { /* parse it now */ @@ -3532,6 +3632,13 @@ options_validate(or_options_t *old_options, or_options_t *options, if (validate_addr_policies(options, msg) < 0) return -1; + /* If FallbackDir is set, we don't UseDefaultFallbackDirs */ + if (options->UseDefaultFallbackDirs && options->FallbackDir) { + log_info(LD_CONFIG, "You have set UseDefaultFallbackDirs 1 and " + "FallbackDir(s). Ignoring UseDefaultFallbackDirs, and " + "using the FallbackDir(s) you have set."); + } + if (validate_dir_servers(options, old_options) < 0) REJECT("Directory authority/fallback line did not parse. See logs " "for details."); @@ -3734,10 +3841,16 @@ options_validate(or_options_t *old_options, or_options_t *options, CHECK_DEFAULT(TestingClientDownloadSchedule); CHECK_DEFAULT(TestingServerConsensusDownloadSchedule); CHECK_DEFAULT(TestingClientConsensusDownloadSchedule); + CHECK_DEFAULT(TestingClientBootstrapConsensusAuthorityDownloadSchedule); + CHECK_DEFAULT(TestingClientBootstrapConsensusFallbackDownloadSchedule); + CHECK_DEFAULT(TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule); CHECK_DEFAULT(TestingBridgeDownloadSchedule); CHECK_DEFAULT(TestingClientMaxIntervalWithoutRequest); CHECK_DEFAULT(TestingDirConnectionMaxStall); CHECK_DEFAULT(TestingConsensusMaxDownloadTries); + CHECK_DEFAULT(TestingClientBootstrapConsensusMaxDownloadTries); + CHECK_DEFAULT(TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries); + CHECK_DEFAULT(TestingClientBootstrapConsensusMaxInProgressTries); CHECK_DEFAULT(TestingDescriptorMaxDownloadTries); CHECK_DEFAULT(TestingMicrodescMaxDownloadTries); CHECK_DEFAULT(TestingCertMaxDownloadTries); @@ -3812,11 +3925,41 @@ options_validate(or_options_t *old_options, or_options_t *options, } if (options->TestingConsensusMaxDownloadTries < 2) { - REJECT("TestingConsensusMaxDownloadTries must be greater than 1."); + REJECT("TestingConsensusMaxDownloadTries must be greater than 2."); } else if (options->TestingConsensusMaxDownloadTries > 800) { COMPLAIN("TestingConsensusMaxDownloadTries is insanely high."); } + if (options->TestingClientBootstrapConsensusMaxDownloadTries < 2) { + REJECT("TestingClientBootstrapConsensusMaxDownloadTries must be greater " + "than 2." + ); + } else if (options->TestingClientBootstrapConsensusMaxDownloadTries > 800) { + COMPLAIN("TestingClientBootstrapConsensusMaxDownloadTries is insanely " + "high."); + } + + if (options->TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries + < 2) { + REJECT("TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries must " + "be greater than 2." + ); + } else if ( + options->TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries + > 800) { + COMPLAIN("TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries is " + "insanely high."); + } + + if (options->TestingClientBootstrapConsensusMaxInProgressTries < 1) { + REJECT("TestingClientBootstrapConsensusMaxInProgressTries must be greater " + "than 0."); + } else if (options->TestingClientBootstrapConsensusMaxInProgressTries + > 100) { + COMPLAIN("TestingClientBootstrapConsensusMaxInProgressTries is insanely " + "high."); + } + if (options->TestingDescriptorMaxDownloadTries < 2) { REJECT("TestingDescriptorMaxDownloadTries must be greater than 1."); } else if (options->TestingDescriptorMaxDownloadTries > 800) { @@ -3949,6 +4092,52 @@ compute_real_max_mem_in_queues(const uint64_t val, int log_guess) } } +/* If we have less than 300 MB suggest disabling dircache */ +#define DIRCACHE_MIN_MB_BANDWIDTH 300 +#define DIRCACHE_MIN_BANDWIDTH (DIRCACHE_MIN_MB_BANDWIDTH*ONE_MEGABYTE) +#define STRINGIFY(val) #val + +/** Create a warning message for emitting if we are a dircache but may not have + * enough system memory, or if we are not a dircache but probably should be. + * Return -1 when a message is returned in *msg*, else return 0. */ +STATIC int +have_enough_mem_for_dircache(const or_options_t *options, size_t total_mem, + char **msg) +{ + *msg = NULL; + /* XXX We should possibly be looking at MaxMemInQueues here + * unconditionally. Or we should believe total_mem unconditionally. */ + if (total_mem == 0) { + if (get_total_system_memory(&total_mem) < 0) { + total_mem = options->MaxMemInQueues >= SIZE_MAX ? + SIZE_MAX : (size_t)options->MaxMemInQueues; + } + } + if (options->DirCache) { + if (total_mem < DIRCACHE_MIN_BANDWIDTH) { + if (options->BridgeRelay) { + *msg = strdup("Running a Bridge with less than " + STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is " + "not recommended."); + } else { + *msg = strdup("Being a directory cache (default) with less than " + STRINGIFY(DIRCACHE_MIN_MB_BANDWIDTH) " MB of memory is " + "not recommended and may consume most of the available " + "resources, consider disabling this functionality by " + "setting the DirCache option to 0."); + } + } + } else { + if (total_mem >= DIRCACHE_MIN_BANDWIDTH) { + *msg = strdup("DirCache is disabled and we are configured as a " + "relay. This may disqualify us from becoming a guard in the " + "future."); + } + } + return *msg == NULL ? 0 : -1; +} +#undef STRINGIFY + /** Helper: return true iff s1 and s2 are both NULL, or both non-NULL * equal strings. */ static int @@ -3996,6 +4185,18 @@ options_transition_allowed(const or_options_t *old, return -1; } + if (old->KeepBindCapabilities != new_val->KeepBindCapabilities) { + *msg = tor_strdup("While Tor is running, changing KeepBindCapabilities is " + "not allowed."); + return -1; + } + + if (!opt_streq(old->SyslogIdentityTag, new_val->SyslogIdentityTag)) { + *msg = tor_strdup("While Tor is running, changing " + "SyslogIdentityTag is not allowed."); + return -1; + } + if ((old->HardwareAccel != new_val->HardwareAccel) || !opt_streq(old->AccelName, new_val->AccelName) || !opt_streq(old->AccelDir, new_val->AccelDir)) { @@ -4125,7 +4326,8 @@ options_transition_affects_descriptor(const or_options_t *old_options, !opt_streq(old_options->MyFamily, new_options->MyFamily) || !opt_streq(old_options->AccountingStart, new_options->AccountingStart) || old_options->AccountingMax != new_options->AccountingMax || - public_server_mode(old_options) != public_server_mode(new_options)) + public_server_mode(old_options) != public_server_mode(new_options) || + old_options->DirCache != new_options->DirCache) return 1; return 0; @@ -4937,7 +5139,7 @@ options_init_logs(const or_options_t *old_options, or_options_t *options, !strcasecmp(smartlist_get(elts,0), "syslog")) { #ifdef HAVE_SYSLOG_H if (!validate_only) { - add_syslog_log(severity); + add_syslog_log(severity, options->SyslogIdentityTag); } #else log_warn(LD_CONFIG, "Syslog is not supported on this system. Sorry."); @@ -5510,13 +5712,14 @@ get_options_for_server_transport(const char *transport) * (minus whatever bits it's missing) as a valid authority. * Return 0 on success or filtering out by type, * or -1 if the line isn't well-formed or if we can't add it. */ -static int +STATIC int parse_dir_authority_line(const char *line, dirinfo_type_t required_type, int validate_only) { smartlist_t *items = NULL; int r; char *addrport=NULL, *address=NULL, *nickname=NULL, *fingerprint=NULL; + tor_addr_port_t ipv6_addrport, *ipv6_addrport_ptr = NULL; uint16_t dir_port = 0, or_port = 0; char digest[DIGEST_LEN]; char v3_digest[DIGEST_LEN]; @@ -5573,6 +5776,20 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, } else { type |= V3_DIRINFO|EXTRAINFO_DIRINFO|MICRODESC_DIRINFO; } + } else if (!strcasecmpstart(flag, "ipv6=")) { + if (ipv6_addrport_ptr) { + log_warn(LD_CONFIG, "Redundant ipv6 addr/port on DirAuthority line"); + } else { + if (tor_addr_port_parse(LOG_WARN, flag+strlen("ipv6="), + &ipv6_addrport.addr, &ipv6_addrport.port, + -1) < 0 + || tor_addr_family(&ipv6_addrport.addr) != AF_INET6) { + log_warn(LD_CONFIG, "Bad ipv6 addr/port %s on DirAuthority line", + escaped(flag)); + goto err; + } + ipv6_addrport_ptr = &ipv6_addrport; + } } else { log_warn(LD_CONFIG, "Unrecognized flag '%s' on DirAuthority line", flag); @@ -5615,6 +5832,7 @@ parse_dir_authority_line(const char *line, dirinfo_type_t required_type, log_debug(LD_DIR, "Trusted %d dirserver at %s:%d (%s)", (int)type, address, (int)dir_port, (char*)smartlist_get(items,0)); if (!(ds = trusted_dir_server_new(nickname, address, dir_port, or_port, + ipv6_addrport_ptr, digest, v3_digest, type, weight))) goto err; dir_server_add(ds); @@ -5652,6 +5870,7 @@ parse_dir_fallback_line(const char *line, int ok; char id[DIGEST_LEN]; char *address=NULL; + tor_addr_port_t ipv6_addrport, *ipv6_addrport_ptr = NULL; double weight=1.0; memset(id, 0, sizeof(id)); @@ -5670,6 +5889,20 @@ parse_dir_fallback_line(const char *line, } else if (!strcmpstart(cp, "id=")) { ok = !base16_decode(id, DIGEST_LEN, cp+strlen("id="), strlen(cp)-strlen("id=")); + } else if (!strcasecmpstart(cp, "ipv6=")) { + if (ipv6_addrport_ptr) { + log_warn(LD_CONFIG, "Redundant ipv6 addr/port on FallbackDir line"); + } else { + if (tor_addr_port_parse(LOG_WARN, cp+strlen("ipv6="), + &ipv6_addrport.addr, &ipv6_addrport.port, + -1) < 0 + || tor_addr_family(&ipv6_addrport.addr) != AF_INET6) { + log_warn(LD_CONFIG, "Bad ipv6 addr/port %s on FallbackDir line", + escaped(cp)); + goto end; + } + ipv6_addrport_ptr = &ipv6_addrport; + } } else if (!strcmpstart(cp, "weight=")) { int ok; const char *wstring = cp + strlen("weight="); @@ -5711,7 +5944,8 @@ parse_dir_fallback_line(const char *line, if (!validate_only) { dir_server_t *ds; - ds = fallback_dir_server_new(&addr, dirport, orport, id, weight); + ds = fallback_dir_server_new(&addr, dirport, orport, ipv6_addrport_ptr, + id, weight); if (!ds) { log_warn(LD_CONFIG, "Couldn't create FallbackDir %s", escaped(line)); goto end; @@ -5730,7 +5964,7 @@ parse_dir_fallback_line(const char *line, } /** Allocate and return a new port_cfg_t with reasonable defaults. */ -static port_cfg_t * +STATIC port_cfg_t * port_cfg_new(size_t namelen) { tor_assert(namelen <= SIZE_T_CEILING - sizeof(port_cfg_t) - 1); @@ -5742,7 +5976,7 @@ port_cfg_new(size_t namelen) } /** Free all storage held in <b>port</b> */ -static void +STATIC void port_cfg_free(port_cfg_t *port) { tor_free(port); @@ -5796,9 +6030,9 @@ warn_nonlocal_ext_orports(const smartlist_t *ports, const char *portname) } SMARTLIST_FOREACH_END(port); } -/** Given a list of port_cfg_t in <b>ports</b>, warn any controller port there - * is listening on any non-loopback address. If <b>forbid_nonlocal</b> is - * true, then emit a stronger warning and remove the port from the list. +/** Given a list of port_cfg_t in <b>ports</b>, warn if any controller port + * there is listening on any non-loopback address. If <b>forbid_nonlocal</b> + * is true, then emit a stronger warning and remove the port from the list. */ static void warn_nonlocal_controller_ports(smartlist_t *ports, unsigned forbid_nonlocal) @@ -6140,7 +6374,7 @@ parse_port_config(smartlist_t *out, } port = ptmp; } else { - log_warn(LD_CONFIG, "Couldn't parse address '%s' for %sPort", + log_warn(LD_CONFIG, "Couldn't parse address %s for %sPort", escaped(addrport), portname); goto err; } @@ -6528,10 +6762,13 @@ parse_ports(or_options_t *options, int validate_only, } } - if (check_server_ports(ports, options) < 0) { + int n_low_ports = 0; + if (check_server_ports(ports, options, &n_low_ports) < 0) { *msg = tor_strdup("Misconfigured server ports"); goto err; } + if (have_low_ports < 0) + have_low_ports = (n_low_ports > 0); *n_ports_out = smartlist_len(ports); @@ -6585,10 +6822,12 @@ parse_ports(or_options_t *options, int validate_only, } /** Given a list of <b>port_cfg_t</b> in <b>ports</b>, check them for internal - * consistency and warn as appropriate. */ + * consistency and warn as appropriate. Set *<b>n_low_ports_out</b> to the + * number of sub-1024 ports we will be binding. */ static int check_server_ports(const smartlist_t *ports, - const or_options_t *options) + const or_options_t *options, + int *n_low_ports_out) { int n_orport_advertised = 0; int n_orport_advertised_ipv4 = 0; @@ -6651,23 +6890,31 @@ check_server_ports(const smartlist_t *ports, r = -1; } - if (n_low_port && options->AccountingMax) { + if (n_low_port && options->AccountingMax && + (!have_capability_support() || options->KeepBindCapabilities == 0)) { + const char *extra = ""; + if (options->KeepBindCapabilities == 0 && have_capability_support()) + extra = ", and you have disabled KeepBindCapabilities."; log_warn(LD_CONFIG, "You have set AccountingMax to use hibernation. You have also " - "chosen a low DirPort or OrPort. This combination can make Tor stop " + "chosen a low DirPort or OrPort%s." + "This combination can make Tor stop " "working when it tries to re-attach the port after a period of " "hibernation. Please choose a different port or turn off " "hibernation unless you know this combination will work on your " - "platform."); + "platform.", extra); } + if (n_low_ports_out) + *n_low_ports_out = n_low_port; + return r; } /** Return a list of port_cfg_t for client ports parsed from the * options. */ -const smartlist_t * -get_configured_ports(void) +MOCK_IMPL(const smartlist_t *, +get_configured_ports,(void)) { if (!configured_ports) configured_ports = smartlist_new(); @@ -7151,7 +7398,7 @@ getinfo_helper_config(control_connection_t *conn, smartlist_free(sl); } else if (!strcmp(question, "config/defaults")) { smartlist_t *sl = smartlist_new(); - int i, dirauth_lines_seen = 0; + int i, dirauth_lines_seen = 0, fallback_lines_seen = 0; for (i = 0; option_vars_[i].name; ++i) { const config_var_t *var = &option_vars_[i]; if (var->initvalue != NULL) { @@ -7162,6 +7409,13 @@ getinfo_helper_config(control_connection_t *conn, */ ++dirauth_lines_seen; } + if (strcmp(option_vars_[i].name, "FallbackDir") == 0) { + /* + * Similarly count fallback lines, so that we can decided later + * to add the defaults manually. + */ + ++fallback_lines_seen; + } char *val = esc_for_log(var->initvalue); smartlist_add_asprintf(sl, "%s %s\n",var->name,val); tor_free(val); @@ -7187,6 +7441,24 @@ getinfo_helper_config(control_connection_t *conn, } } + if (fallback_lines_seen == 0 && + get_options()->UseDefaultFallbackDirs == 1) { + /* + * We didn't see any explicitly configured fallback mirrors, + * so add the defaults to the list manually. + * + * default_fallbacks is included earlier in this file and + * is a const char ** NULL-terminated array of fallback config lines. + */ + const char **i; + + for (i = default_fallbacks; *i != NULL; ++i) { + char *val = esc_for_log(*i); + smartlist_add_asprintf(sl, "FallbackDir %s\n", val); + tor_free(val); + } + } + *answer = smartlist_join_strings(sl, "", 0, NULL); SMARTLIST_FOREACH(sl, char *, c, tor_free(c)); smartlist_free(sl); @@ -7329,8 +7601,7 @@ init_cookie_authentication(const char *fname, const char *header, /* Generate the cookie */ *cookie_out = tor_malloc(cookie_len); - if (crypto_rand((char *)*cookie_out, cookie_len) < 0) - goto done; + crypto_rand((char *)*cookie_out, cookie_len); /* Create the string that should be written on the file. */ memcpy(cookie_file_str, header, strlen(header)); diff --git a/src/or/config.h b/src/or/config.h index 0ee1e1a3c4..6e08f9d178 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -14,8 +14,8 @@ #include "testsupport.h" -const char *get_dirportfrontpage(void); -MOCK_DECL(const or_options_t *,get_options,(void)); +MOCK_DECL(const char*, get_dirportfrontpage, (void)); +MOCK_DECL(const or_options_t *, get_options, (void)); or_options_t *get_options_mutable(void); int set_options(or_options_t *new_val, char **msg); void config_free_all(void); @@ -76,7 +76,7 @@ int write_to_data_subdir(const char* subdir, const char* fname, int get_num_cpus(const or_options_t *options); -const smartlist_t *get_configured_ports(void); +MOCK_DECL(const smartlist_t *,get_configured_ports,(void)); int get_first_advertised_port_by_type_af(int listener_type, int address_family); #define get_primary_or_port() \ @@ -140,6 +140,8 @@ smartlist_t *get_options_for_server_transport(const char *transport); extern struct config_format_t options_format; #endif +STATIC port_cfg_t *port_cfg_new(size_t namelen); +STATIC void port_cfg_free(port_cfg_t *port); STATIC void or_options_free(or_options_t *options); STATIC int options_validate(or_options_t *old_options, or_options_t *options, @@ -150,10 +152,14 @@ STATIC int parse_transport_line(const or_options_t *options, int server); STATIC int consider_adding_dir_servers(const or_options_t *options, const or_options_t *old_options); +STATIC void add_default_trusted_dir_authorities(dirinfo_type_t type); MOCK_DECL(STATIC void, add_default_fallback_dir_servers, (void)); -STATIC int -parse_dir_fallback_line(const char *line, - int validate_only); +STATIC int parse_dir_authority_line(const char *line, + dirinfo_type_t required_type, + int validate_only); +STATIC int parse_dir_fallback_line(const char *line, int validate_only); +STATIC int have_enough_mem_for_dircache(const or_options_t *options, + size_t total_mem, char **msg); #endif #endif diff --git a/src/or/connection.c b/src/or/connection.c index 78176d3768..c0031047e7 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -678,6 +678,13 @@ connection_free,(connection_t *conn)) if (conn->type == CONN_TYPE_CONTROL) { connection_control_closed(TO_CONTROL_CONN(conn)); } +#if 1 + /* DEBUGGING */ + if (conn->type == CONN_TYPE_AP) { + connection_ap_warn_and_unmark_if_pending_circ(TO_ENTRY_CONN(conn), + "connection_free"); + } +#endif connection_unregister_events(conn); connection_free_(conn); } @@ -1128,11 +1135,12 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_socket_strerror(errno)); } -#if defined USE_TRANSPARENT && defined(IP_TRANSPARENT) +#if defined(USE_TRANSPARENT) && defined(IP_TRANSPARENT) if (options->TransProxyType_parsed == TPT_TPROXY && type == CONN_TYPE_AP_TRANS_LISTENER) { int one = 1; - if (setsockopt(s, SOL_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0) { + if (setsockopt(s, SOL_IP, IP_TRANSPARENT, (void*)&one, + (socklen_t)sizeof(one)) < 0) { const char *extra = ""; int e = tor_socket_errno(s); if (e == EPERM) @@ -1146,16 +1154,11 @@ connection_listener_new(const struct sockaddr *listensockaddr, #ifdef IPV6_V6ONLY if (listensockaddr->sa_family == AF_INET6) { -#ifdef _WIN32 - /* In Redmond, this kind of thing passes for standards-conformance. */ - DWORD one = 1; -#else int one = 1; -#endif /* We need to set IPV6_V6ONLY so that this socket can't get used for * IPv4 connections. */ if (setsockopt(s,IPPROTO_IPV6, IPV6_V6ONLY, - (void*)&one, sizeof(one)) < 0) { + (void*)&one, (socklen_t)sizeof(one)) < 0) { int e = tor_socket_errno(s); log_warn(LD_NET, "Error setting IPV6_V6ONLY flag: %s", tor_socket_strerror(e)); @@ -1597,6 +1600,8 @@ connection_init_accepted_conn(connection_t *conn, break; case CONN_TYPE_AP_TRANS_LISTENER: TO_ENTRY_CONN(conn)->is_transparent_ap = 1; + /* XXXX028 -- is this correct still, with the addition of + * pending_entry_connections ? */ conn->state = AP_CONN_STATE_CIRCUIT_WAIT; return connection_ap_process_transparent(TO_ENTRY_CONN(conn)); case CONN_TYPE_AP_NATD_LISTENER: @@ -1616,13 +1621,18 @@ connection_init_accepted_conn(connection_t *conn, return 0; } -static int -connection_connect_sockaddr(connection_t *conn, +/** Take conn, make a nonblocking socket; try to connect to + * sa, binding to bindaddr if sa is not localhost. If fail, return -1 and if + * applicable put your best guess about errno into *<b>socket_error</b>. + * If connected return 1, if EAGAIN return 0. + */ +MOCK_IMPL(STATIC int, +connection_connect_sockaddr,(connection_t *conn, const struct sockaddr *sa, socklen_t sa_len, const struct sockaddr *bindaddr, socklen_t bindaddr_len, - int *socket_error) + int *socket_error)) { tor_socket_t s; int inprogress = 0; @@ -1706,10 +1716,13 @@ connection_connect_sockaddr(connection_t *conn, } /** Take conn, make a nonblocking socket; try to connect to - * addr:port (they arrive in *host order*). If fail, return -1 and if + * addr:port (port arrives in *host order*). If fail, return -1 and if * applicable put your best guess about errno into *<b>socket_error</b>. * Else assign s to conn-\>s: if connected return 1, if EAGAIN return 0. * + * addr:port can be different to conn->addr:conn->port if connecting through + * a proxy. + * * address is used to make the logs useful. * * On success, add conn to the list of polled connections. @@ -2381,6 +2394,14 @@ retry_listener_ports(smartlist_t *old_conns, if (port->server_cfg.no_listen) continue; +#ifndef _WIN32 + /* We don't need to be root to create a UNIX socket, so defer until after + * setuid. */ + const or_options_t *options = get_options(); + if (port->is_unix_addr && !geteuid() && strcmp(options->User, "root")) + continue; +#endif + if (port->is_unix_addr) { listensockaddr = (struct sockaddr *) create_unix_sockaddr(port->unix_addr, @@ -4209,24 +4230,32 @@ connection_write_to_buf_impl_,(const char *string, size_t len, } } +/** 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) \ + STMT_BEGIN \ + smartlist_t *conns = get_connection_array(); \ + SMARTLIST_FOREACH(conns, connection_t *, var, \ + { \ + if (var && (test) && !var->marked_for_close) \ + return var; \ + }); \ + return NULL; \ + STMT_END + /** Return a connection with given type, address, port, and purpose; - * or NULL if no such connection exists. */ + * or NULL if no such connection exists (or if all such connections are marked + * for close). */ connection_t * connection_get_by_type_addr_port_purpose(int type, const tor_addr_t *addr, uint16_t port, int purpose) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type == type && + CONN_GET_TEMPLATE(conn, + (conn->type == type && tor_addr_eq(&conn->addr, addr) && conn->port == port && - conn->purpose == purpose && - !conn->marked_for_close) - return conn; - }); - return NULL; + conn->purpose == purpose)); } /** Return the stream with id <b>id</b> if it is not already marked for @@ -4235,13 +4264,7 @@ connection_get_by_type_addr_port_purpose(int type, connection_t * connection_get_by_global_id(uint64_t id) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->global_identifier == id) - return conn; - }); - return NULL; + CONN_GET_TEMPLATE(conn, conn->global_identifier == id); } /** Return a connection of type <b>type</b> that is not marked for close. @@ -4249,13 +4272,7 @@ connection_get_by_global_id(uint64_t id) connection_t * connection_get_by_type(int type) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type == type && !conn->marked_for_close) - return conn; - }); - return NULL; + CONN_GET_TEMPLATE(conn, conn->type == type); } /** Return a connection of type <b>type</b> that is in state <b>state</b>, @@ -4264,13 +4281,7 @@ connection_get_by_type(int type) connection_t * connection_get_by_type_state(int type, int state) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH(conns, connection_t *, conn, - { - if (conn->type == type && conn->state == state && !conn->marked_for_close) - return conn; - }); - return NULL; + CONN_GET_TEMPLATE(conn, conn->type == type && conn->state == state); } /** Return a connection of type <b>type</b> that has rendquery equal @@ -4281,55 +4292,141 @@ connection_t * connection_get_by_type_state_rendquery(int type, int state, const char *rendquery) { - smartlist_t *conns = get_connection_array(); - tor_assert(type == CONN_TYPE_DIR || type == CONN_TYPE_AP || type == CONN_TYPE_EXIT); tor_assert(rendquery); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - if (conn->type == type && - !conn->marked_for_close && - (!state || state == conn->state)) { - if (type == CONN_TYPE_DIR && + CONN_GET_TEMPLATE(conn, + (conn->type == type && + (!state || state == conn->state)) && + ( + (type == CONN_TYPE_DIR && TO_DIR_CONN(conn)->rend_data && !rend_cmp_service_ids(rendquery, TO_DIR_CONN(conn)->rend_data->onion_address)) - return conn; - else if (CONN_IS_EDGE(conn) && + || + (CONN_IS_EDGE(conn) && TO_EDGE_CONN(conn)->rend_data && !rend_cmp_service_ids(rendquery, TO_EDGE_CONN(conn)->rend_data->onion_address)) - return conn; - } - } SMARTLIST_FOREACH_END(conn); - return NULL; + )); } +#define CONN_FIRST_AND_FREE_TEMPLATE(sl) \ + STMT_BEGIN \ + if (smartlist_len(sl) > 0) { \ + void *first_item = smartlist_get(sl, 0); \ + smartlist_free(sl); \ + return first_item; \ + } else { \ + smartlist_free(sl); \ + return NULL; \ + } \ + STMT_END + /** Return a directory connection (if any one exists) that is fetching - * the item described by <b>state</b>/<b>resource</b> */ + * the item described by <b>purpose</b>/<b>resource</b>, otherwise return NULL. + */ dir_connection_t * -connection_dir_get_by_purpose_and_resource(int purpose, +connection_dir_get_by_purpose_and_resource( + int purpose, const char *resource) { - smartlist_t *conns = get_connection_array(); + smartlist_t *conns = connection_dir_list_by_purpose_and_resource( + purpose, + resource); + CONN_FIRST_AND_FREE_TEMPLATE(conns); +} - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - dir_connection_t *dirconn; - if (conn->type != CONN_TYPE_DIR || conn->marked_for_close || - conn->purpose != purpose) - continue; - dirconn = TO_DIR_CONN(conn); - if (dirconn->requested_resource == NULL) { - if (resource == NULL) - return dirconn; - } else if (resource) { - if (0 == strcmp(resource, dirconn->requested_resource)) - return dirconn; - } - } SMARTLIST_FOREACH_END(conn); +/** Return a new smartlist of dir_connection_t * from get_connection_array() + * that satisfy conn_test on connection_t *conn_var, and dirconn_test on + * dir_connection_t *dirconn_var. conn_var must be of CONN_TYPE_DIR and not + * marked for close to be included in the list. */ +#define DIR_CONN_LIST_TEMPLATE(conn_var, conn_test, \ + dirconn_var, dirconn_test) \ + STMT_BEGIN \ + smartlist_t *conns = get_connection_array(); \ + smartlist_t *dir_conns = smartlist_new(); \ + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn_var) { \ + if (conn_var && (conn_test) \ + && conn_var->type == CONN_TYPE_DIR \ + && !conn_var->marked_for_close) { \ + dir_connection_t *dirconn_var = TO_DIR_CONN(conn_var); \ + if (dirconn_var && (dirconn_test)) { \ + smartlist_add(dir_conns, dirconn_var); \ + } \ + } \ + } SMARTLIST_FOREACH_END(conn_var); \ + return dir_conns; \ + STMT_END + +/** Return a list of directory connections that are fetching the item + * described by <b>purpose</b>/<b>resource</b>. If there are none, + * return an empty list. This list must be freed using smartlist_free, + * but the pointers in it must not be freed. + * Note that this list should not be cached, as the pointers in it can be + * freed if their connections close. */ +smartlist_t * +connection_dir_list_by_purpose_and_resource( + int purpose, + const char *resource) +{ + DIR_CONN_LIST_TEMPLATE(conn, + conn->purpose == purpose, + dirconn, + 0 == strcmp_opt(resource, + dirconn->requested_resource)); +} - return NULL; +/** Return a directory connection (if any one exists) that is fetching + * the item described by <b>purpose</b>/<b>resource</b>/<b>state</b>, + * otherwise return NULL. */ +dir_connection_t * +connection_dir_get_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state) +{ + smartlist_t *conns = + connection_dir_list_by_purpose_resource_and_state( + purpose, + resource, + state); + CONN_FIRST_AND_FREE_TEMPLATE(conns); +} + +#undef CONN_FIRST_AND_FREE_TEMPLATE + +/** Return a list of directory connections that are fetching the item + * described by <b>purpose</b>/<b>resource</b>/<b>state</b>. If there are + * none, return an empty list. This list must be freed using smartlist_free, + * but the pointers in it must not be freed. + * Note that this list should not be cached, as the pointers in it can be + * freed if their connections close. */ +smartlist_t * +connection_dir_list_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state) +{ + DIR_CONN_LIST_TEMPLATE(conn, + conn->purpose == purpose && conn->state == state, + dirconn, + 0 == strcmp_opt(resource, + dirconn->requested_resource)); +} + +#undef DIR_CONN_LIST_TEMPLATE + +/** Return an arbitrary active OR connection that isn't <b>this_conn</b>. + * + * We use this to guess if we should tell the controller that we + * didn't manage to connect to any of our bridges. */ +static connection_t * +connection_get_another_active_or_conn(const or_connection_t *this_conn) +{ + CONN_GET_TEMPLATE(conn, + conn != TO_CONN(this_conn) && conn->type == CONN_TYPE_OR); } /** Return 1 if there are any active OR connections apart from @@ -4340,23 +4437,18 @@ connection_dir_get_by_purpose_and_resource(int purpose, int any_other_active_or_conns(const or_connection_t *this_conn) { - smartlist_t *conns = get_connection_array(); - SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { - if (conn == TO_CONN(this_conn)) { /* don't consider this conn */ - continue; - } - - if (conn->type == CONN_TYPE_OR && - !conn->marked_for_close) { - log_debug(LD_DIR, "%s: Found an OR connection: %s", - __func__, conn->address); - return 1; - } - } SMARTLIST_FOREACH_END(conn); + connection_t *conn = connection_get_another_active_or_conn(this_conn); + if (conn != NULL) { + log_debug(LD_DIR, "%s: Found an OR connection: %s", + __func__, conn->address); + return 1; + } return 0; } +#undef CONN_GET_TEMPLATE + /** Return 1 if <b>conn</b> is a listener conn, else return 0. */ int connection_is_listener(connection_t *conn) @@ -5012,3 +5104,34 @@ connection_free_all(void) #endif } +/** Log a warning, and possibly emit a control event, that <b>received</b> came + * at a skewed time. <b>trusted</b> indicates that the <b>source</b> was one + * that we had more faith in and therefore the warning level should have higher + * severity. + */ +void +clock_skew_warning(const connection_t *conn, long apparent_skew, int trusted, + log_domain_mask_t domain, const char *received, + const char *source) +{ + char dbuf[64]; + char *ext_source = NULL; + format_time_interval(dbuf, sizeof(dbuf), apparent_skew); + if (conn) + tor_asprintf(&ext_source, "%s:%s:%d", source, conn->address, conn->port); + else + ext_source = tor_strdup(source); + log_fn(trusted ? LOG_WARN : LOG_INFO, domain, + "Received %s with skewed time (%s): " + "It seems that our clock is %s by %s, or that theirs is %s%s. " + "Tor requires an accurate clock to work: please check your time, " + "timezone, and date settings.", received, ext_source, + apparent_skew > 0 ? "ahead" : "behind", dbuf, + apparent_skew > 0 ? "behind" : "ahead", + (!conn || trusted) ? "" : ", or they are sending us the wrong time"); + if (trusted) + control_event_general_status(LOG_WARN, "CLOCK_SKEW SKEW=%ld SOURCE=%s", + apparent_skew, ext_source); + tor_free(ext_source); +} + diff --git a/src/or/connection.h b/src/or/connection.h index b6ff3d7bd6..59ea6d898e 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -146,12 +146,12 @@ static void connection_write_to_buf(const char *string, size_t len, /* DOCDOC connection_write_to_buf_zlib */ static void connection_write_to_buf_zlib(const char *string, size_t len, dir_connection_t *conn, int done); -static INLINE void +static inline void connection_write_to_buf(const char *string, size_t len, connection_t *conn) { connection_write_to_buf_impl_(string, len, conn, 0); } -static INLINE void +static inline void connection_write_to_buf_zlib(const char *string, size_t len, dir_connection_t *conn, int done) { @@ -163,7 +163,7 @@ static size_t connection_get_inbuf_len(connection_t *conn); /* DOCDOC connection_get_outbuf_len */ static size_t connection_get_outbuf_len(connection_t *conn); -static INLINE size_t +static inline size_t connection_get_inbuf_len(connection_t *conn) { IF_HAS_BUFFEREVENT(conn, { @@ -173,7 +173,7 @@ connection_get_inbuf_len(connection_t *conn) } } -static INLINE size_t +static inline size_t connection_get_outbuf_len(connection_t *conn) { IF_HAS_BUFFEREVENT(conn, { @@ -193,7 +193,57 @@ 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); dir_connection_t *connection_dir_get_by_purpose_and_resource( - int state, const char *resource); + int purpose, + const char *resource); +dir_connection_t *connection_dir_get_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state); +smartlist_t *connection_dir_list_by_purpose_and_resource( + int purpose, + const char *resource); +smartlist_t *connection_dir_list_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state); + +#define CONN_LEN_AND_FREE_TEMPLATE(sl) \ + STMT_BEGIN \ + int len = smartlist_len(sl); \ + smartlist_free(sl); \ + return len; \ + STMT_END + +/** Return a count of directory connections that are fetching the item + * described by <b>purpose</b>/<b>resource</b>. */ +static inline int +connection_dir_count_by_purpose_and_resource( + int purpose, + const char *resource) +{ + smartlist_t *conns = connection_dir_list_by_purpose_and_resource( + purpose, + resource); + CONN_LEN_AND_FREE_TEMPLATE(conns); +} + +/** Return a count of directory connections that are fetching the item + * described by <b>purpose</b>/<b>resource</b>/<b>state</b>. */ +static inline int +connection_dir_count_by_purpose_resource_and_state( + int purpose, + const char *resource, + int state) +{ + smartlist_t *conns = + connection_dir_list_by_purpose_resource_and_state( + purpose, + resource, + state); + CONN_LEN_AND_FREE_TEMPLATE(conns); +} + +#undef CONN_LEN_AND_FREE_TEMPLATE int any_other_active_or_conns(const or_connection_t *this_conn); @@ -210,6 +260,10 @@ int connection_or_nonopen_was_started_here(or_connection_t *conn); void connection_dump_buffer_mem_stats(int severity); void remove_file_if_very_old(const char *fname, time_t now); +void clock_skew_warning(const connection_t *conn, long apparent_skew, + int trusted, log_domain_mask_t domain, + const char *received, const char *source); + #ifdef USE_BUFFEREVENTS int connection_type_uses_bufferevent(connection_t *conn); void connection_configure_bufferevent_callbacks(connection_t *conn); @@ -235,6 +289,13 @@ void connection_buckets_note_empty_ts(uint32_t *timestamp_var, int tokens_before, size_t tokens_removed, const struct timeval *tvnow); +MOCK_DECL(STATIC int,connection_connect_sockaddr, + (connection_t *conn, + const struct sockaddr *sa, + socklen_t sa_len, + const struct sockaddr *bindaddr, + socklen_t bindaddr_len, + int *socket_error)); #endif #endif diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 729ef8a4c7..758d583c39 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -11,6 +11,9 @@ #define CONNECTION_EDGE_PRIVATE #include "or.h" + +#include "backtrace.h" + #include "addressmap.h" #include "buffers.h" #include "channel.h" @@ -503,6 +506,16 @@ connection_edge_finished_connecting(edge_connection_t *edge_conn) return connection_edge_process_inbuf(edge_conn, 1); } +/** A list of all the entry_connection_t * objects that are not marked + * for close, and are in AP_CONN_STATE_CIRCUIT_WAIT. + * + * (Right now, we check in several places to make sure that this list is + * correct. When it's incorrect, we'll fix it, and log a BUG message.) + */ +static smartlist_t *pending_entry_connections = NULL; + +static int untried_pending_connections = 0; + /** Common code to connection_(ap|exit)_about_to_close. */ static void connection_edge_about_to_close(edge_connection_t *edge_conn) @@ -525,6 +538,8 @@ connection_ap_about_to_close(entry_connection_t *entry_conn) edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(entry_conn); connection_t *conn = ENTRY_TO_CONN(entry_conn); + connection_edge_about_to_close(edge_conn); + if (entry_conn->socks_request->has_finished == 0) { /* since conn gets removed right after this function finishes, * there's no point trying to send back a reply at this point. */ @@ -543,6 +558,20 @@ connection_ap_about_to_close(entry_connection_t *entry_conn) conn->marked_for_close_file, conn->marked_for_close); dnsserv_reject_request(entry_conn); } + + if (TO_CONN(edge_conn)->state == AP_CONN_STATE_CIRCUIT_WAIT) { + smartlist_remove(pending_entry_connections, entry_conn); + } + +#if 1 + /* Check to make sure that this isn't in pending_entry_connections if it + * didn't actually belong there. */ + if (TO_CONN(edge_conn)->type == CONN_TYPE_AP) { + connection_ap_warn_and_unmark_if_pending_circ(entry_conn, + "about_to_close"); + } +#endif + control_event_stream_bandwidth(edge_conn); control_event_stream_status(entry_conn, STREAM_EVENT_CLOSED, edge_conn->end_reason); @@ -711,26 +740,181 @@ connection_ap_expire_beginning(void) } SMARTLIST_FOREACH_END(base_conn); } -/** Tell any AP streams that are waiting for a new circuit to try again, - * either attaching to an available circ or launching a new one. +/** + * As connection_ap_attach_pending, but first scans the entire connection + * array to see if any elements are missing. */ void -connection_ap_attach_pending(void) +connection_ap_rescan_and_attach_pending(void) { entry_connection_t *entry_conn; smartlist_t *conns = get_connection_array(); + + if (PREDICT_UNLIKELY(NULL == pending_entry_connections)) + pending_entry_connections = smartlist_new(); + SMARTLIST_FOREACH_BEGIN(conns, connection_t *, conn) { if (conn->marked_for_close || conn->type != CONN_TYPE_AP || conn->state != AP_CONN_STATE_CIRCUIT_WAIT) continue; + entry_conn = TO_ENTRY_CONN(conn); + tor_assert(entry_conn); + if (! smartlist_contains(pending_entry_connections, entry_conn)) { + log_warn(LD_BUG, "Found a connection %p that was supposed to be " + "in pending_entry_connections, but wasn't. No worries; " + "adding it.", + pending_entry_connections); + untried_pending_connections = 1; + connection_ap_mark_as_pending_circuit(entry_conn); + } + + } SMARTLIST_FOREACH_END(conn); + + connection_ap_attach_pending(1); +} + +#ifdef DEBUGGING_17659 +#define UNMARK() do { \ + entry_conn->marked_pending_circ_line = 0; \ + entry_conn->marked_pending_circ_file = 0; \ + } while (0) +#else +#define UNMARK() do { } while (0) +#endif + +/** Tell any AP streams that are listed as waiting for a new circuit to try + * again, either attaching to an available circ or launching a new one. + * + * If <b>retry</b> is false, only check the list if it contains at least one + * streams that we have not yet tried to attach to a circuit. + */ +void +connection_ap_attach_pending(int retry) +{ + if (PREDICT_UNLIKELY(!pending_entry_connections)) { + return; + } + + if (untried_pending_connections == 0 && !retry) + return; + + /* Don't allow modifications to pending_entry_connections while we are + * iterating over it. */ + smartlist_t *pending = pending_entry_connections; + pending_entry_connections = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(pending, + entry_connection_t *, entry_conn) { + connection_t *conn = ENTRY_TO_CONN(entry_conn); + tor_assert(conn && entry_conn); + if (conn->marked_for_close) { + UNMARK(); + continue; + } + if (conn->magic != ENTRY_CONNECTION_MAGIC) { + log_warn(LD_BUG, "%p has impossible magic value %u.", + entry_conn, (unsigned)conn->magic); + UNMARK(); + continue; + } + if (conn->state != AP_CONN_STATE_CIRCUIT_WAIT) { + log_warn(LD_BUG, "%p is no longer in circuit_wait. Its current state " + "is %s. Why is it on pending_entry_connections?", + entry_conn, + conn_state_to_string(conn->type, conn->state)); + UNMARK(); + continue; + } + if (connection_ap_handshake_attach_circuit(entry_conn) < 0) { if (!conn->marked_for_close) connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_CANT_ATTACH); } - } SMARTLIST_FOREACH_END(conn); + + if (! conn->marked_for_close && + conn->type == CONN_TYPE_AP && + conn->state == AP_CONN_STATE_CIRCUIT_WAIT) { + if (!smartlist_contains(pending_entry_connections, entry_conn)) { + smartlist_add(pending_entry_connections, entry_conn); + continue; + } + } + + UNMARK(); + } SMARTLIST_FOREACH_END(entry_conn); + + smartlist_free(pending); + untried_pending_connections = 0; +} + +/** Mark <b>entry_conn</b> as needing to get attached to a circuit. + * + * And <b>entry_conn</b> must be in AP_CONN_STATE_CIRCUIT_WAIT, + * should not already be pending a circuit. The circuit will get + * launched or the connection will get attached the next time we + * call connection_ap_attach_pending(). + */ +void +connection_ap_mark_as_pending_circuit_(entry_connection_t *entry_conn, + const char *fname, int lineno) +{ + connection_t *conn = ENTRY_TO_CONN(entry_conn); + tor_assert(conn->state == AP_CONN_STATE_CIRCUIT_WAIT); + tor_assert(conn->magic == ENTRY_CONNECTION_MAGIC); + if (conn->marked_for_close) + return; + + if (PREDICT_UNLIKELY(NULL == pending_entry_connections)) + pending_entry_connections = smartlist_new(); + + if (PREDICT_UNLIKELY(smartlist_contains(pending_entry_connections, + entry_conn))) { + log_warn(LD_BUG, "What?? pending_entry_connections already contains %p! " + "(Called from %s:%d.)", + entry_conn, fname, lineno); +#ifdef DEBUGGING_17659 + const char *f2 = entry_conn->marked_pending_circ_file; + log_warn(LD_BUG, "(Previously called from %s:%d.)\n", + f2 ? f2 : "<NULL>", + entry_conn->marked_pending_circ_line); +#endif + log_backtrace(LOG_WARN, LD_BUG, "To debug, this may help"); + return; + } + +#ifdef DEBUGGING_17659 + entry_conn->marked_pending_circ_line = (uint16_t) lineno; + entry_conn->marked_pending_circ_file = fname; +#endif + + untried_pending_connections = 1; + smartlist_add(pending_entry_connections, entry_conn); +} + +/** Mark <b>entry_conn</b> as no longer waiting for a circuit. */ +void +connection_ap_mark_as_non_pending_circuit(entry_connection_t *entry_conn) +{ + if (PREDICT_UNLIKELY(NULL == pending_entry_connections)) + return; + UNMARK(); + smartlist_remove(pending_entry_connections, entry_conn); +} + +/** DOCDOC */ +void +connection_ap_warn_and_unmark_if_pending_circ(entry_connection_t *entry_conn, + const char *where) +{ + if (pending_entry_connections && + smartlist_contains(pending_entry_connections, entry_conn)) { + log_warn(LD_BUG, "What was %p doing in pending_entry_connections in %s?", + entry_conn, where); + connection_ap_mark_as_non_pending_circuit(entry_conn); + } } /** Tell any AP streams that are waiting for a one-hop tunnel to @@ -851,12 +1035,13 @@ connection_ap_detach_retriable(entry_connection_t *conn, * a tunneled directory connection, then just attach it. */ ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_CIRCUIT_WAIT; circuit_detach_stream(TO_CIRCUIT(circ),ENTRY_TO_EDGE_CONN(conn)); - return connection_ap_handshake_attach_circuit(conn); + connection_ap_mark_as_pending_circuit(conn); } else { + CONNECTION_AP_EXPECT_NONPENDING(conn); ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; circuit_detach_stream(TO_CIRCUIT(circ),ENTRY_TO_EDGE_CONN(conn)); - return 0; } + return 0; } /** Check if <b>conn</b> is using a dangerous port. Then warn and/or @@ -905,6 +1090,7 @@ connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, const or_options_t *options = get_options(); if (options->LeaveStreamsUnattached) { + CONNECTION_AP_EXPECT_NONPENDING(conn); ENTRY_TO_CONN(conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; return 0; } @@ -1454,10 +1640,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, /* If we were given a circuit to attach to, try to attach. Otherwise, * try to find a good one and attach to that. */ int rv; - if (circ) - rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath); - else - rv = connection_ap_handshake_attach_circuit(conn); + if (circ) { + rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath); + } else { + connection_ap_mark_as_pending_circuit(conn); + rv = 0; + } /* If the above function returned 0 then we're waiting for a circuit. * if it returned 1, we're attached. Both are okay. But if it returned @@ -1554,6 +1742,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, * Also, a fetch could have been requested if the onion address was not * found in the cache previously. */ if (refetch_desc || !rend_client_any_intro_points_usable(entry)) { + connection_ap_mark_as_non_pending_circuit(conn); base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; log_info(LD_REND, "Unknown descriptor %s. Fetching.", safe_str_client(rend_data->onion_address)); @@ -1564,11 +1753,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, /* We have the descriptor so launch a connection to the HS. */ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; log_info(LD_REND, "Descriptor is here. Great."); - if (connection_ap_handshake_attach_circuit(conn) < 0) { - if (!base_conn->marked_for_close) - connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); - return -1; - } + connection_ap_mark_as_pending_circuit(conn); return 0; } @@ -2324,12 +2509,7 @@ connection_ap_make_link(connection_t *partner, control_event_stream_status(conn, STREAM_EVENT_NEW, 0); /* attaching to a dirty circuit is fine */ - if (connection_ap_handshake_attach_circuit(conn) < 0) { - if (!base_conn->marked_for_close) - connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); - return NULL; - } - + connection_ap_mark_as_pending_circuit(conn); log_info(LD_APP,"... application connection created and linked."); return conn; } @@ -3478,3 +3658,12 @@ circuit_clear_isolation(origin_circuit_t *circ) circ->socks_username_len = circ->socks_password_len = 0; } +/** Free all storage held in module-scoped variables for connection_edge.c */ +void +connection_edge_free_all(void) +{ + untried_pending_connections = 0; + smartlist_free(pending_entry_connections); + pending_entry_connections = NULL; +} + diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 7c0b9c0767..2aba801461 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -64,7 +64,20 @@ int connection_edge_is_rendezvous_stream(edge_connection_t *conn); int connection_ap_can_use_exit(const entry_connection_t *conn, const node_t *exit); void connection_ap_expire_beginning(void); -void connection_ap_attach_pending(void); +void connection_ap_rescan_and_attach_pending(void); +void connection_ap_attach_pending(int retry); +void connection_ap_mark_as_pending_circuit_(entry_connection_t *entry_conn, + const char *file, int line); +#define connection_ap_mark_as_pending_circuit(c) \ + connection_ap_mark_as_pending_circuit_((c), __FILE__, __LINE__) +void connection_ap_mark_as_non_pending_circuit(entry_connection_t *entry_conn); +#define CONNECTION_AP_EXPECT_NONPENDING(c) do { \ + if (ENTRY_TO_CONN(c)->state == AP_CONN_STATE_CIRCUIT_WAIT) { \ + log_warn(LD_BUG, "At %s:%d: %p was unexpectedly in circuit_wait.", \ + __FILE__, __LINE__, (c)); \ + connection_ap_mark_as_non_pending_circuit(c); \ + } \ + } while (0) void connection_ap_fail_onehop(const char *failed_digest, cpath_build_state_t *build_state); void circuit_discard_optional_exit_enclaves(extend_info_t *info); @@ -100,6 +113,12 @@ int connection_edge_update_circuit_isolation(const entry_connection_t *conn, void circuit_clear_isolation(origin_circuit_t *circ); streamid_t get_unique_stream_id_by_circ(origin_circuit_t *circ); +void connection_edge_free_all(void); + +void connection_ap_warn_and_unmark_if_pending_circ( + entry_connection_t *entry_conn, + const char *where); + /** @name Begin-cell flags * * These flags are used in RELAY_BEGIN cells to change the default behavior diff --git a/src/or/connection_or.c b/src/or/connection_or.c index a967c93aca..73e4d19369 100644 --- a/src/or/connection_or.c +++ b/src/or/connection_or.c @@ -1450,17 +1450,12 @@ connection_tls_continue_handshake(or_connection_t *conn) { int result; check_no_tls_errors(); - again: - if (conn->base_.state == OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING) { - // log_notice(LD_OR, "Renegotiate with %p", conn->tls); - result = tor_tls_renegotiate(conn->tls); - // log_notice(LD_OR, "Result: %d", result); - } else { - tor_assert(conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING); - // log_notice(LD_OR, "Continue handshake with %p", conn->tls); - result = tor_tls_handshake(conn->tls); - // log_notice(LD_OR, "Result: %d", result); - } + + tor_assert(conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING); + // log_notice(LD_OR, "Continue handshake with %p", conn->tls); + result = tor_tls_handshake(conn->tls); + // log_notice(LD_OR, "Result: %d", result); + switch (result) { CASE_TOR_TLS_ERROR_ANY: log_info(LD_OR,"tls error [%s]. breaking connection.", @@ -1469,23 +1464,10 @@ connection_tls_continue_handshake(or_connection_t *conn) case TOR_TLS_DONE: if (! tor_tls_used_v1_handshake(conn->tls)) { if (!tor_tls_is_server(conn->tls)) { - if (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) { - if (tor_tls_received_v3_certificate(conn->tls)) { - log_info(LD_OR, "Client got a v3 cert! Moving on to v3 " - "handshake with ciphersuite %s", - tor_tls_get_ciphersuite_name(conn->tls)); - return connection_or_launch_v3_or_handshake(conn); - } else { - log_debug(LD_OR, "Done with initial SSL handshake (client-side)." - " Requesting renegotiation."); - connection_or_change_state(conn, - OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING); - goto again; - } - } - // log_notice(LD_OR,"Done. state was %d.", conn->base_.state); + tor_assert(conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING); + return connection_or_launch_v3_or_handshake(conn); } else { - /* v2/v3 handshake, but not a client. */ + /* v2/v3 handshake, but we are not a client. */ log_debug(LD_OR, "Done with initial SSL handshake (server-side). " "Expecting renegotiation or VERSIONS cell"); tor_tls_set_renegotiate_callback(conn->tls, @@ -1498,6 +1480,7 @@ connection_tls_continue_handshake(or_connection_t *conn) return 0; } } + tor_assert(tor_tls_is_server(conn->tls)); return connection_tls_finish_handshake(conn); case TOR_TLS_WANTWRITE: connection_start_writing(TO_CONN(conn)); @@ -1533,22 +1516,8 @@ connection_or_handle_event_cb(struct bufferevent *bufev, short event, if (! tor_tls_used_v1_handshake(conn->tls)) { if (!tor_tls_is_server(conn->tls)) { if (conn->base_.state == OR_CONN_STATE_TLS_HANDSHAKING) { - if (tor_tls_received_v3_certificate(conn->tls)) { - log_info(LD_OR, "Client got a v3 cert!"); - if (connection_or_launch_v3_or_handshake(conn) < 0) - connection_or_close_for_error(conn, 0); - return; - } else { - connection_or_change_state(conn, - OR_CONN_STATE_TLS_CLIENT_RENEGOTIATING); - tor_tls_unblock_renegotiation(conn->tls); - if (bufferevent_ssl_renegotiate(conn->base_.bufev)<0) { - log_warn(LD_OR, "Start_renegotiating went badly."); - connection_or_close_for_error(conn, 0); - } - tor_tls_unblock_renegotiation(conn->tls); - return; /* ???? */ - } + if (connection_or_launch_v3_or_handshake(conn) < 0) + connection_or_close_for_error(conn, 0); } } else { const int handshakes = tor_tls_get_num_server_handshakes(conn->tls); @@ -1800,6 +1769,8 @@ connection_tls_finish_handshake(or_connection_t *conn) char digest_rcvd[DIGEST_LEN]; int started_here = connection_or_nonopen_was_started_here(conn); + tor_assert(!started_here); + log_debug(LD_HANDSHAKE,"%s tls handshake on %p with %s done, using " "ciphersuite %s. verifying.", started_here?"outgoing":"incoming", @@ -1815,10 +1786,8 @@ connection_tls_finish_handshake(or_connection_t *conn) if (tor_tls_used_v1_handshake(conn->tls)) { conn->link_proto = 1; - if (!started_here) { - connection_or_init_conn_from_address(conn, &conn->base_.addr, - conn->base_.port, digest_rcvd, 0); - } + connection_or_init_conn_from_address(conn, &conn->base_.addr, + conn->base_.port, digest_rcvd, 0); tor_tls_block_renegotiation(conn->tls); rep_hist_note_negotiated_link_proto(1, started_here); return connection_or_set_state_open(conn); @@ -1826,10 +1795,8 @@ connection_tls_finish_handshake(or_connection_t *conn) connection_or_change_state(conn, OR_CONN_STATE_OR_HANDSHAKING_V2); if (connection_init_or_handshake_state(conn, started_here) < 0) return -1; - if (!started_here) { - connection_or_init_conn_from_address(conn, &conn->base_.addr, - conn->base_.port, digest_rcvd, 0); - } + connection_or_init_conn_from_address(conn, &conn->base_.addr, + conn->base_.port, digest_rcvd, 0); return connection_or_send_versions(conn, 0); } } @@ -1844,7 +1811,6 @@ static int connection_or_launch_v3_or_handshake(or_connection_t *conn) { tor_assert(connection_or_nonopen_was_started_here(conn)); - tor_assert(tor_tls_received_v3_certificate(conn->tls)); circuit_build_times_network_is_live(get_circuit_build_times_mutable()); @@ -2290,8 +2256,7 @@ connection_or_send_auth_challenge_cell(or_connection_t *conn) auth_challenge_cell_t *ac = auth_challenge_cell_new(); - if (crypto_rand((char*)ac->challenge, sizeof(ac->challenge)) < 0) - goto done; + crypto_rand((char*)ac->challenge, sizeof(ac->challenge)); auth_challenge_cell_add_methods(ac, AUTHTYPE_RSA_SHA256_TLSSECRET); auth_challenge_cell_set_n_methods(ac, diff --git a/src/or/control.c b/src/or/control.c index 220e7e514f..66182fe2a4 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -192,7 +192,7 @@ static void flush_queued_events_cb(evutil_socket_t fd, short what, void *arg); /** Given a control event code for a message event, return the corresponding * log severity. */ -static INLINE int +static inline int event_to_log_severity(int event) { switch (event) { @@ -206,7 +206,7 @@ event_to_log_severity(int event) } /** Given a log severity, return the corresponding control event code. */ -static INLINE int +static inline int log_severity_to_event(int severity) { switch (severity) { @@ -325,7 +325,7 @@ control_event_is_interesting(int event) /** Append a NUL-terminated string <b>s</b> to the end of * <b>conn</b>-\>outbuf. */ -static INLINE void +static inline void connection_write_str_to_buf(const char *s, control_connection_t *conn) { size_t len = strlen(s); @@ -428,7 +428,7 @@ read_escaped_data(const char *data, size_t len, char **out) /** If the first <b>in_len_max</b> characters in <b>start</b> contain a * double-quoted string with escaped characters, return the length of that * string (as encoded, including quotes). Otherwise return -1. */ -static INLINE int +static inline int get_escaped_string_length(const char *start, size_t in_len_max, int *chars_out) { @@ -1927,6 +1927,22 @@ getinfo_helper_dir(control_connection_t *control_conn, *errmsg = "Not found in cache"; return -1; } + } else if (!strcmpstart(question, "hs/service/desc/id/")) { + rend_cache_entry_t *e = NULL; + + question += strlen("hs/service/desc/id/"); + if (strlen(question) != REND_SERVICE_ID_LEN_BASE32) { + *errmsg = "Invalid address"; + return -1; + } + + if (!rend_cache_lookup_v2_desc_as_service(question, &e)) { + /* Descriptor found in cache */ + *answer = tor_strdup(e->desc); + } else { + *errmsg = "Not found in cache"; + return -1; + } } else if (!strcmpstart(question, "md/id/")) { const node_t *node = node_get_by_hex_id(question+strlen("md/id/")); const microdesc_t *md = NULL; @@ -2481,6 +2497,8 @@ static const getinfo_item_t getinfo_items[] = { PREFIX("extra-info/digest/", dir, "Extra-info documents by digest."), PREFIX("hs/client/desc/id", dir, "Hidden Service descriptor in client's cache by onion."), + PREFIX("hs/service/desc/id/", dir, + "Hidden Service descriptor in services's cache by onion."), PREFIX("net/listeners/", listeners, "Bound addresses by type"), ITEM("ns/all", networkstatus, "Brief summary of router status (v2 directory format)"), @@ -2544,6 +2562,12 @@ static const getinfo_item_t getinfo_items[] = { "v3 Networkstatus consensus as retrieved from a DirPort."), ITEM("exit-policy/default", policies, "The default value appended to the configured exit policy."), + ITEM("exit-policy/reject-private/default", policies, + "The default rules appended to the configured exit policy by" + " ExitPolicyRejectPrivate."), + ITEM("exit-policy/reject-private/relay", policies, + "The relay-specific rules appended to the configured exit policy by" + " ExitPolicyRejectPrivate."), ITEM("exit-policy/full", policies, "The entire exit policy of onion router"), ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"), ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"), @@ -2987,6 +3011,7 @@ handle_control_attachstream(control_connection_t *conn, uint32_t len, edge_conn->end_reason = 0; if (tmpcirc) circuit_detach_stream(tmpcirc, edge_conn); + CONNECTION_AP_EXPECT_NONPENDING(ap_conn); TO_CONN(edge_conn)->state = AP_CONN_STATE_CONTROLLER_WAIT; } @@ -3418,8 +3443,7 @@ handle_control_authchallenge(control_connection_t *conn, uint32_t len, tor_free(client_nonce); return -1; } - const int fail = crypto_rand(server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); - tor_assert(!fail); + crypto_rand(server_nonce, SAFECOOKIE_SERVER_NONCE_LEN); /* Now compute and send the server-to-controller response, and the * server's nonce. */ @@ -6233,6 +6257,31 @@ get_desc_id_from_query(const rend_data_t *rend_data, const char *hsdir_fp) return desc_id; } +/** send HS_DESC CREATED event when a local service generates a descriptor. + * + * <b>service_id</b> is the descriptor onion address. + * <b>desc_id_base32</b> is the descriptor ID. + * <b>replica</b> is the the descriptor replica number. + */ +void +control_event_hs_descriptor_created(const char *service_id, + const char *desc_id_base32, + int replica) +{ + if (!service_id || !desc_id_base32) { + log_warn(LD_BUG, "Called with service_digest==%p, " + "desc_id_base32==%p", service_id, desc_id_base32); + return; + } + + send_control_event(EVENT_HS_DESC, + "650 HS_DESC CREATED %s UNKNOWN UNKNOWN %s " + "REPLICA=%d\r\n", + service_id, + desc_id_base32, + replica); +} + /** send HS_DESC upload event. * * <b>service_id</b> is the descriptor onion address. diff --git a/src/or/control.h b/src/or/control.h index fdf7903cb8..1f8e2bcdc6 100644 --- a/src/or/control.h +++ b/src/or/control.h @@ -117,6 +117,9 @@ MOCK_DECL(const char *, node_describe_longname_by_id,(const char *id_digest)); void control_event_hs_descriptor_requested(const rend_data_t *rend_query, const char *desc_id_base32, const char *hs_dir); +void control_event_hs_descriptor_created(const char *service_id, + const char *desc_id_base32, + int replica); void control_event_hs_descriptor_upload(const char *service_id, const char *desc_id_base32, const char *hs_dir); diff --git a/src/or/directory.c b/src/or/directory.c index 9461606f1b..e1bdcb0151 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -143,7 +143,7 @@ purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose) /** Return a newly allocated string describing <b>auth</b>. Only describes * authority features. */ -static char * +STATIC char * authdir_type_to_string(dirinfo_type_t auth) { char *result; @@ -162,7 +162,7 @@ authdir_type_to_string(dirinfo_type_t auth) } /** Return a string describing a given directory connection purpose. */ -static const char * +STATIC const char * dir_conn_purpose_to_string(int purpose) { switch (purpose) @@ -370,7 +370,7 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, /** Return true iff, according to the values in <b>options</b>, we should be * using directory guards for direct downloads of directory information. */ -static int +STATIC int should_use_directory_guards(const or_options_t *options) { /* Public (non-bridge) servers never use directory guards. */ @@ -425,14 +425,17 @@ directory_pick_generic_dirserver(dirinfo_type_t type, int pds_flags, * Use <b>pds_flags</b> as arguments to router_pick_directory_server() * or router_pick_trusteddirserver(). */ -MOCK_IMPL(void, directory_get_from_dirserver, (uint8_t dir_purpose, - uint8_t router_purpose, - const char *resource, - int pds_flags)) +MOCK_IMPL(void, directory_get_from_dirserver, ( + uint8_t dir_purpose, + uint8_t router_purpose, + const char *resource, + int pds_flags, + download_want_authority_t want_authority)) { const routerstatus_t *rs = NULL; const or_options_t *options = get_options(); - int prefer_authority = directory_fetches_from_authorities(options); + int prefer_authority = (directory_fetches_from_authorities(options) + || want_authority == DL_WANT_AUTHORITY); int require_authority = 0; int get_via_tor = purpose_needs_anonymity(dir_purpose, router_purpose); dirinfo_type_t type = dir_fetch_type(dir_purpose, router_purpose, resource); @@ -666,15 +669,15 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, * When fetching a rendezvous descriptor, <b>resource</b> is the service ID we * want to fetch. */ -void -directory_initiate_command_routerstatus(const routerstatus_t *status, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since) +MOCK_IMPL(void, directory_initiate_command_routerstatus, + (const routerstatus_t *status, + uint8_t dir_purpose, + uint8_t router_purpose, + dir_indirection_t indirection, + const char *resource, + const char *payload, + size_t payload_len, + time_t if_modified_since)) { directory_initiate_command_routerstatus_rend(status, dir_purpose, router_purpose, @@ -940,6 +943,15 @@ directory_initiate_command_rend(const tor_addr_t *_addr, log_debug(LD_DIR, "anonymized %d, use_begindir %d.", anonymized_connection, use_begindir); + if (!dir_port && !use_begindir) { + char ipaddr[TOR_ADDR_BUF_LEN]; + tor_addr_to_str(ipaddr, _addr, TOR_ADDR_BUF_LEN, 0); + log_warn(LD_BUG, "Cannot use directory server without dirport or " + "begindir! Address: %s, ORPort: %d, DirPort: %d", + escaped_safe_str_client(ipaddr), or_port, dir_port); + return; + } + log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose)); #ifndef NON_ANONYMOUS_MODE_ENABLED @@ -958,6 +970,12 @@ directory_initiate_command_rend(const tor_addr_t *_addr, return; } + /* ensure we don't make excess connections when we're already downloading + * a consensus during bootstrap */ + if (connection_dir_avoid_extra_connection_for_purpose(dir_purpose)) { + return; + } + conn = dir_connection_new(tor_addr_family(&addr)); /* set up conn so it's got all the data we need to remember */ @@ -991,16 +1009,16 @@ directory_initiate_command_rend(const tor_addr_t *_addr, switch (connection_connect(TO_CONN(conn), conn->base_.address, &addr, dir_port, &socket_error)) { case -1: - connection_dir_request_failed(conn); /* retry if we want */ - /* XXX we only pass 'conn' above, not 'resource', 'payload', - * etc. So in many situations it can't retry! -RD */ - connection_free(TO_CONN(conn)); + connection_mark_for_close(TO_CONN(conn)); return; case 1: /* start flushing conn */ conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* fall through */ case 0: + if (connection_dir_close_consensus_conn_if_extra(conn)) { + return; + } /* queue the command on the outbuf */ directory_send_command(conn, dir_purpose, 1, resource, payload, payload_len, @@ -1044,6 +1062,9 @@ directory_initiate_command_rend(const tor_addr_t *_addr, connection_mark_for_close(TO_CONN(conn)); return; } + if (connection_dir_close_consensus_conn_if_extra(conn)) { + return; + } conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* queue the command on the outbuf */ directory_send_command(conn, dir_purpose, 0, resource, @@ -1598,7 +1619,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn) size_t body_len = 0, orig_len = 0; int status_code; time_t date_header = 0; - long delta; + long apparent_skew; compress_method_t compression; int plausible; int skewed = 0; @@ -1657,28 +1678,15 @@ connection_dir_client_reached_eof(dir_connection_t *conn) * and the date header. (We used to check now-date_header, but that's * inaccurate if we spend a lot of time downloading.) */ - delta = conn->base_.timestamp_lastwritten - date_header; - if (labs(delta)>ALLOW_DIRECTORY_TIME_SKEW) { - char dbuf[64]; + apparent_skew = conn->base_.timestamp_lastwritten - date_header; + if (labs(apparent_skew)>ALLOW_DIRECTORY_TIME_SKEW) { int trusted = router_digest_is_trusted_dir(conn->identity_digest); - format_time_interval(dbuf, sizeof(dbuf), delta); - log_fn(trusted ? LOG_WARN : LOG_INFO, - LD_HTTP, - "Received directory with skewed time (server '%s:%d'): " - "It seems that our clock is %s by %s, or that theirs is %s. " - "Tor requires an accurate clock to work: please check your time, " - "timezone, and date settings.", - conn->base_.address, conn->base_.port, - delta>0 ? "ahead" : "behind", dbuf, - delta>0 ? "behind" : "ahead"); + clock_skew_warning(TO_CONN(conn), apparent_skew, trusted, LD_HTTP, + "directory", "DIRSERV"); skewed = 1; /* don't check the recommended-versions line */ - if (trusted) - control_event_general_status(LOG_WARN, - "CLOCK_SKEW SKEW=%ld SOURCE=DIRSERV:%s:%d", - delta, conn->base_.address, conn->base_.port); } else { log_debug(LD_HTTP, "Time on received directory is within tolerance; " - "we are %ld seconds skewed. (That's okay.)", delta); + "we are %ld seconds skewed. (That's okay.)", apparent_skew); } } (void) skewed; /* skewed isn't used yet. */ @@ -2593,7 +2601,7 @@ client_likes_consensus(networkstatus_t *v, const char *want_url) /** Return the compression level we should use for sending a compressed * response of size <b>n_bytes</b>. */ -static zlib_compression_level_t +STATIC zlib_compression_level_t choose_compression_level(ssize_t n_bytes) { if (! have_been_under_memory_pressure()) { @@ -2614,7 +2622,7 @@ choose_compression_level(ssize_t n_bytes) * service descriptor. On finding one, write a response into * conn-\>outbuf. If the request is unrecognized, send a 400. * Always return 0. */ -static int +STATIC int directory_handle_command_get(dir_connection_t *conn, const char *headers, const char *req_body, size_t req_body_len) { @@ -2874,7 +2882,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, }); if (global_write_bucket_low(TO_CONN(conn), estimated_len, 2)) { - write_http_status_line(conn, 503, "Directory busy, try again later."); + write_http_status_line(conn, 503, "Directory busy, try again later"); goto vote_done; } write_http_response_header(conn, body_len ? body_len : -1, compressed, @@ -3071,7 +3079,7 @@ directory_handle_command_get(dir_connection_t *conn, const char *headers, len += c->cache_info.signed_descriptor_len); if (global_write_bucket_low(TO_CONN(conn), compressed?len/2:len, 2)) { - write_http_status_line(conn, 503, "Directory busy, try again later."); + write_http_status_line(conn, 503, "Directory busy, try again later"); goto keys_done; } @@ -3398,7 +3406,7 @@ connection_dir_finished_flushing(dir_connection_t *conn) tor_assert(conn->base_.type == CONN_TYPE_DIR); /* Note that we have finished writing the directory response. For direct - * connections this means we're done, for tunneled connections its only + * connections this means we're done; for tunneled connections it's only * an intermediate step. */ if (conn->dirreq_id) geoip_change_dirreq_state(conn->dirreq_id, DIRREQ_TUNNELED, @@ -3439,8 +3447,205 @@ connection_dir_finished_flushing(dir_connection_t *conn) return 0; } +/* A helper function for connection_dir_close_consensus_conn_if_extra() + * and connection_dir_close_extra_consensus_conns() that returns 0 if + * we can't have, or don't want to close, excess consensus connections. */ +STATIC int +connection_dir_would_close_consensus_conn_helper(void) +{ + const or_options_t *options = get_options(); + + /* we're only interested in closing excess connections if we could + * have created any in the first place */ + if (!networkstatus_consensus_can_use_multiple_directories(options)) { + return 0; + } + + /* We want to close excess connections downloading a consensus. + * If there aren't any excess, we don't have anything to close. */ + if (!networkstatus_consensus_has_excess_connections()) { + return 0; + } + + /* If we have excess connections, but none of them are downloading a + * consensus, and we are still bootstrapping (that is, we have no usable + * consensus), we don't want to close any until one starts downloading. */ + if (!networkstatus_consensus_is_downloading_usable_flavor() + && networkstatus_consensus_is_boostrapping(time(NULL))) { + return 0; + } + + /* If we have just stopped bootstrapping (that is, just parsed a consensus), + * we might still have some excess connections hanging around. So we still + * have to check if we want to close any, even if we've stopped + * bootstrapping. */ + return 1; +} + +/* Check if we would close excess consensus connections. If we would, any + * new consensus connection would become excess immediately, so return 1. + * Otherwise, return 0. */ +int +connection_dir_avoid_extra_connection_for_purpose(unsigned int purpose) +{ + const or_options_t *options = get_options(); + + /* We're not interested in connections that aren't fetching a consensus. */ + if (purpose != DIR_PURPOSE_FETCH_CONSENSUS) { + return 0; + } + + /* we're only interested in avoiding excess connections if we could + * have created any in the first place */ + if (!networkstatus_consensus_can_use_multiple_directories(options)) { + return 0; + } + + /* If there are connections downloading a consensus, and we are still + * bootstrapping (that is, we have no usable consensus), we can be sure that + * any further connections would be excess. */ + if (networkstatus_consensus_is_downloading_usable_flavor() + && networkstatus_consensus_is_boostrapping(time(NULL))) { + return 1; + } + + return 0; +} + +/* Check if we have excess consensus download connection attempts, and close + * conn: + * - if we don't have a consensus, and we're downloading a consensus, and conn + * is not downloading a consensus yet, close it; + * - if we do have a consensus, conn is excess, close it. */ +int +connection_dir_close_consensus_conn_if_extra(dir_connection_t *conn) +{ + tor_assert(conn); + tor_assert(conn->base_.type == CONN_TYPE_DIR); + + /* We're not interested in connections that aren't fetching a consensus. */ + if (conn->base_.purpose != DIR_PURPOSE_FETCH_CONSENSUS) { + return 0; + } + + /* The connection has already been closed */ + if (conn->base_.marked_for_close) { + return 0; + } + + if (!connection_dir_would_close_consensus_conn_helper()) { + return 0; + } + + const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + time(NULL)); + + /* We don't want to check other connections to see if they are downloading, + * as this is prone to race-conditions. So leave it for + * connection_dir_consider_close_extra_consensus_conns() to clean up. + * + * But if conn has just started connecting, or we have a consensus already, + * we can be sure it's not needed any more. */ + if (!we_are_bootstrapping + || conn->base_.state == DIR_CONN_STATE_CONNECTING) { + connection_close_immediate(&conn->base_); + connection_mark_for_close(&conn->base_); + return -1; + } + + return 0; +} + +/* Check if we have excess consensus download connection attempts, and close + * them: + * - if we don't have a consensus, and we're downloading a consensus, keep an + * earlier connection, or a connection to a fallback directory, and close + * all other connections; + * - if we do have a consensus, close all connections: they are all excess. */ +void +connection_dir_close_extra_consensus_conns(void) +{ + if (!connection_dir_would_close_consensus_conn_helper()) { + return; + } + + int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + time(NULL)); + + const char *usable_resource = networkstatus_get_flavor_name( + usable_consensus_flavor()); + smartlist_t *consens_usable_conns = + connection_dir_list_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource); + + /* If we want to keep a connection that's downloading, find a connection to + * keep, favouring: + * - connections opened earlier (they are likely to have progressed further) + * - connections to fallbacks (to reduce the load on authorities) */ + dir_connection_t *kept_download_conn = NULL; + int kept_is_authority = 0; + if (we_are_bootstrapping) { + SMARTLIST_FOREACH_BEGIN(consens_usable_conns, + dir_connection_t *, d) { + tor_assert(d); + int d_is_authority = router_digest_is_trusted_dir(d->identity_digest); + /* keep the first connection that is past the connecting state, but + * prefer fallbacks. */ + if (d->base_.state != DIR_CONN_STATE_CONNECTING) { + if (!kept_download_conn || (kept_is_authority && !d_is_authority)) { + kept_download_conn = d; + kept_is_authority = d_is_authority; + /* we've found the earliest fallback, and want to keep it regardless + * of any other connections */ + if (!kept_is_authority) + break; + } + } + } SMARTLIST_FOREACH_END(d); + } + + SMARTLIST_FOREACH_BEGIN(consens_usable_conns, + dir_connection_t *, d) { + tor_assert(d); + /* don't close this connection if it's the one we want to keep */ + if (kept_download_conn && d == kept_download_conn) + continue; + /* mark all other connections for close */ + if (!d->base_.marked_for_close) { + connection_close_immediate(&d->base_); + connection_mark_for_close(&d->base_); + } + } SMARTLIST_FOREACH_END(d); + + smartlist_free(consens_usable_conns); + consens_usable_conns = NULL; + + /* make sure we've closed all excess connections */ + const int final_connecting_conn_count = + connection_dir_count_by_purpose_resource_and_state( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource, + DIR_CONN_STATE_CONNECTING); + if (final_connecting_conn_count > 0) { + log_warn(LD_BUG, "Expected 0 consensus connections connecting after " + "cleanup, got %d.", final_connecting_conn_count); + } + const int expected_final_conn_count = (we_are_bootstrapping ? 1 : 0); + const int final_conn_count = + connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource); + if (final_conn_count > expected_final_conn_count) { + log_warn(LD_BUG, "Expected %d consensus connections after cleanup, got " + "%d.", expected_final_conn_count, final_connecting_conn_count); + } +} + /** Connected handler for directory connections: begin sending data to the - * server */ + * server, and return 0, or, if the connection is an excess bootstrap + * connection, close all excess bootstrap connections. + * Only used when connections don't immediately connect. */ int connection_dir_finished_connecting(dir_connection_t *conn) { @@ -3451,31 +3656,63 @@ connection_dir_finished_connecting(dir_connection_t *conn) log_debug(LD_HTTP,"Dir connection to router %s:%u established.", conn->base_.address,conn->base_.port); - conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */ + if (connection_dir_close_consensus_conn_if_extra(conn)) { + return -1; + } + + /* start flushing conn */ + conn->base_.state = DIR_CONN_STATE_CLIENT_SENDING; return 0; } /** Decide which download schedule we want to use based on descriptor type - * in <b>dls</b> and whether we are acting as directory <b>server</b>, and - * then return a list of int pointers defining download delays in seconds. - * Helper function for download_status_increment_failure() and - * download_status_reset(). */ -static const smartlist_t * -find_dl_schedule_and_len(download_status_t *dls, int server) + * in <b>dls</b> and <b>options</b>. + * Then return a list of int pointers defining download delays in seconds. + * Helper function for download_status_increment_failure(), + * download_status_reset(), and download_status_increment_attempt(). */ +STATIC const smartlist_t * +find_dl_schedule(download_status_t *dls, const or_options_t *options) { + const int dir_server = dir_server_mode(options); + const int multi_d = networkstatus_consensus_can_use_multiple_directories( + options); + const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + time(NULL)); + const int use_fallbacks = networkstatus_consensus_can_use_extra_fallbacks( + options); switch (dls->schedule) { case DL_SCHED_GENERIC: - if (server) - return get_options()->TestingServerDownloadSchedule; - else - return get_options()->TestingClientDownloadSchedule; + if (dir_server) { + return options->TestingServerDownloadSchedule; + } else { + return options->TestingClientDownloadSchedule; + } case DL_SCHED_CONSENSUS: - if (server) - return get_options()->TestingServerConsensusDownloadSchedule; - else - return get_options()->TestingClientConsensusDownloadSchedule; + if (!multi_d) { + return options->TestingServerConsensusDownloadSchedule; + } else { + if (we_are_bootstrapping) { + if (!use_fallbacks) { + /* A bootstrapping client without extra fallback directories */ + return + options->TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule; + } else if (dls->want_authority) { + /* A bootstrapping client with extra fallback directories, but + * connecting to an authority */ + return + options->TestingClientBootstrapConsensusAuthorityDownloadSchedule; + } else { + /* A bootstrapping client connecting to extra fallback directories + */ + return + options->TestingClientBootstrapConsensusFallbackDownloadSchedule; + } + } else { + return options->TestingClientConsensusDownloadSchedule; + } + } case DL_SCHED_BRIDGE: - return get_options()->TestingBridgeDownloadSchedule; + return options->TestingBridgeDownloadSchedule; default: tor_assert(0); } @@ -3484,54 +3721,168 @@ find_dl_schedule_and_len(download_status_t *dls, int server) return NULL; } -/** Called when an attempt to download <b>dls</b> has failed with HTTP status +/* Find the current delay for dls based on schedule. + * Set dls->next_attempt_at based on now, and return the delay. + * Helper for download_status_increment_failure and + * download_status_increment_attempt. */ +STATIC int +download_status_schedule_get_delay(download_status_t *dls, + const smartlist_t *schedule, + time_t now) +{ + tor_assert(dls); + tor_assert(schedule); + + int delay = INT_MAX; + uint8_t dls_schedule_position = (dls->increment_on + == DL_SCHED_INCREMENT_ATTEMPT + ? dls->n_download_attempts + : dls->n_download_failures); + + if (dls_schedule_position < smartlist_len(schedule)) + delay = *(int *)smartlist_get(schedule, dls_schedule_position); + else if (dls_schedule_position == IMPOSSIBLE_TO_DOWNLOAD) + delay = INT_MAX; + else + delay = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1); + + /* A negative delay makes no sense. Knowing that delay is + * non-negative allows us to safely do the wrapping check below. */ + tor_assert(delay >= 0); + + /* Avoid now+delay overflowing INT_MAX, by comparing with a subtraction + * that won't overflow (since delay is non-negative). */ + if (delay < INT_MAX && now <= INT_MAX - delay) { + dls->next_attempt_at = now+delay; + } else { + dls->next_attempt_at = TIME_MAX; + } + + return delay; +} + +/* Log a debug message about item, which increments on increment_action, has + * incremented dls_n_download_increments times. The message varies based on + * was_schedule_incremented (if not, not_incremented_response is logged), and + * the values of increment, dls_next_attempt_at, and now. + * Helper for download_status_increment_failure and + * download_status_increment_attempt. */ +static void +download_status_log_helper(const char *item, int was_schedule_incremented, + const char *increment_action, + const char *not_incremented_response, + uint8_t dls_n_download_increments, int increment, + time_t dls_next_attempt_at, time_t now) +{ + if (item) { + if (!was_schedule_incremented) + log_debug(LD_DIR, "%s %s %d time(s); I'll try again %s.", + item, increment_action, (int)dls_n_download_increments, + not_incremented_response); + else if (increment == 0) + log_debug(LD_DIR, "%s %s %d time(s); I'll try again immediately.", + item, increment_action, (int)dls_n_download_increments); + else if (dls_next_attempt_at < TIME_MAX) + log_debug(LD_DIR, "%s %s %d time(s); I'll try again in %d seconds.", + item, increment_action, (int)dls_n_download_increments, + (int)(dls_next_attempt_at-now)); + else + log_debug(LD_DIR, "%s %s %d time(s); Giving up for a while.", + item, increment_action, (int)dls_n_download_increments); + } +} + +/** Determine when a failed download attempt should be retried. + * Called when an attempt to download <b>dls</b> has failed with HTTP status * <b>status_code</b>. Increment the failure count (if the code indicates a - * real failure) and set <b>dls</b>-\>next_attempt_at to an appropriate time - * in the future. */ + * real failure, or if we're a server) and set <b>dls</b>-\>next_attempt_at to + * an appropriate time in the future and return it. + * If <b>dls->increment_on</b> is DL_SCHED_INCREMENT_ATTEMPT, increment the + * failure count, and return a time in the far future for the next attempt (to + * avoid an immediate retry). */ time_t download_status_increment_failure(download_status_t *dls, int status_code, const char *item, int server, time_t now) { - const smartlist_t *schedule; - int increment; + int increment = -1; tor_assert(dls); + + /* only count the failure if it's permanent, or we're a server */ if (status_code != 503 || server) { if (dls->n_download_failures < IMPOSSIBLE_TO_DOWNLOAD-1) ++dls->n_download_failures; } - schedule = find_dl_schedule_and_len(dls, server); + if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { + /* We don't find out that a failure-based schedule has attempted a + * connection until that connection fails. + * We'll never find out about successful connections, but this doesn't + * matter, because schedules are reset after a successful download. + */ + if (dls->n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD-1) + ++dls->n_download_attempts; - if (dls->n_download_failures < smartlist_len(schedule)) - increment = *(int *)smartlist_get(schedule, dls->n_download_failures); - else if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD) - increment = INT_MAX; - else - increment = *(int *)smartlist_get(schedule, smartlist_len(schedule) - 1); + /* only return a failure retry time if this schedule increments on failures + */ + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); + increment = download_status_schedule_get_delay(dls, schedule, now); + } - if (increment < INT_MAX) - dls->next_attempt_at = now+increment; - else - dls->next_attempt_at = TIME_MAX; + download_status_log_helper(item, !dls->increment_on, "failed", + "concurrently", dls->n_download_failures, + increment, dls->next_attempt_at, now); - if (item) { - if (increment == 0) - log_debug(LD_DIR, "%s failed %d time(s); I'll try again immediately.", - item, (int)dls->n_download_failures); - else if (dls->next_attempt_at < TIME_MAX) - log_debug(LD_DIR, "%s failed %d time(s); I'll try again in %d seconds.", - item, (int)dls->n_download_failures, - (int)(dls->next_attempt_at-now)); - else - log_debug(LD_DIR, "%s failed %d time(s); Giving up for a while.", - item, (int)dls->n_download_failures); + if (dls->increment_on == DL_SCHED_INCREMENT_ATTEMPT) { + /* stop this schedule retrying on failure, it will launch concurrent + * connections instead */ + return TIME_MAX; + } else { + return dls->next_attempt_at; } +} + +/** Determine when the next download attempt should be made when using an + * attempt-based (potentially concurrent) download schedule. + * Called when an attempt to download <b>dls</b> is being initiated. + * Increment the attempt count and set <b>dls</b>-\>next_attempt_at to an + * appropriate time in the future and return it. + * If <b>dls->increment_on</b> is DL_SCHED_INCREMENT_FAILURE, don't increment + * the attempts, and return a time in the far future (to avoid launching a + * concurrent attempt). */ +time_t +download_status_increment_attempt(download_status_t *dls, const char *item, + time_t now) +{ + int delay = -1; + tor_assert(dls); + + if (dls->increment_on == DL_SCHED_INCREMENT_FAILURE) { + /* this schedule should retry on failure, and not launch any concurrent + attempts */ + log_info(LD_BUG, "Tried to launch an attempt-based connection on a " + "failure-based schedule."); + return TIME_MAX; + } + + if (dls->n_download_attempts < IMPOSSIBLE_TO_DOWNLOAD-1) + ++dls->n_download_attempts; + + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); + delay = download_status_schedule_get_delay(dls, schedule, now); + + download_status_log_helper(item, dls->increment_on, "attempted", + "on failure", dls->n_download_attempts, + delay, dls->next_attempt_at, now); + return dls->next_attempt_at; } /** Reset <b>dls</b> so that it will be considered downloadable * immediately, and/or to show that we don't need it anymore. * + * Must be called to initialise a download schedule, otherwise the zeroth item + * in the schedule will never be used. + * * (We find the zeroth element of the download schedule, and set * next_attempt_at to be the appropriate offset from 'now'. In most * cases this means setting it to 'now', so the item will be immediately @@ -3540,14 +3891,16 @@ download_status_increment_failure(download_status_t *dls, int status_code, void download_status_reset(download_status_t *dls) { - if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD) + if (dls->n_download_failures == IMPOSSIBLE_TO_DOWNLOAD + || dls->n_download_attempts == IMPOSSIBLE_TO_DOWNLOAD) return; /* Don't reset this. */ - const smartlist_t *schedule = find_dl_schedule_and_len( - dls, get_options()->DirPort_set); + const smartlist_t *schedule = find_dl_schedule(dls, get_options()); dls->n_download_failures = 0; + dls->n_download_attempts = 0; dls->next_attempt_at = time(NULL) + *(int *)smartlist_get(schedule, 0); + /* Don't reset dls->want_authority or dls->increment_on */ } /** Return the number of failures on <b>dls</b> since the last success (if @@ -3558,6 +3911,22 @@ download_status_get_n_failures(const download_status_t *dls) return dls->n_download_failures; } +/** Return the number of attempts to download <b>dls</b> since the last success + * (if any). This can differ from download_status_get_n_failures() due to + * outstanding concurrent attempts. */ +int +download_status_get_n_attempts(const download_status_t *dls) +{ + return dls->n_download_attempts; +} + +/** Return the next time to attempt to download <b>dls</b>. */ +time_t +download_status_get_next_attempt_at(const download_status_t *dls) +{ + return dls->next_attempt_at; +} + /** Called when one or more routerdesc (or extrainfo, if <b>was_extrainfo</b>) * fetches have failed (with uppercase fingerprints listed in <b>failed</b>, * either as descriptor digests or as identity digests based on diff --git a/src/or/directory.h b/src/or/directory.h index 4899eb5c8c..c5b5a5afe1 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -16,10 +16,12 @@ int directories_have_accepted_server_descriptor(void); void directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, dirinfo_type_t type, const char *payload, size_t payload_len, size_t extrainfo_len); -MOCK_DECL(void, directory_get_from_dirserver, (uint8_t dir_purpose, - uint8_t router_purpose, - const char *resource, - int pds_flags)); +MOCK_DECL(void, directory_get_from_dirserver, ( + uint8_t dir_purpose, + uint8_t router_purpose, + const char *resource, + int pds_flags, + download_want_authority_t want_authority)); void directory_get_from_all_authorities(uint8_t dir_purpose, uint8_t router_purpose, const char *resource); @@ -37,14 +39,16 @@ typedef enum { DIRIND_ANON_DIRPORT, } dir_indirection_t; -void directory_initiate_command_routerstatus(const routerstatus_t *status, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since); +MOCK_DECL(void, directory_initiate_command_routerstatus, + (const routerstatus_t *status, + uint8_t dir_purpose, + uint8_t router_purpose, + dir_indirection_t indirection, + const char *resource, + const char *payload, + size_t payload_len, + time_t if_modified_since)); + void directory_initiate_command_routerstatus_rend(const routerstatus_t *status, uint8_t dir_purpose, uint8_t router_purpose, @@ -72,6 +76,9 @@ void directory_initiate_command(const tor_addr_t *addr, const char *resource, const char *payload, size_t payload_len, time_t if_modified_since); +int connection_dir_avoid_extra_connection_for_purpose(unsigned int purpose); +int connection_dir_close_consensus_conn_if_extra(dir_connection_t *conn); +void connection_dir_close_extra_consensus_conns(void); #define DSR_HEX (1<<0) #define DSR_BASE64 (1<<1) @@ -90,34 +97,41 @@ int router_supports_extrainfo(const char *identity_digest, int is_authority); time_t download_status_increment_failure(download_status_t *dls, int status_code, const char *item, int server, time_t now); +time_t download_status_increment_attempt(download_status_t *dls, + const char *item, time_t now); /** Increment the failure count of the download_status_t <b>dls</b>, with * the optional status code <b>sc</b>. */ #define download_status_failed(dls, sc) \ download_status_increment_failure((dls), (sc), NULL, \ - get_options()->DirPort_set, time(NULL)) + dir_server_mode(get_options()), \ + time(NULL)) void download_status_reset(download_status_t *dls); static int download_status_is_ready(download_status_t *dls, time_t now, int max_failures); /** Return true iff, as of <b>now</b>, the resource tracked by <b>dls</b> is * ready to get its download reattempted. */ -static INLINE int +static inline int download_status_is_ready(download_status_t *dls, time_t now, int max_failures) { - return (dls->n_download_failures <= max_failures - && dls->next_attempt_at <= now); + int under_failure_limit = (dls->n_download_failures <= max_failures + && dls->n_download_attempts <= max_failures); + return (under_failure_limit && dls->next_attempt_at <= now); } static void download_status_mark_impossible(download_status_t *dl); /** Mark <b>dl</b> as never downloadable. */ -static INLINE void +static inline void download_status_mark_impossible(download_status_t *dl) { dl->n_download_failures = IMPOSSIBLE_TO_DOWNLOAD; + dl->n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD; } int download_status_get_n_failures(const download_status_t *dls); +int download_status_get_n_attempts(const download_status_t *dls); +time_t download_status_get_next_attempt_at(const download_status_t *dls); #ifdef TOR_UNIT_TESTS /* Used only by directory.c and test_dir.c */ @@ -127,6 +141,21 @@ STATIC int purpose_needs_anonymity(uint8_t dir_purpose, uint8_t router_purpose); STATIC dirinfo_type_t dir_fetch_type(int dir_purpose, int router_purpose, const char *resource); +STATIC int directory_handle_command_get(dir_connection_t *conn, + const char *headers, + const char *req_body, + size_t req_body_len); +STATIC int connection_dir_would_close_consensus_conn_helper(void); +STATIC int download_status_schedule_get_delay(download_status_t *dls, + const smartlist_t *schedule, + time_t now); + +STATIC char* authdir_type_to_string(dirinfo_type_t auth); +STATIC const char * dir_conn_purpose_to_string(int purpose); +STATIC int should_use_directory_guards(const or_options_t *options); +STATIC zlib_compression_level_t choose_compression_level(ssize_t n_bytes); +STATIC const smartlist_t *find_dl_schedule(download_status_t *dls, + const or_options_t *options); #endif #endif diff --git a/src/or/dirserv.c b/src/or/dirserv.c index 8d9f166556..620d324704 100644 --- a/src/or/dirserv.c +++ b/src/or/dirserv.c @@ -797,7 +797,7 @@ list_single_server_status(const routerinfo_t *desc, int is_live) } /* DOCDOC running_long_enough_to_decide_unreachable */ -static INLINE int +static inline int running_long_enough_to_decide_unreachable(void) { return time_of_process_start @@ -1091,13 +1091,13 @@ directory_fetches_from_authorities(const or_options_t *options) return 1; /* we don't know our IP address; ask an authority. */ refuseunknown = ! router_my_exit_policy_is_reject_star() && should_refuse_unknown_exits(options); - if (!options->DirPort_set && !refuseunknown) + if (!dir_server_mode(options) && !refuseunknown) return 0; if (!server_mode(options) || !advertised_server_mode()) return 0; me = router_get_my_routerinfo(); - if (!me || (!me->dir_port && !refuseunknown)) - return 0; /* if dirport not advertised, return 0 too */ + if (!me || (!me->supports_tunnelled_dir_requests && !refuseunknown)) + return 0; /* if we don't service directory requests, return 0 too */ return 1; } @@ -1128,7 +1128,7 @@ directory_fetches_dir_info_later(const or_options_t *options) int directory_caches_unknown_auth_certs(const or_options_t *options) { - return options->DirPort_set || options->BridgeRelay; + return dir_server_mode(options) || options->BridgeRelay; } /** Return 1 if we want to keep descriptors, networkstatuses, etc around @@ -1137,7 +1137,7 @@ directory_caches_unknown_auth_certs(const or_options_t *options) int directory_caches_dir_info(const or_options_t *options) { - if (options->BridgeRelay || options->DirPort_set) + if (options->BridgeRelay || dir_server_mode(options)) return 1; if (!server_mode(options) || !advertised_server_mode()) return 0; @@ -1153,7 +1153,7 @@ directory_caches_dir_info(const or_options_t *options) int directory_permits_begindir_requests(const or_options_t *options) { - return options->BridgeRelay != 0 || options->DirPort_set; + return options->BridgeRelay != 0 || dir_server_mode(options); } /** Return 1 if we have no need to fetch new descriptors. This generally @@ -1302,7 +1302,7 @@ static uint32_t guard_bandwidth_excluding_exits_kb = 0; /** Helper: estimate the uptime of a router given its stated uptime and the * amount of time since it last stated its stated uptime. */ -static INLINE long +static inline long real_uptime(const routerinfo_t *router, time_t now) { if (now < router->cache_info.published_on) @@ -1350,8 +1350,9 @@ dirserv_thinks_router_is_unreliable(time_t now, } /** Return true iff <b>router</b> should be assigned the "HSDir" flag. + * * Right now this means it advertises support for it, it has a high uptime, - * it has a DirPort open, it has the Stable and Fast flag and it's currently + * it's a directory cache, it has the Stable and Fast flags, and it's currently * considered Running. * * This function needs to be called after router-\>is_running has @@ -1378,7 +1379,8 @@ dirserv_thinks_router_is_hs_dir(const routerinfo_t *router, else uptime = real_uptime(router, now); - return (router->wants_to_be_hs_dir && router->dir_port && + return (router->wants_to_be_hs_dir && + router->supports_tunnelled_dir_requests && node->is_stable && node->is_fast && uptime >= get_options()->MinUptimeHidServDirectoryV2 && router_is_active(router, node, now)); @@ -1921,7 +1923,7 @@ routerstatus_format_entry(const routerstatus_t *rs, const char *version, rs->is_hs_dir?" HSDir":"", rs->is_flagged_running?" Running":"", rs->is_stable?" Stable":"", - (rs->dir_port!=0)?" V2Dir":"", + rs->is_v2_dir?" V2Dir":"", rs->is_valid?" Valid":""); /* length of "opt v \n" */ @@ -2185,6 +2187,7 @@ set_routerstatus_from_routerinfo(routerstatus_t *rs, strlcpy(rs->nickname, ri->nickname, sizeof(rs->nickname)); rs->or_port = ri->or_port; rs->dir_port = ri->dir_port; + rs->is_v2_dir = ri->supports_tunnelled_dir_requests; if (options->AuthDirHasIPv6Connectivity == 1 && !tor_addr_is_null(&ri->ipv6_addr) && node->last_reachable6 >= now - REACHABLE_TIMEOUT) { diff --git a/src/or/dirvote.c b/src/or/dirvote.c index d8e6ee2229..b61b33af79 100644 --- a/src/or/dirvote.c +++ b/src/or/dirvote.c @@ -54,7 +54,6 @@ static int dirvote_perform_vote(void); static void dirvote_clear_votes(int all_votes); static int dirvote_compute_consensuses(void); static int dirvote_publish_consensus(void); -static char *make_consensus_method_list(int low, int high, const char *sep); /* ===== * Voting @@ -564,7 +563,7 @@ consensus_method_is_supported(int method) /** Return a newly allocated string holding the numbers between low and high * (inclusive) that are supported consensus methods. */ -static char * +STATIC char * make_consensus_method_list(int low, int high, const char *separator) { char *list; @@ -3373,8 +3372,8 @@ dirvote_free_all(void) * ==== */ /** Return the body of the consensus that we're currently trying to build. */ -const char * -dirvote_get_pending_consensus(consensus_flavor_t flav) +MOCK_IMPL(const char *, +dirvote_get_pending_consensus, (consensus_flavor_t flav)) { tor_assert(((int)flav) >= 0 && (int)flav < N_CONSENSUS_FLAVORS); return pending_consensuses[flav].body; @@ -3382,8 +3381,8 @@ dirvote_get_pending_consensus(consensus_flavor_t flav) /** Return the signatures that we know for the consensus that we're currently * trying to build. */ -const char * -dirvote_get_pending_detached_signatures(void) +MOCK_IMPL(const char *, +dirvote_get_pending_detached_signatures, (void)) { return pending_consensus_signatures; } diff --git a/src/or/dirvote.h b/src/or/dirvote.h index dca8540870..cc526ea34e 100644 --- a/src/or/dirvote.h +++ b/src/or/dirvote.h @@ -136,8 +136,10 @@ int dirvote_add_signatures(const char *detached_signatures_body, const char **msg_out); /* Item access */ -const char *dirvote_get_pending_consensus(consensus_flavor_t flav); -const char *dirvote_get_pending_detached_signatures(void); +MOCK_DECL(const char*, dirvote_get_pending_consensus, + (consensus_flavor_t flav)); +MOCK_DECL(const char*, dirvote_get_pending_detached_signatures, (void)); + #define DGV_BY_ID 1 #define DGV_INCLUDE_PENDING 2 #define DGV_INCLUDE_PREVIOUS 4 @@ -175,6 +177,7 @@ STATIC char *format_networkstatus_vote(crypto_pk_t *private_key, STATIC char *dirvote_compute_params(smartlist_t *votes, int method, int total_authorities); STATIC char *compute_consensus_package_lines(smartlist_t *votes); +STATIC char *make_consensus_method_list(int low, int high, const char *sep); #endif #endif diff --git a/src/or/dns.c b/src/or/dns.c index d71246d61e..3f5dfd2a8a 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -107,13 +107,9 @@ static void dns_found_answer(const char *address, uint8_t query_type, const tor_addr_t *addr, const char *hostname, uint32_t ttl); -static int launch_resolve(cached_resolve_t *resolve); static void add_wildcarded_test_address(const char *address); static int configure_nameservers(int force); static int answer_is_wildcarded(const char *ip); -static int set_exitconn_info_from_resolve(edge_connection_t *exitconn, - const cached_resolve_t *resolve, - char **hostname_out); static int evdns_err_is_transient(int err); static void inform_pending_connections(cached_resolve_t *resolve); static void make_pending_resolve_cached(cached_resolve_t *cached); @@ -138,7 +134,7 @@ static int dns_is_broken_for_ipv6 = 0; /** Function to compare hashed resolves on their addresses; used to * implement hash tables. */ -static INLINE int +static inline int cached_resolves_eq(cached_resolve_t *a, cached_resolve_t *b) { /* make this smarter one day? */ @@ -147,7 +143,7 @@ cached_resolves_eq(cached_resolve_t *a, cached_resolve_t *b) } /** Hash function for cached_resolve objects */ -static INLINE unsigned int +static inline unsigned int cached_resolve_hash(cached_resolve_t *a) { return (unsigned) siphash24g((const uint8_t*)a->address, strlen(a->address)); @@ -859,10 +855,10 @@ dns_resolve_impl,(edge_connection_t *exitconn, int is_resolve, * Return -2 on a transient error, -1 on a permenent error, and 1 on * a successful lookup. */ -static int -set_exitconn_info_from_resolve(edge_connection_t *exitconn, - const cached_resolve_t *resolve, - char **hostname_out) +MOCK_IMPL(STATIC int, +set_exitconn_info_from_resolve,(edge_connection_t *exitconn, + const cached_resolve_t *resolve, + char **hostname_out)) { int ipv4_ok, ipv6_ok, answer_with_ipv4, r; uint32_t begincell_flags; @@ -1130,7 +1126,7 @@ dns_cancel_pending_resolve,(const char *address)) /** Return true iff <b>address</b> is one of the addresses we use to verify * that well-known sites aren't being hijacked by our DNS servers. */ -static INLINE int +static inline int is_test_address(const char *address) { const or_options_t *options = get_options(); @@ -1664,8 +1660,8 @@ launch_one_resolve(const char *address, uint8_t query_type, /** For eventdns: start resolving as necessary to find the target for * <b>exitconn</b>. Returns -1 on error, -2 on transient error, * 0 on "resolve launched." */ -static int -launch_resolve(cached_resolve_t *resolve) +MOCK_IMPL(STATIC int, +launch_resolve,(cached_resolve_t *resolve)) { tor_addr_t a; int r; @@ -2118,5 +2114,18 @@ assert_cache_ok_(void) } }); } + #endif +cached_resolve_t +*dns_get_cache_entry(cached_resolve_t *query) +{ + return HT_FIND(cache_map, &cache_root, query); +} + +void +dns_insert_cache_entry(cached_resolve_t *new_entry) +{ + HT_INSERT(cache_map, &cache_root, new_entry); +} + diff --git a/src/or/dns.h b/src/or/dns.h index 6af7796dbb..c2778b216c 100644 --- a/src/or/dns.h +++ b/src/or/dns.h @@ -42,6 +42,18 @@ uint8_t answer_type,const cached_resolve_t *resolved)); MOCK_DECL(STATIC void,send_resolved_hostname_cell,(edge_connection_t *conn, const char *hostname)); + +cached_resolve_t *dns_get_cache_entry(cached_resolve_t *query); +void dns_insert_cache_entry(cached_resolve_t *new_entry); + +MOCK_DECL(STATIC int, +set_exitconn_info_from_resolve,(edge_connection_t *exitconn, + const cached_resolve_t *resolve, + char **hostname_out)); + +MOCK_DECL(STATIC int, +launch_resolve,(cached_resolve_t *resolve)); + #endif #endif diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index f7710908bd..ded0d8431b 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -125,6 +125,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) /* Make a new dummy AP connection, and attach the request to it. */ entry_conn = entry_connection_new(CONN_TYPE_AP, AF_INET); conn = ENTRY_TO_EDGE_CONN(entry_conn); + CONNECTION_AP_EXPECT_NONPENDING(entry_conn); TO_CONN(conn)->state = AP_CONN_STATE_RESOLVE_WAIT; conn->is_dns_request = 1; @@ -199,6 +200,7 @@ dnsserv_launch_request(const char *name, int reverse, /* Make a new dummy AP connection, and attach the request to it. */ entry_conn = entry_connection_new(CONN_TYPE_AP, AF_INET); conn = ENTRY_TO_EDGE_CONN(entry_conn); + CONNECTION_AP_EXPECT_NONPENDING(entry_conn); conn->base_.state = AP_CONN_STATE_RESOLVE_WAIT; tor_addr_copy(&TO_CONN(conn)->addr, &control_conn->base_.addr); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index ebf675166b..bf71fc30c0 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -2205,7 +2205,7 @@ fetch_bridge_descriptors(const or_options_t *options, time_t now) log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.", resource); directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, - ROUTER_PURPOSE_BRIDGE, resource, 0); + ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY); } } SMARTLIST_FOREACH_END(bridge); diff --git a/src/or/eventdns_tor.h b/src/or/eventdns_tor.h index 9d51f0960e..f41c5c0099 100644 --- a/src/or/eventdns_tor.h +++ b/src/or/eventdns_tor.h @@ -12,9 +12,6 @@ typedef unsigned int uint; #ifndef HAVE_U_CHAR typedef unsigned char u_char; #endif -#ifdef _WIN32 -#define inline __inline -#endif #include "torint.h" /* These are for debugging possible memory leaks. */ diff --git a/src/or/ext_orport.c b/src/or/ext_orport.c index e8c8aa60a4..f159f7d0a6 100644 --- a/src/or/ext_orport.c +++ b/src/or/ext_orport.c @@ -193,8 +193,7 @@ handle_client_auth_nonce(const char *client_nonce, size_t client_nonce_len, return -1; /* Get our nonce */ - if (crypto_rand(server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN) < 0) - return -1; + crypto_rand(server_nonce, EXT_OR_PORT_AUTH_NONCE_LEN); { /* set up macs */ size_t hmac_s_msg_len = strlen(EXT_OR_PORT_AUTH_SERVER_TO_CLIENT_CONST) + diff --git a/src/or/fallback_dirs.inc b/src/or/fallback_dirs.inc new file mode 100644 index 0000000000..d9214cb884 --- /dev/null +++ b/src/or/fallback_dirs.inc @@ -0,0 +1 @@ +/* This list will be empty until opt-ins are finalised. */ diff --git a/src/or/fp_pair.c b/src/or/fp_pair.c index 42bebcd847..c863d4176c 100644 --- a/src/or/fp_pair.c +++ b/src/or/fp_pair.c @@ -21,7 +21,7 @@ struct fp_pair_map_s { */ /** Compare fp_pair_entry_t objects by key value. */ -static INLINE int +static inline int fp_pair_map_entries_eq(const fp_pair_map_entry_t *a, const fp_pair_map_entry_t *b) { @@ -29,7 +29,7 @@ fp_pair_map_entries_eq(const fp_pair_map_entry_t *a, } /** Return a hash value for an fp_pair_entry_t. */ -static INLINE unsigned int +static inline unsigned int fp_pair_map_entry_hash(const fp_pair_map_entry_t *a) { tor_assert(sizeof(a->key) == DIGEST_LEN*2); diff --git a/src/or/geoip.c b/src/or/geoip.c index 120ce479cc..26030ae52a 100644 --- a/src/or/geoip.c +++ b/src/or/geoip.c @@ -18,7 +18,6 @@ #include "geoip.h" #include "routerlist.h" -static void clear_geoip_db(void); static void init_geoip_countries(void); /** An entry from the GeoIP IPv4 file: maps an IPv4 range to a country. */ @@ -483,7 +482,7 @@ static HT_HEAD(clientmap, clientmap_entry_t) client_history = HT_INITIALIZER(); /** Hashtable helper: compute a hash of a clientmap_entry_t. */ -static INLINE unsigned +static inline unsigned clientmap_entry_hash(const clientmap_entry_t *a) { unsigned h = (unsigned) tor_addr_hash(&a->addr); @@ -494,7 +493,7 @@ clientmap_entry_hash(const clientmap_entry_t *a) return h; } /** Hashtable helper: compare two clientmap_entry_t values for equality. */ -static INLINE int +static inline int clientmap_entries_eq(const clientmap_entry_t *a, const clientmap_entry_t *b) { if (strcmp_opt(a->transport_name, b->transport_name)) @@ -970,7 +969,7 @@ geoip_get_dirreq_history(dirreq_type_t type) &ent->completion_time); if (time_diff == 0) time_diff = 1; /* Avoid DIV/0; "instant" answers are impossible - * by law of nature or something, but a milisecond + * by law of nature or something, but a millisecond * is a bit greater than "instantly" */ bytes_per_second = (uint32_t)(1000 * ent->response_size / time_diff); dltimes[ent_sl_idx] = bytes_per_second; @@ -1207,9 +1206,9 @@ geoip_format_dirreq_stats(time_t now) { char t[ISO_TIME_LEN+1]; int i; - char *v3_ips_string, *v3_reqs_string, *v3_direct_dl_string, - *v3_tunneled_dl_string; - char *result; + char *v3_ips_string = NULL, *v3_reqs_string = NULL, + *v3_direct_dl_string = NULL, *v3_tunneled_dl_string = NULL; + char *result = NULL; if (!start_of_dirreq_stats_interval) return NULL; /* Not initialized. */ @@ -1280,6 +1279,8 @@ geoip_dirreq_stats_write(time_t now) /* Generate history string .*/ str = geoip_format_dirreq_stats(now); + if (! str) + goto done; /* Write dirreq-stats string to disk. */ if (!check_or_create_data_subdir("stats")) { @@ -1666,7 +1667,7 @@ getinfo_helper_geoip(control_connection_t *control_conn, } /** Release all storage held by the GeoIP databases and country list. */ -static void +STATIC void clear_geoip_db(void) { if (geoip_countries) { diff --git a/src/or/geoip.h b/src/or/geoip.h index 8a3486c7ac..3f1bba01f8 100644 --- a/src/or/geoip.h +++ b/src/or/geoip.h @@ -18,6 +18,7 @@ STATIC int geoip_parse_entry(const char *line, sa_family_t family); STATIC int geoip_get_country_by_ipv4(uint32_t ipaddr); STATIC int geoip_get_country_by_ipv6(const struct in6_addr *addr); +STATIC void clear_geoip_db(void); #endif int should_record_bridge_info(const or_options_t *options); int geoip_load_file(sa_family_t family, const char *filename); diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 356e11f6ec..7f8530b221 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -412,11 +412,15 @@ configure_accounting(time_t now) /** Return the relevant number of bytes sent/received this interval * based on the set AccountingRule */ -static uint64_t +uint64_t get_accounting_bytes(void) { if (get_options()->AccountingRule == ACCT_SUM) return n_bytes_read_in_interval+n_bytes_written_in_interval; + else if (get_options()->AccountingRule == ACCT_IN) + return n_bytes_read_in_interval; + else if (get_options()->AccountingRule == ACCT_OUT) + return n_bytes_written_in_interval; else return MAX(n_bytes_read_in_interval, n_bytes_written_in_interval); } @@ -490,7 +494,7 @@ reset_accounting(time_t now) } /** Return true iff we should save our bandwidth usage to disk. */ -static INLINE int +static inline int time_to_record_bandwidth_usage(time_t now) { /* Note every 600 sec */ @@ -1010,7 +1014,7 @@ getinfo_helper_accounting(control_connection_t *conn, else *answer = tor_strdup("awake"); } else if (!strcmp(question, "accounting/bytes")) { - tor_asprintf(answer, U64_FORMAT" "U64_FORMAT, + tor_asprintf(answer, U64_FORMAT" "U64_FORMAT, U64_PRINTF_ARG(n_bytes_read_in_interval), U64_PRINTF_ARG(n_bytes_written_in_interval)); } else if (!strcmp(question, "accounting/bytes-left")) { @@ -1022,6 +1026,18 @@ getinfo_helper_accounting(control_connection_t *conn, total_left = limit - total_bytes; tor_asprintf(answer, U64_FORMAT" "U64_FORMAT, U64_PRINTF_ARG(total_left), U64_PRINTF_ARG(total_left)); + } else if (get_options()->AccountingRule == ACCT_IN) { + uint64_t read_left = 0; + if (n_bytes_read_in_interval < limit) + read_left = limit - n_bytes_read_in_interval; + tor_asprintf(answer, U64_FORMAT" "U64_FORMAT, + U64_PRINTF_ARG(read_left), U64_PRINTF_ARG(limit)); + } else if (get_options()->AccountingRule == ACCT_OUT) { + uint64_t write_left = 0; + if (n_bytes_written_in_interval < limit) + write_left = limit - n_bytes_written_in_interval; + tor_asprintf(answer, U64_FORMAT" "U64_FORMAT, + U64_PRINTF_ARG(limit), U64_PRINTF_ARG(write_left)); } else { uint64_t read_left = 0, write_left = 0; if (n_bytes_read_in_interval < limit) diff --git a/src/or/hibernate.h b/src/or/hibernate.h index b9e619c5ad..e0d0c29bdb 100644 --- a/src/or/hibernate.h +++ b/src/or/hibernate.h @@ -19,6 +19,7 @@ MOCK_DECL(int, accounting_is_enabled, (const or_options_t *options)); int accounting_get_interval_length(void); MOCK_DECL(time_t, accounting_get_end_time, (void)); void configure_accounting(time_t now); +uint64_t get_accounting_bytes(void); void accounting_run_housekeeping(time_t now); void accounting_add_bytes(size_t n_read, size_t n_written, int seconds); int accounting_record_bandwidth_usage(time_t now, or_state_t *state); diff --git a/src/or/include.am b/src/or/include.am index a3ac49c5d6..712ae18406 100644 --- a/src/or/include.am +++ b/src/or/include.am @@ -63,6 +63,7 @@ LIBTOR_A_SOURCES = \ src/or/onion_fast.c \ src/or/onion_tap.c \ src/or/transports.c \ + src/or/periodic.c \ src/or/policies.c \ src/or/reasons.c \ src/or/relay.c \ @@ -92,7 +93,8 @@ src_or_libtor_testing_a_SOURCES = $(LIBTOR_A_SOURCES) src_or_tor_SOURCES = src/or/tor_main.c AM_CPPFLAGS += -I$(srcdir)/src/or -Isrc/or -src/or/tor_main.o: micro-revision.i +src/or/tor_main.$(OBJEXT) \ + src/or/src_or_tor_cov-tor_main.$(OBJEXT): micro-revision.i AM_CPPFLAGS += -DSHARE_DATADIR="\"$(datadir)\"" \ -DLOCALSTATEDIR="\"$(localstatedir)\"" \ @@ -108,7 +110,7 @@ src_or_libtor_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) src_or_tor_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@ src_or_tor_LDADD = src/or/libtor.a src/common/libor.a \ - src/common/libor-crypto.a $(LIBDONNA) \ + src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event.a src/trunnel/libor-trunnel.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ @@ -119,7 +121,7 @@ src_or_tor_cov_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_CPPFLAGS) src_or_tor_cov_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) src_or_tor_cov_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ @TOR_LDFLAGS_libevent@ src_or_tor_cov_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \ - src/common/libor-crypto-testing.a $(LIBDONNA) \ + src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event-testing.a src/trunnel/libor-trunnel-testing.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @TOR_SYSTEMD_LIBS@ @@ -154,6 +156,7 @@ ORHEADERS = \ src/or/dnsserv.h \ src/or/eventdns_tor.h \ src/or/ext_orport.h \ + src/or/fallback_dirs.inc \ src/or/fp_pair.h \ src/or/geoip.h \ src/or/entrynodes.h \ @@ -170,6 +173,7 @@ ORHEADERS = \ src/or/onion_tap.h \ src/or/or.h \ src/or/transports.h \ + src/or/periodic.h \ src/or/policies.h \ src/or/reasons.h \ src/or/relay.h \ diff --git a/src/or/keypin.c b/src/or/keypin.c index 047d2b069b..574a76d51e 100644 --- a/src/or/keypin.c +++ b/src/or/keypin.c @@ -57,14 +57,14 @@ static HT_HEAD(edmap, keypin_ent_st) the_ed_map = HT_INITIALIZER(); /** Hashtable helper: compare two keypin table entries and return true iff * they have the same RSA key IDs. */ -static INLINE int +static inline int keypin_ents_eq_rsa(const keypin_ent_t *a, const keypin_ent_t *b) { return tor_memeq(a->rsa_id, b->rsa_id, sizeof(a->rsa_id)); } /** Hashtable helper: hash a keypin table entries based on its RSA key ID */ -static INLINE unsigned +static inline unsigned keypin_ent_hash_rsa(const keypin_ent_t *a) { return (unsigned) siphash24g(a->rsa_id, sizeof(a->rsa_id)); @@ -72,14 +72,14 @@ return (unsigned) siphash24g(a->rsa_id, sizeof(a->rsa_id)); /** Hashtable helper: compare two keypin table entries and return true iff * they have the same ed25519 keys */ -static INLINE int +static inline int keypin_ents_eq_ed(const keypin_ent_t *a, const keypin_ent_t *b) { return tor_memeq(a->ed25519_key, b->ed25519_key, sizeof(a->ed25519_key)); } /** Hashtable helper: hash a keypin table entries based on its ed25519 key */ -static INLINE unsigned +static inline unsigned keypin_ent_hash_ed(const keypin_ent_t *a) { return (unsigned) siphash24g(a->ed25519_key, sizeof(a->ed25519_key)); diff --git a/src/or/main.c b/src/or/main.c index 534a6acc51..bd4f7eaa71 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -44,6 +44,7 @@ #include "nodelist.h" #include "ntmain.h" #include "onion.h" +#include "periodic.h" #include "policies.h" #include "transports.h" #include "relay.h" @@ -224,11 +225,13 @@ set_buffer_lengths_to_zero(tor_socket_t s) { int zero = 0; int r = 0; - if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&zero, sizeof(zero))) { + if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, (void*)&zero, + (socklen_t)sizeof(zero))) { log_warn(LD_NET, "Unable to clear SO_SNDBUF"); r = -1; } - if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&zero, sizeof(zero))) { + if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, (void*)&zero, + (socklen_t)sizeof(zero))) { log_warn(LD_NET, "Unable to clear SO_RCVBUF"); r = -1; } @@ -499,8 +502,7 @@ connection_in_array(connection_t *conn) return smartlist_contains(connection_array, conn); } -/** Set <b>*array</b> to an array of all connections, and <b>*n</b> - * to the length of the array. <b>*array</b> and <b>*n</b> must not +/** Set <b>*array</b> to an array of all connections. <b>*array</b> must not * be modified. */ smartlist_t * @@ -1227,39 +1229,85 @@ get_signewnym_epoch(void) return newnym_epoch; } -typedef struct { - time_t last_rotated_x509_certificate; - time_t check_v3_certificate; - time_t check_listeners; - time_t download_networkstatus; - time_t try_getting_descriptors; - time_t reset_descriptor_failures; - time_t add_entropy; - time_t write_bridge_status_file; - time_t downrate_stability; - time_t save_stability; - time_t clean_caches; - time_t recheck_bandwidth; - time_t check_for_expired_networkstatus; - time_t write_stats_files; - time_t write_bridge_stats; - time_t check_port_forwarding; - time_t launch_reachability_tests; - time_t retry_dns_init; - time_t next_heartbeat; - time_t check_descriptor; - /** When do we next launch DNS wildcarding checks? */ - time_t check_for_correct_dns; - /** When do we next make sure our Ed25519 keys aren't about to expire? */ - time_t check_ed_keys; - -} time_to_t; - -static time_to_t time_to = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +/** True iff we have initialized all the members of <b>periodic_events</b>. + * Used to prevent double-initialization. */ +static int periodic_events_initialized = 0; + +/* Declare all the timer callback functions... */ +#undef CALLBACK +#define CALLBACK(name) \ + static int name ## _callback(time_t, const or_options_t *) +CALLBACK(rotate_onion_key); +CALLBACK(check_ed_keys); +CALLBACK(launch_descriptor_fetches); +CALLBACK(reset_descriptor_failures); +CALLBACK(rotate_x509_certificate); +CALLBACK(add_entropy); +CALLBACK(launch_reachability_tests); +CALLBACK(downrate_stability); +CALLBACK(save_stability); +CALLBACK(check_authority_cert); +CALLBACK(check_expired_networkstatus); +CALLBACK(write_stats_file); +CALLBACK(record_bridge_stats); +CALLBACK(clean_caches); +CALLBACK(rend_cache_failure_clean); +CALLBACK(retry_dns); +CALLBACK(check_descriptor); +CALLBACK(check_for_reachability_bw); +CALLBACK(fetch_networkstatus); +CALLBACK(retry_listeners); +CALLBACK(expire_old_ciruits_serverside); +CALLBACK(check_dns_honesty); +CALLBACK(write_bridge_ns); +CALLBACK(check_fw_helper_app); +CALLBACK(heartbeat); + +#undef CALLBACK + +/* Now we declare an array of periodic_event_item_t for each periodic event */ +#define CALLBACK(name) PERIODIC_EVENT(name) + +static periodic_event_item_t periodic_events[] = { + CALLBACK(rotate_onion_key), + CALLBACK(check_ed_keys), + CALLBACK(launch_descriptor_fetches), + CALLBACK(reset_descriptor_failures), + CALLBACK(rotate_x509_certificate), + CALLBACK(add_entropy), + CALLBACK(launch_reachability_tests), + CALLBACK(downrate_stability), + CALLBACK(save_stability), + CALLBACK(check_authority_cert), + CALLBACK(check_expired_networkstatus), + CALLBACK(write_stats_file), + CALLBACK(record_bridge_stats), + CALLBACK(clean_caches), + CALLBACK(rend_cache_failure_clean), + CALLBACK(retry_dns), + CALLBACK(check_descriptor), + CALLBACK(check_for_reachability_bw), + CALLBACK(fetch_networkstatus), + CALLBACK(retry_listeners), + CALLBACK(expire_old_ciruits_serverside), + CALLBACK(check_dns_honesty), + CALLBACK(write_bridge_ns), + CALLBACK(check_fw_helper_app), + CALLBACK(heartbeat), + END_OF_PERIODIC_EVENTS }; - -/** Reset all the time_to's so we'll do all our actions again as if we +#undef CALLBACK + +/* These are pointers to members of periodic_events[] that are used to + * implement particular callbacks. We keep them separate here so that we + * can access them by name. We also keep them inside periodic_events[] + * so that we can implement "reset all timers" in a reasonable way. */ +static periodic_event_item_t *check_descriptor_event=NULL; +static periodic_event_item_t *fetch_networkstatus_event=NULL; +static periodic_event_item_t *launch_descriptor_fetches_event=NULL; +static periodic_event_item_t *check_dns_honesty_event=NULL; + +/** Reset all the periodic events so we'll do all our actions again as if we * just started up. * Useful if our clock just moved back a long time from the future, * so we don't wait until that future arrives again before acting. @@ -1267,7 +1315,77 @@ static time_to_t time_to = { void reset_all_main_loop_timers(void) { - memset(&time_to, 0, sizeof(time_to)); + int i; + for (i = 0; periodic_events[i].name; ++i) { + periodic_event_reschedule(&periodic_events[i]); + } +} + +/** Return the member of periodic_events[] whose name is <b>name</b>. + * Return NULL if no such event is found. + */ +static periodic_event_item_t * +find_periodic_event(const char *name) +{ + int i; + for (i = 0; periodic_events[i].name; ++i) { + if (strcmp(name, periodic_events[i].name) == 0) + return &periodic_events[i]; + } + return NULL; +} + +/** Helper, run one second after setup: + * Initializes all members of periodic_events and starts them running. + * + * (We do this one second after setup for backward-compatibility reasons; + * it might not actually be necessary.) */ +static void +initialize_periodic_events_cb(evutil_socket_t fd, short events, void *data) +{ + (void) fd; + (void) events; + (void) data; + int i; + for (i = 0; periodic_events[i].name; ++i) { + periodic_event_launch(&periodic_events[i]); + } +} + +/** Set up all the members of periodic_events[], and configure them all to be + * launched from a callback. */ +STATIC void +initialize_periodic_events(void) +{ + tor_assert(periodic_events_initialized == 0); + periodic_events_initialized = 1; + + int i; + for (i = 0; periodic_events[i].name; ++i) { + periodic_event_setup(&periodic_events[i]); + } + +#define NAMED_CALLBACK(name) \ + STMT_BEGIN name ## _event = find_periodic_event( #name ); STMT_END + + NAMED_CALLBACK(check_descriptor); + NAMED_CALLBACK(fetch_networkstatus); + NAMED_CALLBACK(launch_descriptor_fetches); + NAMED_CALLBACK(check_dns_honesty); + + struct timeval one_second = { 1, 0 }; + event_base_once(tor_libevent_get_base(), -1, EV_TIMEOUT, + initialize_periodic_events_cb, NULL, + &one_second); +} + +STATIC void +teardown_periodic_events(void) +{ + int i; + for (i = 0; periodic_events[i].name; ++i) { + periodic_event_destroy(&periodic_events[i]); + } } /** @@ -1278,7 +1396,8 @@ reset_all_main_loop_timers(void) void reschedule_descriptor_update_check(void) { - time_to.check_descriptor = 0; + tor_assert(check_descriptor_event); + periodic_event_reschedule(check_descriptor_event); } /** @@ -1288,8 +1407,22 @@ reschedule_descriptor_update_check(void) void reschedule_directory_downloads(void) { - time_to.download_networkstatus = 0; - time_to.try_getting_descriptors = 0; + tor_assert(fetch_networkstatus_event); + tor_assert(launch_descriptor_fetches_event); + + periodic_event_reschedule(fetch_networkstatus_event); + periodic_event_reschedule(launch_descriptor_fetches_event); +} + +static inline int +safe_timer_diff(time_t now, time_t next) +{ + if (next > now) { + tor_assert(next - now <= INT_MAX); + return (int)(next - now); + } else { + return 1; + } } /** Perform regular maintenance tasks. This function gets run once per @@ -1298,13 +1431,8 @@ reschedule_directory_downloads(void) static void run_scheduled_events(time_t now) { - static int should_init_bridge_stats = 1; const or_options_t *options = get_options(); - int is_server = server_mode(options); - int i; - int have_dir_info; - /* 0. See if we've been asked to shut down and our timeout has * expired; or if our bandwidth limits are exhausted and we * should hibernate; or if it's time to wake up from hibernation. @@ -1322,12 +1450,103 @@ run_scheduled_events(time_t now) /* 0c. If we've deferred log messages for the controller, handle them now */ flush_pending_log_callbacks(); + if (options->UseBridges && !options->DisableNetwork) { + fetch_bridge_descriptors(options, now); + } + + if (accounting_is_enabled(options)) { + accounting_run_housekeeping(now); + } + + if (authdir_mode_v3(options)) { + dirvote_act(options, now); + } + + /* 2d. Cleanup excess consensus bootstrap connections every second. + * connection_dir_close_consensus_conn_if_extra() will close connections + * that are clearly excess, but this check is more thorough. */ + connection_dir_close_extra_consensus_conns(); + + /* 3a. Every second, we examine pending circuits and prune the + * ones which have been pending for more than a few seconds. + * We do this before step 4, so it can try building more if + * it's not comfortable with the number of available circuits. + */ + /* (If our circuit build timeout can ever become lower than a second (which + * it can't, currently), we should do this more often.) */ + circuit_expire_building(); + + /* 3b. Also look at pending streams and prune the ones that 'began' + * a long time ago but haven't gotten a 'connected' yet. + * Do this before step 4, so we can put them back into pending + * state to be picked up by the new circuit. + */ + connection_ap_expire_beginning(); + + /* 3c. And expire connections that we've held open for too long. + */ + connection_expire_held_open(); + + /* 4. Every second, we try a new circuit if there are no valid + * circuits. Every NewCircuitPeriod seconds, we expire circuits + * that became dirty more than MaxCircuitDirtiness seconds ago, + * and we make a new circ if there are no clean circuits. + */ + const int have_dir_info = router_have_minimum_dir_info(); + if (have_dir_info && !net_is_disabled()) { + circuit_build_needed_circs(now); + } else { + circuit_expire_old_circs_as_needed(now); + } + + /* 5. We do housekeeping for each connection... */ + connection_or_set_bad_connections(NULL, 0); + int i; + for (i=0;i<smartlist_len(connection_array);i++) { + run_connection_housekeeping(i, now); + } + + /* 6. And remove any marked circuits... */ + circuit_close_all_marked(); + + /* 7. And upload service descriptors if necessary. */ + if (have_completed_a_circuit() && !net_is_disabled()) { + rend_consider_services_upload(now); + rend_consider_descriptor_republication(); + } + + /* 8. and blow away any connections that need to die. have to do this now, + * because if we marked a conn for close and left its socket -1, then + * we'll pass it to poll/select and bad things will happen. + */ + close_closeable_connections(); + + /* 8b. And if anything in our state is ready to get flushed to disk, we + * flush it. */ + or_state_save(now); + + /* 8c. Do channel cleanup just like for connections */ + channel_run_cleanup(); + channel_listener_run_cleanup(); + + /* 11b. check pending unconfigured managed proxies */ + if (!net_is_disabled() && pt_proxies_configuration_pending()) + pt_configure_remaining_proxies(); +} + +static int +rotate_onion_key_callback(time_t now, const or_options_t *options) +{ /* 1a. Every MIN_ONION_KEY_LIFETIME seconds, rotate the onion keys, * shut down and restart all cpuworkers, and update the directory if * necessary. */ - if (is_server && - get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME < now) { + if (server_mode(options)) { + time_t rotation_time = get_onion_key_set_at()+MIN_ONION_KEY_LIFETIME; + if (rotation_time > now) { + return safe_timer_diff(now, rotation_time); + } + log_info(LD_GENERAL,"Rotating onion key."); rotate_onion_key(); cpuworkers_rotate_keyinfo(); @@ -1336,9 +1555,15 @@ run_scheduled_events(time_t now) } if (advertised_server_mode() && !options->DisableNetwork) router_upload_dir_desc_to_dirservers(0); + return MIN_ONION_KEY_LIFETIME; } + return PERIODIC_EVENT_NO_UPDATE; +} - if (is_server && time_to.check_ed_keys < now) { +static int +check_ed_keys_callback(time_t now, const or_options_t *options) +{ + if (server_mode(options)) { if (should_make_new_ed_keys(options, now)) { if (load_ed_keys(options, now) < 0 || generate_ed_link_cert(options, now)) { @@ -1347,199 +1572,255 @@ run_scheduled_events(time_t now) exit(0); } } - time_to.check_ed_keys = now + 30; + return 30; } + return PERIODIC_EVENT_NO_UPDATE; +} - if (!should_delay_dir_fetches(options, NULL) && - time_to.try_getting_descriptors < now) { - update_all_descriptor_downloads(now); - update_extrainfo_downloads(now); - if (router_have_minimum_dir_info()) - time_to.try_getting_descriptors = now + LAZY_DESCRIPTOR_RETRY_INTERVAL; - else - time_to.try_getting_descriptors = now + GREEDY_DESCRIPTOR_RETRY_INTERVAL; - } +static int +launch_descriptor_fetches_callback(time_t now, const or_options_t *options) +{ + if (should_delay_dir_fetches(options, NULL)) + return PERIODIC_EVENT_NO_UPDATE; - if (time_to.reset_descriptor_failures < now) { - router_reset_descriptor_download_failures(); - time_to.reset_descriptor_failures = - now + DESCRIPTOR_FAILURE_RESET_INTERVAL; - } + update_all_descriptor_downloads(now); + update_extrainfo_downloads(now); + if (router_have_minimum_dir_info()) + return LAZY_DESCRIPTOR_RETRY_INTERVAL; + else + return GREEDY_DESCRIPTOR_RETRY_INTERVAL; +} - if (options->UseBridges && !options->DisableNetwork) - fetch_bridge_descriptors(options, now); +static int +reset_descriptor_failures_callback(time_t now, const or_options_t *options) +{ + (void)now; + (void)options; + router_reset_descriptor_download_failures(); + return DESCRIPTOR_FAILURE_RESET_INTERVAL; +} + +static int +rotate_x509_certificate_callback(time_t now, const or_options_t *options) +{ + static int first = 1; + (void)now; + (void)options; + if (first) { + first = 0; + return MAX_SSL_KEY_LIFETIME_INTERNAL; + } /* 1b. Every MAX_SSL_KEY_LIFETIME_INTERNAL seconds, we change our * TLS context. */ - if (!time_to.last_rotated_x509_certificate) - time_to.last_rotated_x509_certificate = now; - if (time_to.last_rotated_x509_certificate + - MAX_SSL_KEY_LIFETIME_INTERNAL < now) { - log_info(LD_GENERAL,"Rotating tls context."); - if (router_initialize_tls_context() < 0) { - log_warn(LD_BUG, "Error reinitializing TLS context"); - /* XXX is it a bug here, that we just keep going? -RD */ - } - time_to.last_rotated_x509_certificate = now; - /* We also make sure to rotate the TLS connections themselves if they've - * been up for too long -- but that's done via is_bad_for_new_circs in - * connection_run_housekeeping() above. */ + log_info(LD_GENERAL,"Rotating tls context."); + if (router_initialize_tls_context() < 0) { + log_warn(LD_BUG, "Error reinitializing TLS context"); + tor_assert(0); } - if (time_to.add_entropy < now) { - if (time_to.add_entropy) { - /* We already seeded once, so don't die on failure. */ - crypto_seed_rng(); - } -/** How often do we add more entropy to OpenSSL's RNG pool? */ -#define ENTROPY_INTERVAL (60*60) - time_to.add_entropy = now + ENTROPY_INTERVAL; + /* We also make sure to rotate the TLS connections themselves if they've + * been up for too long -- but that's done via is_bad_for_new_circs in + * run_connection_housekeeping() above. */ + return MAX_SSL_KEY_LIFETIME_INTERNAL; +} + +static int +add_entropy_callback(time_t now, const or_options_t *options) +{ + (void)now; + (void)options; + /* We already seeded once, so don't die on failure. */ + if (crypto_seed_rng() < 0) { + log_warn(LD_GENERAL, "Tried to re-seed RNG, but failed. We already " + "seeded once, though, so we won't exit here."); } - /* 1c. If we have to change the accounting interval or record - * bandwidth used in this accounting interval, do so. */ - if (accounting_is_enabled(options)) - accounting_run_housekeeping(now); + /** How often do we add more entropy to OpenSSL's RNG pool? */ +#define ENTROPY_INTERVAL (60*60) + return ENTROPY_INTERVAL; +} - if (time_to.launch_reachability_tests < now && - (authdir_mode_tests_reachability(options)) && - !net_is_disabled()) { - time_to.launch_reachability_tests = now + REACHABILITY_TEST_INTERVAL; +static int +launch_reachability_tests_callback(time_t now, const or_options_t *options) +{ + if (authdir_mode_tests_reachability(options) && + !net_is_disabled()) { /* try to determine reachability of the other Tor relays */ dirserv_test_reachability(now); } + return REACHABILITY_TEST_INTERVAL; +} +static int +downrate_stability_callback(time_t now, const or_options_t *options) +{ + (void)options; /* 1d. Periodically, we discount older stability information so that new * stability info counts more, and save the stability information to disk as * appropriate. */ - if (time_to.downrate_stability < now) - time_to.downrate_stability = rep_hist_downrate_old_runs(now); + time_t next = rep_hist_downrate_old_runs(now); + return safe_timer_diff(now, next); +} + +static int +save_stability_callback(time_t now, const or_options_t *options) +{ if (authdir_mode_tests_reachability(options)) { - if (time_to.save_stability < now) { - if (time_to.save_stability && rep_hist_record_mtbf_data(now, 1)<0) { - log_warn(LD_GENERAL, "Couldn't store mtbf data."); - } -#define SAVE_STABILITY_INTERVAL (30*60) - time_to.save_stability = now + SAVE_STABILITY_INTERVAL; + if (rep_hist_record_mtbf_data(now, 1)<0) { + log_warn(LD_GENERAL, "Couldn't store mtbf data."); } } +#define SAVE_STABILITY_INTERVAL (30*60) + return SAVE_STABILITY_INTERVAL; +} +static int +check_authority_cert_callback(time_t now, const or_options_t *options) +{ + (void)now; + (void)options; /* 1e. Periodically, if we're a v3 authority, we check whether our cert is * close to expiring and warn the admin if it is. */ - if (time_to.check_v3_certificate < now) { - v3_authority_check_key_expiry(); + v3_authority_check_key_expiry(); #define CHECK_V3_CERTIFICATE_INTERVAL (5*60) - time_to.check_v3_certificate = now + CHECK_V3_CERTIFICATE_INTERVAL; - } + return CHECK_V3_CERTIFICATE_INTERVAL; +} +static int +check_expired_networkstatus_callback(time_t now, const or_options_t *options) +{ + (void)options; /* 1f. Check whether our networkstatus has expired. */ - if (time_to.check_for_expired_networkstatus < now) { - networkstatus_t *ns = networkstatus_get_latest_consensus(); - /*XXXX RD: This value needs to be the same as REASONABLY_LIVE_TIME in - * networkstatus_get_reasonably_live_consensus(), but that value is way - * way too high. Arma: is the bridge issue there resolved yet? -NM */ + networkstatus_t *ns = networkstatus_get_latest_consensus(); + /*XXXX RD: This value needs to be the same as REASONABLY_LIVE_TIME in + * networkstatus_get_reasonably_live_consensus(), but that value is way + * way too high. Arma: is the bridge issue there resolved yet? -NM */ #define NS_EXPIRY_SLOP (24*60*60) - if (ns && ns->valid_until < now+NS_EXPIRY_SLOP && - router_have_minimum_dir_info()) { - router_dir_info_changed(); - } -#define CHECK_EXPIRED_NS_INTERVAL (2*60) - time_to.check_for_expired_networkstatus = now + CHECK_EXPIRED_NS_INTERVAL; + if (ns && ns->valid_until < now+NS_EXPIRY_SLOP && + router_have_minimum_dir_info()) { + router_dir_info_changed(); } +#define CHECK_EXPIRED_NS_INTERVAL (2*60) + return CHECK_EXPIRED_NS_INTERVAL; +} +static int +write_stats_file_callback(time_t now, const or_options_t *options) +{ /* 1g. Check whether we should write statistics to disk. */ - if (time_to.write_stats_files < now) { #define CHECK_WRITE_STATS_INTERVAL (60*60) - time_t next_time_to_write_stats_files = (time_to.write_stats_files > 0 ? - time_to.write_stats_files : now) + CHECK_WRITE_STATS_INTERVAL; - if (options->CellStatistics) { - time_t next_write = - rep_hist_buffer_stats_write(time_to.write_stats_files); - if (next_write && next_write < next_time_to_write_stats_files) - next_time_to_write_stats_files = next_write; - } - if (options->DirReqStatistics) { - time_t next_write = geoip_dirreq_stats_write(time_to.write_stats_files); - if (next_write && next_write < next_time_to_write_stats_files) - next_time_to_write_stats_files = next_write; - } - if (options->EntryStatistics) { - time_t next_write = geoip_entry_stats_write(time_to.write_stats_files); - if (next_write && next_write < next_time_to_write_stats_files) - next_time_to_write_stats_files = next_write; - } - if (options->HiddenServiceStatistics) { - time_t next_write = rep_hist_hs_stats_write(time_to.write_stats_files); - if (next_write && next_write < next_time_to_write_stats_files) - next_time_to_write_stats_files = next_write; - } - if (options->ExitPortStatistics) { - time_t next_write = rep_hist_exit_stats_write(time_to.write_stats_files); - if (next_write && next_write < next_time_to_write_stats_files) - next_time_to_write_stats_files = next_write; - } - if (options->ConnDirectionStatistics) { - time_t next_write = rep_hist_conn_stats_write(time_to.write_stats_files); - if (next_write && next_write < next_time_to_write_stats_files) - next_time_to_write_stats_files = next_write; - } - if (options->BridgeAuthoritativeDir) { - time_t next_write = rep_hist_desc_stats_write(time_to.write_stats_files); - if (next_write && next_write < next_time_to_write_stats_files) - next_time_to_write_stats_files = next_write; - } - time_to.write_stats_files = next_time_to_write_stats_files; + time_t next_time_to_write_stats_files = now + CHECK_WRITE_STATS_INTERVAL; + if (options->CellStatistics) { + time_t next_write = + rep_hist_buffer_stats_write(now); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } + if (options->DirReqStatistics) { + time_t next_write = geoip_dirreq_stats_write(now); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } + if (options->EntryStatistics) { + time_t next_write = geoip_entry_stats_write(now); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } + if (options->HiddenServiceStatistics) { + time_t next_write = rep_hist_hs_stats_write(now); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } + if (options->ExitPortStatistics) { + time_t next_write = rep_hist_exit_stats_write(now); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } + if (options->ConnDirectionStatistics) { + time_t next_write = rep_hist_conn_stats_write(now); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; } + if (options->BridgeAuthoritativeDir) { + time_t next_write = rep_hist_desc_stats_write(now); + if (next_write && next_write < next_time_to_write_stats_files) + next_time_to_write_stats_files = next_write; + } + + return safe_timer_diff(now, next_time_to_write_stats_files); +} + +static int +record_bridge_stats_callback(time_t now, const or_options_t *options) +{ + static int should_init_bridge_stats = 1; /* 1h. Check whether we should write bridge statistics to disk. */ if (should_record_bridge_info(options)) { - if (time_to.write_bridge_stats < now) { - if (should_init_bridge_stats) { - /* (Re-)initialize bridge statistics. */ + if (should_init_bridge_stats) { + /* (Re-)initialize bridge statistics. */ geoip_bridge_stats_init(now); - time_to.write_bridge_stats = now + WRITE_STATS_INTERVAL; should_init_bridge_stats = 0; - } else { - /* Possibly write bridge statistics to disk and ask when to write - * them next time. */ - time_to.write_bridge_stats = geoip_bridge_stats_write( - time_to.write_bridge_stats); - } + return WRITE_STATS_INTERVAL; + } else { + /* Possibly write bridge statistics to disk and ask when to write + * them next time. */ + time_t next = geoip_bridge_stats_write(now); + return safe_timer_diff(now, next); } } else if (!should_init_bridge_stats) { /* Bridge mode was turned off. Ensure that stats are re-initialized * next time bridge mode is turned on. */ should_init_bridge_stats = 1; } + return PERIODIC_EVENT_NO_UPDATE; +} +static int +clean_caches_callback(time_t now, const or_options_t *options) +{ /* Remove old information from rephist and the rend cache. */ - if (time_to.clean_caches < now) { - rep_history_clean(now - options->RephistTrackTime); - rend_cache_clean(now); - rend_cache_clean_v2_descs_as_dir(now, 0); - microdesc_cache_rebuild(NULL, 0); + rep_history_clean(now - options->RephistTrackTime); + rend_cache_clean(now, REND_CACHE_TYPE_CLIENT); + rend_cache_clean(now, REND_CACHE_TYPE_SERVICE); + rend_cache_clean_v2_descs_as_dir(now, 0); + microdesc_cache_rebuild(NULL, 0); #define CLEAN_CACHES_INTERVAL (30*60) - time_to.clean_caches = now + CLEAN_CACHES_INTERVAL; - } + return CLEAN_CACHES_INTERVAL; +} + +static int +rend_cache_failure_clean_callback(time_t now, const or_options_t *options) +{ + (void)options; /* We don't keep entries that are more than five minutes old so we try to * clean it as soon as we can since we want to make sure the client waits * as little as possible for reachability reasons. */ rend_cache_failure_clean(now); + return 30; +} +static int +retry_dns_callback(time_t now, const or_options_t *options) +{ + (void)now; #define RETRY_DNS_INTERVAL (10*60) /* If we're a server and initializing dns failed, retry periodically. */ - if (time_to.retry_dns_init < now) { - time_to.retry_dns_init = now + RETRY_DNS_INTERVAL; - if (is_server && has_dns_init_failed()) - dns_init(); - } + if (server_mode(options) && has_dns_init_failed()) + dns_init(); + return RETRY_DNS_INTERVAL; +} /* 2. Periodically, we consider force-uploading our descriptor * (if we've passed our internal checks). */ +static int +check_descriptor_callback(time_t now, const or_options_t *options) +{ /** How often do we check whether part of our router info has changed in a * way that would require an upload? That includes checking whether our IP * address has changed. */ @@ -1547,185 +1828,178 @@ run_scheduled_events(time_t now) /* 2b. Once per minute, regenerate and upload the descriptor if the old * one is inaccurate. */ - if (time_to.check_descriptor < now && !options->DisableNetwork) { - static int dirport_reachability_count = 0; - time_to.check_descriptor = now + CHECK_DESCRIPTOR_INTERVAL; + if (!options->DisableNetwork) { check_descriptor_bandwidth_changed(now); check_descriptor_ipaddress_changed(now); mark_my_descriptor_dirty_if_too_old(now); consider_publishable_server(0); - /* also, check religiously for reachability, if it's within the first - * 20 minutes of our uptime. */ - if (is_server && - (have_completed_a_circuit() || !any_predicted_circuits(now)) && - !we_are_hibernating()) { - if (stats_n_seconds_working < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { - consider_testing_reachability(1, dirport_reachability_count==0); - if (++dirport_reachability_count > 5) - dirport_reachability_count = 0; - } else if (time_to.recheck_bandwidth < now) { - /* If we haven't checked for 12 hours and our bandwidth estimate is - * low, do another bandwidth test. This is especially important for - * bridges, since they might go long periods without much use. */ - const routerinfo_t *me = router_get_my_routerinfo(); - if (time_to.recheck_bandwidth && me && - me->bandwidthcapacity < me->bandwidthrate && - me->bandwidthcapacity < 51200) { - reset_bandwidth_test(); - } -#define BANDWIDTH_RECHECK_INTERVAL (12*60*60) - time_to.recheck_bandwidth = now + BANDWIDTH_RECHECK_INTERVAL; - } - } - /* If any networkstatus documents are no longer recent, we need to * update all the descriptors' running status. */ /* Remove dead routers. */ + /* XXXX This doesn't belong here, but it was here in the pre- + * XXXX refactoring code. */ routerlist_remove_old_routers(); } - /* 2c. Every minute (or every second if TestingTorNetwork), check - * whether we want to download any networkstatus documents. */ + return CHECK_DESCRIPTOR_INTERVAL; +} -/* How often do we check whether we should download network status - * documents? */ -#define networkstatus_dl_check_interval(o) ((o)->TestingTorNetwork ? 1 : 60) +static int +check_for_reachability_bw_callback(time_t now, const or_options_t *options) +{ + /* XXXX This whole thing was stuck in the middle of what is now + * XXXX check_descriptor_callback. I'm not sure it's right. */ - if (!should_delay_dir_fetches(options, NULL) && - time_to.download_networkstatus < now) { - time_to.download_networkstatus = - now + networkstatus_dl_check_interval(options); - update_networkstatus_downloads(now); + static int dirport_reachability_count = 0; + /* also, check religiously for reachability, if it's within the first + * 20 minutes of our uptime. */ + if (server_mode(options) && + (have_completed_a_circuit() || !any_predicted_circuits(now)) && + !we_are_hibernating()) { + if (stats_n_seconds_working < TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT) { + consider_testing_reachability(1, dirport_reachability_count==0); + if (++dirport_reachability_count > 5) + dirport_reachability_count = 0; + return 1; + } else { + /* If we haven't checked for 12 hours and our bandwidth estimate is + * low, do another bandwidth test. This is especially important for + * bridges, since they might go long periods without much use. */ + const routerinfo_t *me = router_get_my_routerinfo(); + static int first_time = 1; + if (!first_time && me && + me->bandwidthcapacity < me->bandwidthrate && + me->bandwidthcapacity < 51200) { + reset_bandwidth_test(); + } + first_time = 0; +#define BANDWIDTH_RECHECK_INTERVAL (12*60*60) + return BANDWIDTH_RECHECK_INTERVAL; + } } + return CHECK_DESCRIPTOR_INTERVAL; +} - /* 2c. Let directory voting happen. */ - if (authdir_mode_v3(options)) - dirvote_act(options, now); +static int +fetch_networkstatus_callback(time_t now, const or_options_t *options) +{ + /* 2c. Every minute (or every second if TestingTorNetwork, or during + * client bootstrap), check whether we want to download any networkstatus + * documents. */ - /* 3a. Every second, we examine pending circuits and prune the - * ones which have been pending for more than a few seconds. - * We do this before step 4, so it can try building more if - * it's not comfortable with the number of available circuits. - */ - /* (If our circuit build timeout can ever become lower than a second (which - * it can't, currently), we should do this more often.) */ - circuit_expire_building(); + /* How often do we check whether we should download network status + * documents? */ + const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + now); + const int prefer_mirrors = !directory_fetches_from_authorities( + get_options()); + int networkstatus_dl_check_interval = 60; + /* check more often when testing, or when bootstrapping from mirrors + * (connection limits prevent too many connections being made) */ + if (options->TestingTorNetwork + || (we_are_bootstrapping && prefer_mirrors)) { + networkstatus_dl_check_interval = 1; + } - /* 3b. Also look at pending streams and prune the ones that 'began' - * a long time ago but haven't gotten a 'connected' yet. - * Do this before step 4, so we can put them back into pending - * state to be picked up by the new circuit. - */ - connection_ap_expire_beginning(); + if (should_delay_dir_fetches(options, NULL)) + return PERIODIC_EVENT_NO_UPDATE; - /* 3c. And expire connections that we've held open for too long. - */ - connection_expire_held_open(); + update_networkstatus_downloads(now); + return networkstatus_dl_check_interval; +} +static int +retry_listeners_callback(time_t now, const or_options_t *options) +{ + (void)now; + (void)options; /* 3d. And every 60 seconds, we relaunch listeners if any died. */ - if (!net_is_disabled() && time_to.check_listeners < now) { + if (!net_is_disabled()) { retry_all_listeners(NULL, NULL, 0); - time_to.check_listeners = now+60; - } - - /* 4. Every second, we try a new circuit if there are no valid - * circuits. Every NewCircuitPeriod seconds, we expire circuits - * that became dirty more than MaxCircuitDirtiness seconds ago, - * and we make a new circ if there are no clean circuits. - */ - have_dir_info = router_have_minimum_dir_info(); - if (have_dir_info && !net_is_disabled()) { - circuit_build_needed_circs(now); - } else { - circuit_expire_old_circs_as_needed(now); + return 60; } + return PERIODIC_EVENT_NO_UPDATE; +} - /* every 10 seconds, but not at the same second as other such events */ - if (now % 10 == 5) - circuit_expire_old_circuits_serverside(now); - - /* 5. We do housekeeping for each connection... */ - connection_or_set_bad_connections(NULL, 0); - for (i=0;i<smartlist_len(connection_array);i++) { - run_connection_housekeeping(i, now); - } - - /* 6. And remove any marked circuits... */ - circuit_close_all_marked(); - - /* 7. And upload service descriptors if necessary. */ - if (have_completed_a_circuit() && !net_is_disabled()) { - rend_consider_services_upload(now); - rend_consider_descriptor_republication(); - } - - /* 8. and blow away any connections that need to die. have to do this now, - * because if we marked a conn for close and left its socket -1, then - * we'll pass it to poll/select and bad things will happen. - */ - close_closeable_connections(); - - /* 8b. And if anything in our state is ready to get flushed to disk, we - * flush it. */ - or_state_save(now); - - /* 8c. Do channel cleanup just like for connections */ - channel_run_cleanup(); - channel_listener_run_cleanup(); +static int +expire_old_ciruits_serverside_callback(time_t now, const or_options_t *options) +{ + (void)options; + /* every 11 seconds, so not usually the same second as other such events */ + circuit_expire_old_circuits_serverside(now); + return 11; +} +static int +check_dns_honesty_callback(time_t now, const or_options_t *options) +{ + (void)now; /* 9. and if we're an exit node, check whether our DNS is telling stories * to us. */ - if (!net_is_disabled() && - public_server_mode(options) && - time_to.check_for_correct_dns < now && - ! router_my_exit_policy_is_reject_star()) { - if (!time_to.check_for_correct_dns) { - time_to.check_for_correct_dns = - crypto_rand_time_range(now + 60, now + 180); - } else { - dns_launch_correctness_checks(); - time_to.check_for_correct_dns = now + 12*3600 + - crypto_rand_int(12*3600); - } + if (net_is_disabled() || + ! public_server_mode(options) || + router_my_exit_policy_is_reject_star()) + return PERIODIC_EVENT_NO_UPDATE; + + static int first_time = 1; + if (first_time) { + /* Don't launch right when we start */ + first_time = 0; + return crypto_rand_int_range(60, 180); } + dns_launch_correctness_checks(); + return 12*3600 + crypto_rand_int(12*3600); +} + +static int +write_bridge_ns_callback(time_t now, const or_options_t *options) +{ /* 10. write bridge networkstatus file to disk */ - if (options->BridgeAuthoritativeDir && - time_to.write_bridge_status_file < now) { + if (options->BridgeAuthoritativeDir) { networkstatus_dump_bridge_status_to_file(now); #define BRIDGE_STATUSFILE_INTERVAL (30*60) - time_to.write_bridge_status_file = now+BRIDGE_STATUSFILE_INTERVAL; + return BRIDGE_STATUSFILE_INTERVAL; } + return PERIODIC_EVENT_NO_UPDATE; +} +static int +check_fw_helper_app_callback(time_t now, const or_options_t *options) +{ + if (net_is_disabled() || + ! server_mode(options) || + ! options->PortForwarding) { + return PERIODIC_EVENT_NO_UPDATE; + } /* 11. check the port forwarding app */ - if (!net_is_disabled() && - time_to.check_port_forwarding < now && - options->PortForwarding && - is_server) { + #define PORT_FORWARDING_CHECK_INTERVAL 5 - smartlist_t *ports_to_forward = get_list_of_ports_to_forward(); - if (ports_to_forward) { - tor_check_port_forwarding(options->PortForwardingHelper, - ports_to_forward, - now); - - SMARTLIST_FOREACH(ports_to_forward, char *, cp, tor_free(cp)); - smartlist_free(ports_to_forward); - } - time_to.check_port_forwarding = now+PORT_FORWARDING_CHECK_INTERVAL; - } + smartlist_t *ports_to_forward = get_list_of_ports_to_forward(); + if (ports_to_forward) { + tor_check_port_forwarding(options->PortForwardingHelper, + ports_to_forward, + now); - /* 11b. check pending unconfigured managed proxies */ - if (!net_is_disabled() && pt_proxies_configuration_pending()) - pt_configure_remaining_proxies(); + SMARTLIST_FOREACH(ports_to_forward, char *, cp, tor_free(cp)); + smartlist_free(ports_to_forward); + } + return PORT_FORWARDING_CHECK_INTERVAL; +} +static int +heartbeat_callback(time_t now, const or_options_t *options) +{ + static int first = 1; /* 12. write the heartbeat message */ - if (options->HeartbeatPeriod && - time_to.next_heartbeat <= now) { - if (time_to.next_heartbeat) /* don't log the first heartbeat */ - log_heartbeat(now); - time_to.next_heartbeat = now+options->HeartbeatPeriod; + if (first) { + first = 0; /* Skip the first one. */ + } else { + log_heartbeat(now); } + /* XXXX This isn't such a good way to handle possible changes in the + * callback event */ + return options->HeartbeatPeriod; } /** Timer: used to invoke second_elapsed_callback() once per second. */ @@ -1949,7 +2223,10 @@ dns_servers_relaunch_checks(void) { if (server_mode(get_options())) { dns_reset_correctness_checks(); - time_to.check_for_correct_dns = 0; + if (periodic_events_initialized) { + tor_assert(check_dns_honesty_event); + periodic_event_reschedule(check_dns_honesty_event); + } } } @@ -2043,6 +2320,13 @@ do_main_loop(void) { time_t now; + /* initialize the periodic events first, so that code that depends on the + * events being present does not assert. + */ + if (! periodic_events_initialized) { + initialize_periodic_events(); + } + /* initialize dns resolve map, spawn workers if needed */ if (dns_init() < 0) { if (get_options()->ServerDNSAllowBrokenConfig) @@ -2253,6 +2537,11 @@ run_main_loop_once(void) } } + /* This will be pretty fast if nothing new is pending. Note that this gets + * called once per libevent loop, which will make it happen once per group + * of events that fire, or once per second. */ + connection_ap_attach_pending(0); + return 1; } @@ -2827,6 +3116,7 @@ tor_free_all(int postfork) channel_tls_free_all(); channel_free_all(); connection_free_all(); + connection_edge_free_all(); scheduler_free_all(); memarea_clear_freelist(); nodelist_free_all(); @@ -2853,6 +3143,7 @@ tor_free_all(int postfork) smartlist_free(closeable_connection_lst); smartlist_free(active_linked_connection_lst); periodic_timer_free(second_timer); + teardown_periodic_events(); #ifndef USE_BUFFEREVENTS periodic_timer_free(refill_timer); #endif diff --git a/src/or/main.h b/src/or/main.h index 447d3f4eca..37e93d79d3 100644 --- a/src/or/main.h +++ b/src/or/main.h @@ -78,6 +78,8 @@ int tor_init(int argc, char **argv); #ifdef MAIN_PRIVATE STATIC void init_connection_lists(void); STATIC void close_closeable_connections(void); +STATIC void initialize_periodic_events(void); +STATIC void teardown_periodic_events(void); #endif #endif diff --git a/src/or/microdesc.c b/src/or/microdesc.c index a9bab3ddc6..dc23bcb632 100644 --- a/src/or/microdesc.c +++ b/src/or/microdesc.c @@ -47,14 +47,14 @@ struct microdesc_cache_t { static microdesc_cache_t *get_microdesc_cache_noload(void); /** Helper: computes a hash of <b>md</b> to place it in a hash table. */ -static INLINE unsigned int +static inline unsigned int microdesc_hash_(microdesc_t *md) { return (unsigned) siphash24g(md->digest, sizeof(md->digest)); } /** Helper: compares <b>a</b> and </b> for equality for hash-table purposes. */ -static INLINE int +static inline int microdesc_eq_(microdesc_t *a, microdesc_t *b) { return tor_memeq(a->digest, b->digest, DIGEST256_LEN); diff --git a/src/or/networkstatus.c b/src/or/networkstatus.c index 71a2c0f121..f3a8276689 100644 --- a/src/or/networkstatus.c +++ b/src/or/networkstatus.c @@ -85,8 +85,30 @@ static time_t time_to_download_next_consensus[N_CONSENSUS_FLAVORS]; /** Download status for the current consensus networkstatus. */ static download_status_t consensus_dl_status[N_CONSENSUS_FLAVORS] = { - { 0, 0, DL_SCHED_CONSENSUS }, - { 0, 0, DL_SCHED_CONSENSUS }, + { 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_FAILURE }, + { 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_FAILURE }, + }; + +#define N_CONSENSUS_BOOTSTRAP_SCHEDULES 2 +#define CONSENSUS_BOOTSTRAP_SOURCE_AUTHORITY 0 +#define CONSENSUS_BOOTSTRAP_SOURCE_ANY_DIRSERVER 1 + +/* Using DL_SCHED_INCREMENT_ATTEMPT on these schedules means that + * download_status_increment_failure won't increment these entries. + * However, any bootstrap connection failures that occur after we have + * a valid consensus will count against the failure counts on the non-bootstrap + * schedules. There should only be one of these, as all the others will have + * been cancelled. (This doesn't seem to be a significant issue.) */ +static download_status_t + consensus_bootstrap_dl_status[N_CONSENSUS_BOOTSTRAP_SCHEDULES] = + { + { 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_AUTHORITY, + DL_SCHED_INCREMENT_ATTEMPT }, + /* During bootstrap, DL_WANT_ANY_DIRSERVER means "use fallbacks". */ + { 0, 0, 0, DL_SCHED_CONSENSUS, DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_ATTEMPT }, }; /** True iff we have logged a warning about this OR's version being older than @@ -97,6 +119,10 @@ static int have_warned_about_old_version = 0; 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, + int we_are_bootstrapping); /** Forget that we've warned about anything networkstatus-related, so we will * give fresh warnings if the same behavior happens again. */ @@ -122,6 +148,9 @@ networkstatus_reset_download_failures(void) for (i=0; i < N_CONSENSUS_FLAVORS; ++i) download_status_reset(&consensus_dl_status[i]); + + for (i=0; i < N_CONSENSUS_BOOTSTRAP_SCHEDULES; ++i) + download_status_reset(&consensus_bootstrap_dl_status[i]); } /** Read every cached v3 consensus networkstatus from the disk. */ @@ -734,6 +763,55 @@ we_want_to_fetch_flavor(const or_options_t *options, int flavor) * fetching certs before we check whether there is a better one? */ #define DELAY_WHILE_FETCHING_CERTS (20*60) +/* Check if a downloaded consensus flavor should still wait for certificates + * to download now. + * If so, return 1. If not, fail dls and return 0. */ +static int +check_consensus_waiting_for_certs(int flavor, time_t now, + download_status_t *dls) +{ + consensus_waiting_for_certs_t *waiting; + + /* We should always have a known flavor, because we_want_to_fetch_flavor() + * filters out unknown flavors. */ + tor_assert(flavor >= 0 && flavor < N_CONSENSUS_FLAVORS); + + waiting = &consensus_waiting_for_certs[flavor]; + if (waiting->consensus) { + /* XXXX make sure this doesn't delay sane downloads. */ + if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) { + return 1; + } else { + if (!waiting->dl_failed) { + download_status_failed(dls, 0); + waiting->dl_failed=1; + } + } + } + + return 0; +} + +/* Return the maximum download tries for a consensus, based on options and + * whether we_are_bootstrapping. */ +static int +consensus_max_download_tries(const or_options_t *options, + int we_are_bootstrapping) +{ + int use_fallbacks = networkstatus_consensus_can_use_extra_fallbacks(options); + + if (we_are_bootstrapping) { + if (use_fallbacks) { + return options->TestingClientBootstrapConsensusMaxDownloadTries; + } else { + return + options->TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries; + } + } + + return options->TestingConsensusMaxDownloadTries; +} + /** If we want to download a fresh consensus, launch a new download as * appropriate. */ static void @@ -741,12 +819,19 @@ update_consensus_networkstatus_downloads(time_t now) { int i; const or_options_t *options = get_options(); + const int we_are_bootstrapping = networkstatus_consensus_is_boostrapping( + now); + const int use_multi_conn = + networkstatus_consensus_can_use_multiple_directories(options); + + if (should_delay_dir_fetches(options, NULL)) + return; for (i=0; i < N_CONSENSUS_FLAVORS; ++i) { /* XXXX need some way to download unknown flavors if we are caching. */ const char *resource; - consensus_waiting_for_certs_t *waiting; networkstatus_t *c; + int max_in_progress_conns = 1; if (! we_want_to_fetch_flavor(options, i)) continue; @@ -762,35 +847,166 @@ update_consensus_networkstatus_downloads(time_t now) resource = networkstatus_get_flavor_name(i); - /* Let's make sure we remembered to update consensus_dl_status */ - tor_assert(consensus_dl_status[i].schedule == DL_SCHED_CONSENSUS); + /* Check if we already have enough connections in progress */ + if (we_are_bootstrapping) { + max_in_progress_conns = + options->TestingClientBootstrapConsensusMaxInProgressTries; + } + if (connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + resource) + >= max_in_progress_conns) { + continue; + } - if (!download_status_is_ready(&consensus_dl_status[i], now, - options->TestingConsensusMaxDownloadTries)) - continue; /* We failed downloading a consensus too recently. */ - if (connection_dir_get_by_purpose_and_resource( - DIR_PURPOSE_FETCH_CONSENSUS, resource)) - continue; /* There's an in-progress download.*/ + /* Check if we want to launch another download for a usable consensus. + * Only used during bootstrap. */ + if (we_are_bootstrapping && use_multi_conn + && i == usable_consensus_flavor()) { + + /* Check if we're already downloading a usable consensus */ + int consens_conn_count = + connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + resource); + int connect_consens_conn_count = + connection_dir_count_by_purpose_resource_and_state( + DIR_PURPOSE_FETCH_CONSENSUS, + resource, + DIR_CONN_STATE_CONNECTING); + + if (i == usable_consensus_flavor() + && connect_consens_conn_count < consens_conn_count) { + continue; + } - waiting = &consensus_waiting_for_certs[i]; - if (waiting->consensus) { - /* XXXX make sure this doesn't delay sane downloads. */ - if (waiting->set_at + DELAY_WHILE_FETCHING_CERTS > now) { - continue; /* We're still getting certs for this one. */ - } else { - if (!waiting->dl_failed) { - download_status_failed(&consensus_dl_status[i], 0); - waiting->dl_failed=1; - } + /* Make multiple connections for a bootstrap consensus download */ + update_consensus_bootstrap_multiple_downloads(now, options, + we_are_bootstrapping); + } else { + /* Check if we failed downloading a consensus too recently */ + int max_dl_tries = consensus_max_download_tries(options, + we_are_bootstrapping); + + /* Let's make sure we remembered to update consensus_dl_status */ + tor_assert(consensus_dl_status[i].schedule == DL_SCHED_CONSENSUS); + + if (!download_status_is_ready(&consensus_dl_status[i], + now, + max_dl_tries)) { + continue; } + + /* Check if we're waiting for certificates to download */ + if (check_consensus_waiting_for_certs(i, now, &consensus_dl_status[i])) + continue; + + /* Try the requested attempt */ + log_info(LD_DIR, "Launching %s standard networkstatus consensus " + "download.", networkstatus_get_flavor_name(i)); + directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS, + ROUTER_PURPOSE_GENERAL, resource, + PDS_RETRY_IF_NO_SERVERS, + consensus_dl_status[i].want_authority); } + } +} - log_info(LD_DIR, "Launching %s networkstatus consensus download.", - networkstatus_get_flavor_name(i)); +/** When we're bootstrapping, launch one or more consensus download + * connections, if schedule indicates connection(s) should be made after now. + * If is_authority, connect to an authority, otherwise, use a fallback + * directory mirror. + */ +static void +update_consensus_bootstrap_attempt_downloads( + time_t now, + const or_options_t *options, + int we_are_bootstrapping, + download_status_t *dls, + download_want_authority_t want_authority) +{ + int max_dl_tries = consensus_max_download_tries(options, + we_are_bootstrapping); + const char *resource = networkstatus_get_flavor_name( + usable_consensus_flavor()); + + /* Let's make sure we remembered to update schedule */ + tor_assert(dls->schedule == DL_SCHED_CONSENSUS); + + /* Allow for multiple connections in the same second, if the schedule value + * is 0. */ + while (download_status_is_ready(dls, now, max_dl_tries)) { + log_info(LD_DIR, "Launching %s bootstrap %s networkstatus consensus " + "download.", resource, (want_authority == DL_WANT_AUTHORITY + ? "authority" + : "mirror")); directory_get_from_dirserver(DIR_PURPOSE_FETCH_CONSENSUS, ROUTER_PURPOSE_GENERAL, resource, - PDS_RETRY_IF_NO_SERVERS); + PDS_RETRY_IF_NO_SERVERS, want_authority); + /* schedule the next attempt */ + download_status_increment_attempt(dls, resource, now); + } +} + +/** If we're bootstrapping, check the connection schedules and see if we want + * to make additional, potentially concurrent, consensus download + * connections. + * Only call when bootstrapping, and when we want to make additional + * connections. Only nodes that satisfy + * networkstatus_consensus_can_use_multiple_directories make additonal + * connections. + */ +static void +update_consensus_bootstrap_multiple_downloads(time_t now, + const or_options_t *options, + int we_are_bootstrapping) +{ + const int usable_flavor = usable_consensus_flavor(); + + /* make sure we can use multiple connections */ + if (!networkstatus_consensus_can_use_multiple_directories(options)) { + return; + } + + /* If we've managed to validate a usable consensus, don't make additonal + * connections. */ + if (!we_are_bootstrapping) { + return; + } + + /* Launch concurrent consensus download attempt(s) based on the mirror and + * authority schedules. Try the mirror first - this makes it slightly more + * likely that we'll connect to the fallback first, and then end the + * authority connection attempt. */ + + /* If a consensus download fails because it's waiting for certificates, + * we'll fail both the authority and fallback schedules. This is better than + * failing only one of the schedules, and having the other continue + * unchecked. + */ + + /* If we don't have or can't use extra fallbacks, don't try them. */ + if (networkstatus_consensus_can_use_extra_fallbacks(options)) { + download_status_t *dls_f = + &consensus_bootstrap_dl_status[CONSENSUS_BOOTSTRAP_SOURCE_ANY_DIRSERVER]; + + if (!check_consensus_waiting_for_certs(usable_flavor, now, dls_f)) { + /* During bootstrap, DL_WANT_ANY_DIRSERVER means "use fallbacks". */ + update_consensus_bootstrap_attempt_downloads(now, options, + we_are_bootstrapping, dls_f, + DL_WANT_ANY_DIRSERVER); + } + } + + /* Now try an authority. */ + download_status_t *dls_a = + &consensus_bootstrap_dl_status[CONSENSUS_BOOTSTRAP_SOURCE_AUTHORITY]; + + if (!check_consensus_waiting_for_certs(usable_flavor, now, dls_a)) { + update_consensus_bootstrap_attempt_downloads(now, options, + we_are_bootstrapping, dls_a, + DL_WANT_AUTHORITY); } } @@ -1057,6 +1273,104 @@ networkstatus_get_reasonably_live_consensus(time_t now, int flavor) return NULL; } +/** Check if we're bootstrapping a consensus download. This means that we are + * only using the authorities and fallback directory mirrors to download the + * consensus flavour we'll use. */ +int +networkstatus_consensus_is_boostrapping(time_t now) +{ + /* If we don't have a consensus, we must still be bootstrapping */ + return !networkstatus_get_reasonably_live_consensus( + now, + usable_consensus_flavor()); +} + +/** Check if we can use multiple directories for a consensus download. + * Only clients (including bridge relays, which act like clients) benefit + * from multiple simultaneous consensus downloads. */ +int +networkstatus_consensus_can_use_multiple_directories( + const or_options_t *options) +{ + /* If we are a client, bridge, bridge client, or hidden service */ + return !public_server_mode(options); +} + +/** Check if we can use fallback directory mirrors for a consensus download. + * If we have fallbacks and don't want to fetch from the authorities, + * we can use them. */ +int +networkstatus_consensus_can_use_extra_fallbacks(const or_options_t *options) +{ + /* The list length comparisons are a quick way to check if we have any + * non-authority fallback directories. If we ever have any authorities that + * aren't fallback directories, we will need to change this code. */ + tor_assert(smartlist_len(router_get_fallback_dir_servers()) + >= smartlist_len(router_get_trusted_dir_servers())); + /* If we don't fetch from the authorities, and we have additional mirrors, + * we can use them. */ + return (!directory_fetches_from_authorities(options) + && (smartlist_len(router_get_fallback_dir_servers()) + > smartlist_len(router_get_trusted_dir_servers()))); +} + +/* Check if there is more than 1 consensus connection retrieving the usable + * consensus flavor. If so, return 1, if not, return 0. + * + * During normal operation, Tor only makes one consensus download + * connection. But clients can make multiple simultaneous consensus + * connections to improve bootstrap speed and reliability. + * + * If there is more than one connection, we must have connections left + * over from bootstrapping. However, some of the connections may have + * completed and been cleaned up, so it is not sufficient to check the + * return value of this function to see if a client could make multiple + * bootstrap connections. Use + * networkstatus_consensus_can_use_multiple_directories() + * and networkstatus_consensus_is_boostrapping(). */ +int +networkstatus_consensus_has_excess_connections(void) +{ + const char *usable_resource = networkstatus_get_flavor_name( + usable_consensus_flavor()); + const int consens_conn_usable_count = + connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource); + /* The maximum number of connections we want downloading a usable consensus + * Always 1, whether bootstrapping or not. */ + const int max_expected_consens_conn_usable_count = 1; + + if (consens_conn_usable_count > max_expected_consens_conn_usable_count) { + return 1; + } + + return 0; +} + +/* Is tor currently downloading a consensus of the usable flavor? */ +int +networkstatus_consensus_is_downloading_usable_flavor(void) +{ + const char *usable_resource = networkstatus_get_flavor_name( + usable_consensus_flavor()); + const int consens_conn_usable_count = + connection_dir_count_by_purpose_and_resource( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource); + + const int connect_consens_conn_usable_count = + connection_dir_count_by_purpose_resource_and_state( + DIR_PURPOSE_FETCH_CONSENSUS, + usable_resource, + DIR_CONN_STATE_CONNECTING); + if (connect_consens_conn_usable_count < consens_conn_usable_count) { + return 1; + } + + return 0; +} + /** Given two router status entries for the same router identity, return 1 if * if the contents have changed between them. Otherwise, return 0. */ static int @@ -1147,6 +1461,38 @@ networkstatus_copy_old_consensus_info(networkstatus_t *new_c, } SMARTLIST_FOREACH_JOIN_END(rs_old, rs_new); } +#ifdef TOR_UNIT_TESTS +/**Accept a <b>flavor</b> consensus <b>c</b> without any additional + * validation. This is exclusively for unit tests. + * We copy any ancillary information from a pre-existing consensus + * and then free the current one and replace it with the newly + * provided instance. Returns -1 on unrecognized flavor, 0 otherwise. + */ +int +networkstatus_set_current_consensus_from_ns(networkstatus_t *c, + const char *flavor) +{ + int flav = networkstatus_parse_flavor_name(flavor); + switch (flav) { + case FLAV_NS: + if (current_ns_consensus) { + networkstatus_copy_old_consensus_info(c, current_ns_consensus); + networkstatus_vote_free(current_ns_consensus); + } + current_ns_consensus = c; + break; + case FLAV_MICRODESC: + if (current_md_consensus) { + networkstatus_copy_old_consensus_info(c, current_md_consensus); + networkstatus_vote_free(current_md_consensus); + } + current_md_consensus = c; + break; + } + return current_md_consensus ? 0 : -1; +} +#endif //TOR_UNIT_TESTS + /** Try to replace the current cached v3 networkstatus with the one in * <b>consensus</b>. If we don't have enough certificates to validate it, * store it in consensus_waiting_for_certs and launch a certificate fetch. diff --git a/src/or/networkstatus.h b/src/or/networkstatus.h index d6e9e37013..4eab4d83f8 100644 --- a/src/or/networkstatus.h +++ b/src/or/networkstatus.h @@ -70,6 +70,14 @@ MOCK_DECL(networkstatus_t *,networkstatus_get_latest_consensus_by_flavor, networkstatus_t *networkstatus_get_live_consensus(time_t now); networkstatus_t *networkstatus_get_reasonably_live_consensus(time_t now, int flavor); +int networkstatus_consensus_is_boostrapping(time_t now); +int networkstatus_consensus_can_use_multiple_directories( + const or_options_t *options); +int networkstatus_consensus_can_use_extra_fallbacks( + const or_options_t *options); +int networkstatus_consensus_has_excess_connections(void); +int networkstatus_consensus_is_downloading_usable_flavor(void); + #define NSSET_FROM_CACHE 1 #define NSSET_WAS_WAITING_FOR_CERTS 2 #define NSSET_DONT_DOWNLOAD_CERTS 4 @@ -106,6 +114,10 @@ int networkstatus_get_weight_scale_param(networkstatus_t *ns); #ifdef NETWORKSTATUS_PRIVATE STATIC void vote_routerstatus_free(vote_routerstatus_t *rs); +#ifdef TOR_UNIT_TESTS +STATIC int networkstatus_set_current_consensus_from_ns(networkstatus_t *c, + const char *flavor); +#endif // TOR_UNIT_TESTS #endif #endif diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 2f272a1d56..056d5e8cb9 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -57,13 +57,13 @@ typedef struct nodelist_t { } nodelist_t; -static INLINE unsigned int +static inline unsigned int node_id_hash(const node_t *node) { return (unsigned) siphash24g(node->identity, DIGEST_LEN); } -static INLINE unsigned int +static inline unsigned int node_id_eq(const node_t *node1, const node_t *node2) { return tor_memeq(node1->identity, node2->identity, DIGEST_LEN); @@ -291,7 +291,7 @@ nodelist_set_consensus(networkstatus_t *ns) } /** Helper: return true iff a node has a usable amount of information*/ -static INLINE int +static inline int node_is_usable(const node_t *node) { return (node->rs) || (node->ri); @@ -644,12 +644,19 @@ node_is_named(const node_t *node) int node_is_dir(const node_t *node) { - if (node->rs) - return node->rs->dir_port != 0; - else if (node->ri) - return node->ri->dir_port != 0; - else + if (node->rs) { + routerstatus_t * rs = node->rs; + /* This is true if supports_tunnelled_dir_requests is true which + * indicates that we support directory request tunnelled or through the + * DirPort. */ + return rs->is_v2_dir; + } else if (node->ri) { + routerinfo_t * ri = node->ri; + /* Both tunnelled request is supported or DirPort is set. */ + return ri->supports_tunnelled_dir_requests; + } else { return 0; + } } /** Return true iff <b>node</b> has either kind of usable descriptor -- that @@ -1021,7 +1028,7 @@ nodelist_refresh_countries(void) /** Return true iff router1 and router2 have similar enough network addresses * that we should treat them as being in the same family */ -static INLINE int +static inline int addrs_in_same_network_family(const tor_addr_t *a1, const tor_addr_t *a2) { @@ -1045,7 +1052,7 @@ node_nickname_matches(const node_t *node, const char *nickname) } /** Return true iff <b>node</b> is named by some nickname in <b>lst</b>. */ -static INLINE int +static inline int node_in_nickname_smartlist(const smartlist_t *lst, const node_t *node) { if (!lst) return 0; diff --git a/src/or/onion_fast.c b/src/or/onion_fast.c index 7584112570..22bef4eee0 100644 --- a/src/or/onion_fast.c +++ b/src/or/onion_fast.c @@ -30,10 +30,7 @@ fast_onionskin_create(fast_handshake_state_t **handshake_state_out, { fast_handshake_state_t *s; *handshake_state_out = s = tor_malloc(sizeof(fast_handshake_state_t)); - if (crypto_rand((char*)s->state, sizeof(s->state)) < 0) { - tor_free(s); - return -1; - } + crypto_rand((char*)s->state, sizeof(s->state)); memcpy(handshake_out, s->state, DIGEST_LEN); return 0; } @@ -56,8 +53,7 @@ fast_server_handshake(const uint8_t *key_in, /* DIGEST_LEN bytes */ size_t out_len; int r = -1; - if (crypto_rand((char*)handshake_reply_out, DIGEST_LEN)<0) - return -1; + crypto_rand((char*)handshake_reply_out, DIGEST_LEN); memcpy(tmp, key_in, DIGEST_LEN); memcpy(tmp+DIGEST_LEN, handshake_reply_out, DIGEST_LEN); diff --git a/src/or/or.h b/src/or/or.h index 4496cbcec3..52bf33720a 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -915,18 +915,18 @@ typedef enum { #define VAR_CELL_MAX_HEADER_SIZE 7 static int get_cell_network_size(int wide_circ_ids); -static INLINE int get_cell_network_size(int wide_circ_ids) +static inline int get_cell_network_size(int wide_circ_ids) { return wide_circ_ids ? CELL_MAX_NETWORK_SIZE : CELL_MAX_NETWORK_SIZE - 2; } static int get_var_cell_header_size(int wide_circ_ids); -static INLINE int get_var_cell_header_size(int wide_circ_ids) +static inline int get_var_cell_header_size(int wide_circ_ids) { return wide_circ_ids ? VAR_CELL_MAX_HEADER_SIZE : VAR_CELL_MAX_HEADER_SIZE - 2; } static int get_circ_id_size(int wide_circ_ids); -static INLINE int get_circ_id_size(int wide_circ_ids) +static inline int get_circ_id_size(int wide_circ_ids) { return wide_circ_ids ? 4 : 2; } @@ -1646,6 +1646,13 @@ typedef struct entry_connection_t { * request that we're going to try to answer. */ struct evdns_server_request *dns_server_request; +#define DEBUGGING_17659 + +#ifdef DEBUGGING_17659 + uint16_t marked_pending_circ_line; + const char *marked_pending_circ_file; +#endif + #define NUM_CIRCUITS_LAUNCHED_THRESHOLD 10 /** Number of times we've launched a circuit to handle this stream. If * it gets too high, that could indicate an inconsistency between our @@ -1799,38 +1806,38 @@ static control_connection_t *TO_CONTROL_CONN(connection_t *); * invalid. */ static listener_connection_t *TO_LISTENER_CONN(connection_t *); -static INLINE or_connection_t *TO_OR_CONN(connection_t *c) +static inline or_connection_t *TO_OR_CONN(connection_t *c) { tor_assert(c->magic == OR_CONNECTION_MAGIC); return DOWNCAST(or_connection_t, c); } -static INLINE dir_connection_t *TO_DIR_CONN(connection_t *c) +static inline dir_connection_t *TO_DIR_CONN(connection_t *c) { tor_assert(c->magic == DIR_CONNECTION_MAGIC); return DOWNCAST(dir_connection_t, c); } -static INLINE edge_connection_t *TO_EDGE_CONN(connection_t *c) +static inline edge_connection_t *TO_EDGE_CONN(connection_t *c) { tor_assert(c->magic == EDGE_CONNECTION_MAGIC || c->magic == ENTRY_CONNECTION_MAGIC); return DOWNCAST(edge_connection_t, c); } -static INLINE entry_connection_t *TO_ENTRY_CONN(connection_t *c) +static inline entry_connection_t *TO_ENTRY_CONN(connection_t *c) { tor_assert(c->magic == ENTRY_CONNECTION_MAGIC); return (entry_connection_t*) SUBTYPE_P(c, entry_connection_t, edge_.base_); } -static INLINE entry_connection_t *EDGE_TO_ENTRY_CONN(edge_connection_t *c) +static inline entry_connection_t *EDGE_TO_ENTRY_CONN(edge_connection_t *c) { tor_assert(c->base_.magic == ENTRY_CONNECTION_MAGIC); return (entry_connection_t*) SUBTYPE_P(c, entry_connection_t, edge_); } -static INLINE control_connection_t *TO_CONTROL_CONN(connection_t *c) +static inline control_connection_t *TO_CONTROL_CONN(connection_t *c) { tor_assert(c->magic == CONTROL_CONNECTION_MAGIC); return DOWNCAST(control_connection_t, c); } -static INLINE listener_connection_t *TO_LISTENER_CONN(connection_t *c) +static inline listener_connection_t *TO_LISTENER_CONN(connection_t *c) { tor_assert(c->magic == LISTENER_CONNECTION_MAGIC); return DOWNCAST(listener_connection_t, c); @@ -1946,8 +1953,8 @@ typedef enum { } saved_location_t; #define saved_location_bitfield_t ENUM_BF(saved_location_t) -/** Enumeration: what kind of download schedule are we using for a given - * object? */ +/** Enumeration: what directory object is being downloaded? + * This determines which schedule is selected to perform the download. */ typedef enum { DL_SCHED_GENERIC = 0, DL_SCHED_CONSENSUS = 1, @@ -1955,15 +1962,74 @@ typedef enum { } download_schedule_t; #define download_schedule_bitfield_t ENUM_BF(download_schedule_t) +/** Enumeration: is the download schedule for downloading from an authority, + * or from any available directory mirror? + * During bootstrap, "any" means a fallback (or an authority, if there + * are no fallbacks). + * When we have a valid consensus, "any" means any directory server. */ +typedef enum { + DL_WANT_ANY_DIRSERVER = 0, + DL_WANT_AUTHORITY = 1, +} download_want_authority_t; +#define download_want_authority_bitfield_t \ + ENUM_BF(download_want_authority_t) + +/** Enumeration: do we want to increment the schedule position each time a + * connection is attempted (these attempts can be concurrent), or do we want + * to increment the schedule position after a connection fails? */ +typedef enum { + DL_SCHED_INCREMENT_FAILURE = 0, + DL_SCHED_INCREMENT_ATTEMPT = 1, +} download_schedule_increment_t; +#define download_schedule_increment_bitfield_t \ + ENUM_BF(download_schedule_increment_t) + /** Information about our plans for retrying downloads for a downloadable - * object. */ + * directory object. + * Each type of downloadable directory object has a corresponding retry + * <b>schedule</b>, which can be different depending on whether the object is + * being downloaded from an authority or a mirror (<b>want_authority</b>). + * <b>next_attempt_at</b> contains the next time we will attempt to download + * the object. + * For schedules that <b>increment_on</b> failure, <b>n_download_failures</b> + * is used to determine the position in the schedule. (Each schedule is a + * smartlist of integer delays, parsed from a CSV option.) Every time a + * connection attempt fails, <b>n_download_failures</b> is incremented, + * the new delay value is looked up from the schedule, and + * <b>next_attempt_at</b> is set delay seconds from the time the previous + * connection failed. Therefore, at most one failure-based connection can be + * in progress for each download_status_t. + * For schedules that <b>increment_on</b> attempt, <b>n_download_attempts</b> + * is used to determine the position in the schedule. Every time a + * connection attempt is made, <b>n_download_attempts</b> is incremented, + * the new delay value is looked up from the schedule, and + * <b>next_attempt_at</b> is set delay seconds from the time the previous + * connection was attempted. Therefore, multiple concurrent attempted-based + * connections can be in progress for each download_status_t. + * After an object is successfully downloaded, any other concurrent connections + * are terminated. A new schedule which starts at position 0 is used for + * subsequent downloads of the same object. + */ typedef struct download_status_t { - time_t next_attempt_at; /**< When should we try downloading this descriptor + time_t next_attempt_at; /**< When should we try downloading this object * again? */ - uint8_t n_download_failures; /**< Number of failures trying to download the - * most recent descriptor. */ - download_schedule_bitfield_t schedule : 8; - + uint8_t n_download_failures; /**< Number of failed downloads of the most + * recent object, since the last success. */ + uint8_t n_download_attempts; /**< Number of (potentially concurrent) attempts + * to download the most recent object, since + * the last success. */ + download_schedule_bitfield_t schedule : 8; /**< What kind of object is being + * downloaded? This determines the + * schedule used for the download. + */ + download_want_authority_bitfield_t want_authority : 1; /**< Is the download + * happening from an authority + * or a mirror? This determines + * the schedule used for the + * download. */ + download_schedule_increment_bitfield_t increment_on : 1; /**< does this + * schedule increment on each attempt, + * or after each failure? */ } download_status_t; /** If n_download_failures is this high, the download can never happen. */ @@ -2081,6 +2147,11 @@ typedef struct { * tests for it. */ unsigned int needs_retest_if_added:1; + /** True iff this router included "tunnelled-dir-server" in its descriptor, + * implying it accepts tunnelled directory requests, or it advertised + * dir_port > 0. */ + unsigned int supports_tunnelled_dir_requests:1; + /** Tor can use this router for general positions in circuits; we got it * from a directory server as usual, or we're an authority and a server * uploaded it. */ @@ -2158,6 +2229,9 @@ typedef struct routerstatus_t { * an exit node. */ unsigned int is_hs_dir:1; /**< True iff this router is a v2-or-later hidden * service directory. */ + unsigned int is_v2_dir:1; /** True iff this router publishes an open DirPort + * or it claims to accept tunnelled dir requests. + */ /** True iff we know version info for this router. (i.e., a "v" entry was * included.) We'll replace all these with a big tor_version_t or a char[] * if the number of traits we care about ever becomes incredibly big. */ @@ -2259,7 +2333,7 @@ typedef struct microdesc_t { curve25519_public_key_t *onion_curve25519_pkey; /** Ed25519 identity key, if included. */ ed25519_public_key_t *ed25519_identity_pkey; - /** As routerinfo_t.ipv6_add */ + /** As routerinfo_t.ipv6_addr */ tor_addr_t ipv6_addr; /** As routerinfo_t.ipv6_orport */ uint16_t ipv6_orport; @@ -2277,7 +2351,7 @@ typedef struct microdesc_t { * Specifically, a node_t is a Tor router as we are using it: a router that * we are considering for circuits, connections, and so on. A node_t is a * thin wrapper around the routerstatus, routerinfo, and microdesc for a - * single wrapper, and provides a consistent interface for all of them. + * single router, and provides a consistent interface for all of them. * * Also, a node_t has mutable state. While a routerinfo, a routerstatus, * and a microdesc have[*] only the information read from a router @@ -2892,6 +2966,14 @@ typedef struct circuit_t { * where this circuit was marked.) */ const char *marked_for_close_file; /**< For debugging: in which file was this * circuit marked for close? */ + /** For what reason (See END_CIRC_REASON...) is this circuit being closed? + * This field is set in circuit_mark_for_close and used later in + * circuit_about_to_free. */ + uint16_t marked_for_close_reason; + /** As marked_for_close_reason, but reflects the underlying reason for + * closing this circuit. + */ + uint16_t marked_for_close_orig_reason; /** Unique ID for measuring tunneled network status requests. */ uint64_t dirreq_id; @@ -3281,27 +3363,27 @@ static const origin_circuit_t *CONST_TO_ORIGIN_CIRCUIT(const circuit_t *); /** Return 1 iff <b>node</b> has Exit flag and no BadExit flag. * Otherwise, return 0. */ -static INLINE int node_is_good_exit(const node_t *node) +static inline int node_is_good_exit(const node_t *node) { return node->is_exit && ! node->is_bad_exit; } -static INLINE or_circuit_t *TO_OR_CIRCUIT(circuit_t *x) +static inline or_circuit_t *TO_OR_CIRCUIT(circuit_t *x) { tor_assert(x->magic == OR_CIRCUIT_MAGIC); return DOWNCAST(or_circuit_t, x); } -static INLINE const or_circuit_t *CONST_TO_OR_CIRCUIT(const circuit_t *x) +static inline const or_circuit_t *CONST_TO_OR_CIRCUIT(const circuit_t *x) { tor_assert(x->magic == OR_CIRCUIT_MAGIC); return DOWNCAST(or_circuit_t, x); } -static INLINE origin_circuit_t *TO_ORIGIN_CIRCUIT(circuit_t *x) +static inline origin_circuit_t *TO_ORIGIN_CIRCUIT(circuit_t *x) { tor_assert(x->magic == ORIGIN_CIRCUIT_MAGIC); return DOWNCAST(origin_circuit_t, x); } -static INLINE const origin_circuit_t *CONST_TO_ORIGIN_CIRCUIT( +static inline const origin_circuit_t *CONST_TO_ORIGIN_CIRCUIT( const circuit_t *x) { tor_assert(x->magic == ORIGIN_CIRCUIT_MAGIC); @@ -3424,9 +3506,11 @@ typedef struct { * each log message occurs? */ int TruncateLogFile; /**< Boolean: Should we truncate the log file before we start writing? */ + char *SyslogIdentityTag; /**< Identity tag to add for syslog logging. */ char *DebugLogFile; /**< Where to send verbose log messages. */ char *DataDirectory; /**< OR only: where to store long-term data. */ + int DataDirectoryGroupReadable; /**< Boolean: Is the DataDirectory g+r? */ char *Nickname; /**< OR only: nickname of this onion router. */ char *Address; /**< OR only: configured address for this onion router. */ char *PidFile; /**< Where to store PID of Tor process. */ @@ -3748,6 +3832,8 @@ typedef struct { /** List of fallback directory servers */ config_line_t *FallbackDir; + /** Whether to use the default hard-coded FallbackDirs */ + int UseDefaultFallbackDirs; /** Weight to apply to all directory authority rates if considering them * along with fallbackdirs */ @@ -3807,9 +3893,11 @@ typedef struct { * hibernate." */ /** How do we determine when our AccountingMax has been reached? * "max" for when in or out reaches AccountingMax - * "sum for when in plus out reaches AccountingMax */ + * "sum" for when in plus out reaches AccountingMax + * "in" for when in reaches AccountingMax + * "out" for when out reaches AccountingMax */ char *AccountingRule_option; - enum { ACCT_MAX, ACCT_SUM } AccountingRule; + enum { ACCT_MAX, ACCT_SUM, ACCT_IN, ACCT_OUT } AccountingRule; /** Base64-encoded hash of accepted passwords for the control system. */ config_line_t *HashedControlPassword; @@ -3883,6 +3971,10 @@ typedef struct { /** Should we fetch our dir info at the start of the consensus period? */ int FetchDirInfoExtraEarly; + int DirCache; /**< Cache all directory documents and accept requests via + * tunnelled dir conns from clients. If 1, enabled (default); + * If 0, disabled. */ + char *VirtualAddrNetworkIPv4; /**< Address and mask to hand out for virtual * MAPADDRESS requests for IPv4 addresses */ char *VirtualAddrNetworkIPv6; /**< Address and mask to hand out for virtual @@ -4014,7 +4106,7 @@ typedef struct { char *ConsensusParams; /** Authority only: minimum number of measured bandwidths we must see - * before we only beliee measured bandwidths to assign flags. */ + * before we only believe measured bandwidths to assign flags. */ int MinMeasuredBWsForAuthToIgnoreAdvertised; /** The length of time that we think an initial consensus should be fresh. @@ -4059,6 +4151,36 @@ typedef struct { * on testing networks. */ smartlist_t *TestingClientConsensusDownloadSchedule; + /** Schedule for when clients should download consensuses from authorities + * if they are bootstrapping (that is, they don't have a usable, reasonably + * live consensus). Only used by clients fetching from a list of fallback + * directory mirrors. + * + * This schedule is incremented by (potentially concurrent) connection + * attempts, unlike other schedules, which are incremented by connection + * failures. Only altered on testing networks. */ + smartlist_t *TestingClientBootstrapConsensusAuthorityDownloadSchedule; + + /** Schedule for when clients should download consensuses from fallback + * directory mirrors if they are bootstrapping (that is, they don't have a + * usable, reasonably live consensus). Only used by clients fetching from a + * list of fallback directory mirrors. + * + * This schedule is incremented by (potentially concurrent) connection + * attempts, unlike other schedules, which are incremented by connection + * failures. Only altered on testing networks. */ + smartlist_t *TestingClientBootstrapConsensusFallbackDownloadSchedule; + + /** Schedule for when clients should download consensuses from authorities + * if they are bootstrapping (that is, they don't have a usable, reasonably + * live consensus). Only used by clients which don't have or won't fetch + * from a list of fallback directory mirrors. + * + * This schedule is incremented by (potentially concurrent) connection + * attempts, unlike other schedules, which are incremented by connection + * failures. Only altered on testing networks. */ + smartlist_t *TestingClientBootstrapConsensusAuthorityOnlyDownloadSchedule; + /** Schedule for when clients should download bridge descriptors. Only * altered on testing networks. */ smartlist_t *TestingBridgeDownloadSchedule; @@ -4076,6 +4198,21 @@ typedef struct { * up? Only altered on testing networks. */ int TestingConsensusMaxDownloadTries; + /** How many times will a client try to fetch a consensus while + * bootstrapping using a list of fallback directories, before it gives up? + * Only altered on testing networks. */ + int TestingClientBootstrapConsensusMaxDownloadTries; + + /** How many times will a client try to fetch a consensus while + * bootstrapping using only a list of authorities, before it gives up? + * Only altered on testing networks. */ + int TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries; + + /** How many simultaneous in-progress connections will we make when trying + * to fetch a consensus before we wait for one to complete, timeout, or + * error out? Only altered on testing networks. */ + int TestingClientBootstrapConsensusMaxInProgressTries; + /** How many times will we try to download a router's descriptor before * giving up? Only altered on testing networks. */ int TestingDescriptorMaxDownloadTries; @@ -4316,6 +4453,9 @@ typedef struct { int keygen_passphrase_fd; int change_key_passphrase; char *master_key_fname; + + /** Autobool: Do we try to retain capabilities if we can? */ + int KeepBindCapabilities; } or_options_t; /** Persistent state for an onion router, as saved to disk. */ @@ -4388,7 +4528,7 @@ typedef struct { /** Change the next_write time of <b>state</b> to <b>when</b>, unless the * state is already scheduled to be written to disk earlier than <b>when</b>. */ -static INLINE void or_state_mark_dirty(or_state_t *state, time_t when) +static inline void or_state_mark_dirty(or_state_t *state, time_t when) { if (state->next_write > when) state->next_write = when; @@ -4999,9 +5139,11 @@ typedef struct dir_server_t { char *description; char *nickname; char *address; /**< Hostname. */ + tor_addr_t ipv6_addr; /**< IPv6 address if present; AF_UNSPEC if not */ uint32_t addr; /**< IPv4 address. */ uint16_t dir_port; /**< Directory port. */ uint16_t or_port; /**< OR port: Used for tunneling connections. */ + uint16_t ipv6_orport; /**< OR port corresponding to ipv6_addr. */ double weight; /** Weight used when selecting this node at random */ char digest[DIGEST_LEN]; /**< Digest of identity key. */ char v3_identity_digest[DIGEST_LEN]; /**< Digest of v3 (authority only, diff --git a/src/or/periodic.c b/src/or/periodic.c new file mode 100644 index 0000000000..109717f738 --- /dev/null +++ b/src/or/periodic.c @@ -0,0 +1,120 @@ +/* Copyright (c) 2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "compat_libevent.h" +#include "config.h" +#include "periodic.h" + +#ifdef HAVE_EVENT2_EVENT_H +#include <event2/event.h> +#else +#include <event.h> +#endif + +/** We disable any interval greater than this number of seconds, on the + * grounds that it is probably an absolute time mistakenly passed in as a + * relative time. + */ +static const int MAX_INTERVAL = 10 * 365 * 86400; + +/** Set the event <b>event</b> to run in <b>next_interval</b> seconds from + * now. */ +static void +periodic_event_set_interval(periodic_event_item_t *event, + time_t next_interval) +{ + tor_assert(next_interval < MAX_INTERVAL); + struct timeval tv; + tv.tv_sec = next_interval; + tv.tv_usec = 0; + event_add(event->ev, &tv); +} + +/** Wraps dispatches for periodic events, <b>data</b> will be a pointer to the + * event that needs to be called */ +static void +periodic_event_dispatch(evutil_socket_t fd, short what, void *data) +{ + (void)fd; + (void)what; + periodic_event_item_t *event = data; + + time_t now = time(NULL); + const or_options_t *options = get_options(); + log_debug(LD_GENERAL, "Dispatching %s", event->name); + int r = event->fn(now, options); + int next_interval = 0; + + /* update the last run time if action was taken */ + if (r==0) { + log_err(LD_BUG, "Invalid return value for periodic event from %s.", + event->name); + tor_assert(r != 0); + } else if (r > 0) { + event->last_action_time = now; + /* If the event is meant to happen after ten years, that's likely + * a bug, and somebody gave an absolute time rather than an interval. + */ + tor_assert(r < MAX_INTERVAL); + next_interval = r; + } else { + /* no action was taken, it is likely a precondition failed, + * we should reschedule for next second incase the precondition + * passes then */ + next_interval = 1; + } + + log_debug(LD_GENERAL, "Scheduling %s for %d seconds", event->name, + next_interval); + struct timeval tv = { next_interval , 0 }; + event_add(event->ev, &tv); +} + +/** Schedules <b>event</b> to run as soon as possible from now. */ +void +periodic_event_reschedule(periodic_event_item_t *event) +{ + periodic_event_set_interval(event, 1); +} + +/** Initializes the libevent backend for a periodic event. */ +void +periodic_event_setup(periodic_event_item_t *event) +{ + if (event->ev) { /* Already setup? This is a bug */ + log_err(LD_BUG, "Initial dispatch should only be done once."); + tor_assert(0); + } + + event->ev = tor_event_new(tor_libevent_get_base(), + -1, 0, + periodic_event_dispatch, + event); + tor_assert(event->ev); +} + +/** Handles initial dispatch for periodic events. It should happen 1 second + * after the events are created to mimic behaviour before #3199's refactor */ +void +periodic_event_launch(periodic_event_item_t *event) +{ + if (! event->ev) { /* Not setup? This is a bug */ + log_err(LD_BUG, "periodic_event_launch without periodic_event_setup"); + tor_assert(0); + } + + // Initial dispatch + periodic_event_dispatch(-1, EV_TIMEOUT, event); +} + +/** Release all storage associated with <b>event</b> */ +void +periodic_event_destroy(periodic_event_item_t *event) +{ + if (!event) + return; + tor_event_free(event->ev); + event->last_action_time = 0; +} + diff --git a/src/or/periodic.h b/src/or/periodic.h new file mode 100644 index 0000000000..bab0c91916 --- /dev/null +++ b/src/or/periodic.h @@ -0,0 +1,37 @@ +/* Copyright (c) 2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#ifndef TOR_PERIODIC_H +#define TOR_PERIODIC_H + +#define PERIODIC_EVENT_NO_UPDATE (-1) + +/** Callback function for a periodic event to take action. The return value +* influences the next time the function will get called. Return +* PERIODIC_EVENT_NO_UPDATE to not update <b>last_action_time</b> and be polled +* again in the next second. If a positive value is returned it will update the +* interval time. */ +typedef int (*periodic_event_helper_t)(time_t now, + const or_options_t *options); + +struct event; + +/** A single item for the periodic-events-function table. */ +typedef struct periodic_event_item_t { + periodic_event_helper_t fn; /**< The function to run the event */ + time_t last_action_time; /**< The last time the function did something */ + struct event *ev; /**< Libevent callback we're using to implement this */ + const char *name; /**< Name of the function -- for debug */ +} periodic_event_item_t; + +/** events will get their interval from first execution */ +#define PERIODIC_EVENT(fn) { fn##_callback, 0, NULL, #fn } +#define END_OF_PERIODIC_EVENTS { NULL, 0, NULL, NULL } + +void periodic_event_launch(periodic_event_item_t *event); +void periodic_event_setup(periodic_event_item_t *event); +void periodic_event_destroy(periodic_event_item_t *event); +void periodic_event_reschedule(periodic_event_item_t *event); + +#endif + diff --git a/src/or/policies.c b/src/or/policies.c index b247e6a64d..c9bce1b234 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -8,6 +8,8 @@ * \brief Code to parse and use address policies and exit policies. **/ +#define POLICIES_PRIVATE + #include "or.h" #include "config.h" #include "dirserv.h" @@ -62,14 +64,15 @@ static const char *private_nets[] = { NULL }; -static int policies_parse_exit_policy_internal(config_line_t *cfg, - smartlist_t **dest, - int ipv6_exit, - int rejectprivate, - uint32_t local_address, - tor_addr_t *ipv6_local_address, - int reject_interface_addresses, - int add_default_policy); +static int policies_parse_exit_policy_internal( + config_line_t *cfg, + smartlist_t **dest, + int ipv6_exit, + int rejectprivate, + const smartlist_t *configured_addresses, + int reject_interface_addresses, + int reject_configured_port_addresses, + int add_default_policy); /** Replace all "private" entries in *<b>policy</b> with their expanded * equivalents. */ @@ -443,7 +446,7 @@ validate_addr_policies(const or_options_t *options, char **msg) smartlist_t *addr_policy=NULL; *msg = NULL; - if (policies_parse_exit_policy_from_options(options,0,NULL,0,&addr_policy)) { + if (policies_parse_exit_policy_from_options(options,0,NULL,&addr_policy)) { REJECT("Error in ExitPolicy entry."); } @@ -625,7 +628,7 @@ typedef struct policy_map_ent_t { static HT_HEAD(policy_map, policy_map_ent_t) policy_root = HT_INITIALIZER(); /** Return true iff a and b are equal. */ -static INLINE int +static inline int policy_eq(policy_map_ent_t *a, policy_map_ent_t *b) { return cmp_single_addr_policy(a->policy, b->policy) == 0; @@ -693,6 +696,10 @@ compare_known_tor_addr_to_addr_policy(const tor_addr_t *addr, uint16_t port, /* We know the address and port, and we know the policy, so we can just * compute an exact match. */ SMARTLIST_FOREACH_BEGIN(policy, addr_policy_t *, tmpe) { + if (tmpe->addr.family == AF_UNSPEC) { + log_warn(LD_BUG, "Policy contains an AF_UNSPEC address, which only " + "matches other AF_UNSPEC addresses."); + } /* Address is known */ if (!tor_addr_compare_masked(addr, &tmpe->addr, tmpe->maskbits, CMP_EXACT)) { @@ -720,6 +727,10 @@ compare_known_tor_addr_to_addr_policy_noport(const tor_addr_t *addr, int maybe_accept = 0, maybe_reject = 0; SMARTLIST_FOREACH_BEGIN(policy, addr_policy_t *, tmpe) { + if (tmpe->addr.family == AF_UNSPEC) { + log_warn(LD_BUG, "Policy contains an AF_UNSPEC address, which only " + "matches other AF_UNSPEC addresses."); + } if (!tor_addr_compare_masked(addr, &tmpe->addr, tmpe->maskbits, CMP_EXACT)) { if (tmpe->prt_min <= 1 && tmpe->prt_max >= 65535) { @@ -759,6 +770,10 @@ compare_unknown_tor_addr_to_addr_policy(uint16_t port, int maybe_accept = 0, maybe_reject = 0; SMARTLIST_FOREACH_BEGIN(policy, addr_policy_t *, tmpe) { + if (tmpe->addr.family == AF_UNSPEC) { + log_warn(LD_BUG, "Policy contains an AF_UNSPEC address, which only " + "matches other AF_UNSPEC addresses."); + } if (tmpe->prt_min <= port && port <= tmpe->prt_max) { if (tmpe->maskbits == 0) { /* Definitely matches, since it covers all addresses. */ @@ -864,7 +879,7 @@ addr_policy_intersects(addr_policy_t *a, addr_policy_t *b) /** Add the exit policy described by <b>more</b> to <b>policy</b>. */ -static void +STATIC void append_exit_policy_string(smartlist_t **policy, const char *more) { config_line_t tmp; @@ -881,6 +896,9 @@ append_exit_policy_string(smartlist_t **policy, const char *more) void addr_policy_append_reject_addr(smartlist_t **dest, const tor_addr_t *addr) { + tor_assert(dest); + tor_assert(addr); + addr_policy_t p, *add; memset(&p, 0, sizeof(p)); p.policy_type = ADDR_POLICY_REJECT; @@ -893,6 +911,71 @@ addr_policy_append_reject_addr(smartlist_t **dest, const tor_addr_t *addr) if (!*dest) *dest = smartlist_new(); smartlist_add(*dest, add); + log_debug(LD_CONFIG, "Adding a reject ExitPolicy 'reject %s:*'", + fmt_addr(addr)); +} + +/* Is addr public for the purposes of rejection? */ +static int +tor_addr_is_public_for_reject(const tor_addr_t *addr) +{ + return (!tor_addr_is_null(addr) && !tor_addr_is_internal(addr, 0) + && !tor_addr_is_multicast(addr)); +} + +/* Add "reject <b>addr</b>:*" to <b>dest</b>, creating the list as needed. + * Filter the address, only adding an IPv4 reject rule if ipv4_rules + * is true, and similarly for ipv6_rules. Check each address returns true for + * tor_addr_is_public_for_reject before adding it. + */ +static void +addr_policy_append_reject_addr_filter(smartlist_t **dest, + const tor_addr_t *addr, + int ipv4_rules, + int ipv6_rules) +{ + tor_assert(dest); + tor_assert(addr); + + /* Only reject IP addresses which are public */ + if (tor_addr_is_public_for_reject(addr)) { + + /* Reject IPv4 addresses and IPv6 addresses based on the filters */ + int is_ipv4 = tor_addr_is_v4(addr); + if ((is_ipv4 && ipv4_rules) || (!is_ipv4 && ipv6_rules)) { + addr_policy_append_reject_addr(dest, addr); + } + } +} + +/** Add "reject addr:*" to <b>dest</b>, for each addr in addrs, creating the + * list as needed. */ +void +addr_policy_append_reject_addr_list(smartlist_t **dest, + const smartlist_t *addrs) +{ + tor_assert(dest); + tor_assert(addrs); + + SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, addr) { + addr_policy_append_reject_addr(dest, addr); + } SMARTLIST_FOREACH_END(addr); +} + +/** Add "reject addr:*" to <b>dest</b>, for each addr in addrs, creating the + * list as needed. Filter using */ +static void +addr_policy_append_reject_addr_list_filter(smartlist_t **dest, + const smartlist_t *addrs, + int ipv4_rules, + int ipv6_rules) +{ + tor_assert(dest); + tor_assert(addrs); + + SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, addr) { + addr_policy_append_reject_addr_filter(dest, addr, ipv4_rules, ipv6_rules); + } SMARTLIST_FOREACH_END(addr); } /** Detect and excise "dead code" from the policy *<b>dest</b>. */ @@ -979,127 +1062,90 @@ exit_policy_remove_redundancies(smartlist_t *dest) } } -#define DEFAULT_EXIT_POLICY \ - "reject *:25,reject *:119,reject *:135-139,reject *:445," \ - "reject *:563,reject *:1214,reject *:4661-4666," \ - "reject *:6346-6429,reject *:6699,reject *:6881-6999,accept *:*" - -/** Parse the exit policy <b>cfg</b> into the linked list *<b>dest</b>. - * - * If <b>ipv6_exit</b> is true, prepend "reject *6:*" to the policy. +/** Reject private helper for policies_parse_exit_policy_internal: rejects + * publicly routable addresses on this exit relay. * - * If <b>rejectprivate</b> is true: - * - prepend "reject private:*" to the policy. - * - if local_address is non-zero, treat it as a host-order IPv4 address, - * and prepend an entry that rejects it as a destination. - * - if ipv6_local_address is non-NULL, prepend an entry that rejects it as - * a destination. - * - if reject_interface_addresses is true, prepend entries that reject each + * Add reject entries to the linked list *dest: + * - if configured_addresses is non-NULL, add entries that reject each + * tor_addr_t* in the list as a destination. + * - if reject_interface_addresses is true, add entries that reject each * public IPv4 and IPv6 address of each interface on this machine. + * - if reject_configured_port_addresses is true, add entries that reject + * each IPv4 and IPv6 address configured for a port. * - * If cfg doesn't end in an absolute accept or reject and if - * <b>add_default_policy</b> is true, add the default exit - * policy afterwards. - * - * Return -1 if we can't parse cfg, else return 0. + * IPv6 entries are only added if ipv6_exit is true. (All IPv6 addresses are + * already blocked by policies_parse_exit_policy_internal if ipv6_exit is + * false.) * - * This function is used to parse the exit policy from our torrc. For - * the functions used to parse the exit policy from a router descriptor, - * see router_add_exit_policy. + * The list *dest is created as needed. */ -static int -policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest, - int ipv6_exit, - int rejectprivate, - uint32_t local_address, - tor_addr_t *ipv6_local_address, - int reject_interface_addresses, - int add_default_policy) +void +policies_parse_exit_policy_reject_private( + smartlist_t **dest, + int ipv6_exit, + const smartlist_t *configured_addresses, + int reject_interface_addresses, + int reject_configured_port_addresses) { - if (!ipv6_exit) { - append_exit_policy_string(dest, "reject *6:*"); + tor_assert(dest); + + /* Reject configured addresses, if they are from public netblocks. */ + if (configured_addresses) { + addr_policy_append_reject_addr_list_filter(dest, configured_addresses, + 1, ipv6_exit); } - if (rejectprivate) { - /* Reject IPv4 and IPv6 reserved private netblocks */ - append_exit_policy_string(dest, "reject private:*"); - /* Reject our local IPv4 address */ - if (local_address) { - char buf[POLICY_BUF_LEN]; - tor_snprintf(buf, sizeof(buf), "reject %s:*", fmt_addr32(local_address)); - append_exit_policy_string(dest, buf); - log_info(LD_CONFIG, "Adding a reject ExitPolicy '%s' for our published " - "IPv4 address", buf); - } - /* Reject our local IPv6 address */ - if (ipv6_exit && ipv6_local_address != NULL) { - if (tor_addr_is_v4(ipv6_local_address)) { - log_warn(LD_CONFIG, "IPv4 address '%s' provided as our IPv6 local " - "address", fmt_addr(ipv6_local_address)); - } else { - char buf6[POLICY_BUF_LEN]; - tor_snprintf(buf6, sizeof(buf6), "reject [%s]:*", - fmt_addr(ipv6_local_address)); - append_exit_policy_string(dest, buf6); - log_info(LD_CONFIG, "Adding a reject ExitPolicy '%s' for our " - "published IPv6 address", buf6); - } - } - /* Reject local addresses from public netblocks on any interface, - * but don't reject our published addresses twice */ - if (reject_interface_addresses) { - smartlist_t *public_addresses = NULL; - char bufif[POLICY_BUF_LEN]; - - /* Reject public IPv4 addresses on any interface, - * but don't reject our published IPv4 address twice */ - public_addresses = get_interface_address6_list(LOG_INFO, AF_INET, 0); - SMARTLIST_FOREACH_BEGIN(public_addresses, tor_addr_t *, a) { - if (!tor_addr_eq_ipv4h(a, local_address)) { - tor_snprintf(bufif, sizeof(bufif), "reject %s:*", - fmt_addr(a)); - append_exit_policy_string(dest, bufif); - log_info(LD_CONFIG, "Adding a reject ExitPolicy '%s' for a local " - "interface's public IPv4 address", bufif); - } - } SMARTLIST_FOREACH_END(a); - free_interface_address6_list(public_addresses); - if (ipv6_exit) { - /* Reject public IPv6 addresses on any interface, - * but don't reject our published IPv6 address (if any) twice */ - public_addresses = get_interface_address6_list(LOG_INFO, AF_INET6, 0); - SMARTLIST_FOREACH_BEGIN(public_addresses, tor_addr_t *, a) { - /* if we don't have an IPv6 local address, we won't have rejected - * it above. This could happen if a future release does IPv6 - * autodiscovery, and we are waiting to discover our external IPv6 - * address */ - if (ipv6_local_address == NULL - || !tor_addr_eq(ipv6_local_address, a)) { - tor_snprintf(bufif, sizeof(bufif), "reject6 [%s]:*", - fmt_addr(a)); - append_exit_policy_string(dest, bufif); - log_info(LD_CONFIG, "Adding a reject ExitPolicy '%s' for a local " - "interface's public IPv6 address", bufif); - } - } SMARTLIST_FOREACH_END(a); - free_interface_address6_list(public_addresses); + /* Reject configured port addresses, if they are from public netblocks. */ + if (reject_configured_port_addresses) { + const smartlist_t *port_addrs = get_configured_ports(); + + SMARTLIST_FOREACH_BEGIN(port_addrs, port_cfg_t *, port) { + + /* Only reject port IP addresses, not port unix sockets */ + if (!port->is_unix_addr) { + addr_policy_append_reject_addr_filter(dest, &port->addr, 1, ipv6_exit); } + } SMARTLIST_FOREACH_END(port); + } + + /* Reject local addresses from public netblocks on any interface. */ + if (reject_interface_addresses) { + smartlist_t *public_addresses = NULL; + + /* Reject public IPv4 addresses on any interface */ + public_addresses = get_interface_address6_list(LOG_INFO, AF_INET, 0); + addr_policy_append_reject_addr_list_filter(dest, public_addresses, 1, 0); + free_interface_address6_list(public_addresses); + + /* Don't look for IPv6 addresses if we're configured as IPv4-only */ + if (ipv6_exit) { + /* Reject public IPv6 addresses on any interface */ + public_addresses = get_interface_address6_list(LOG_INFO, AF_INET6, 0); + addr_policy_append_reject_addr_list_filter(dest, public_addresses, 0, 1); + free_interface_address6_list(public_addresses); } } - if (parse_addr_policy(cfg, dest, -1)) - return -1; - /* Before we add the default policy and final rejects, check to see if - * there are any lines after accept *:* or reject *:*. These lines have no - * effect, and are most likely an error. */ + /* If addresses were added multiple times, remove all but one of them. */ + if (*dest) { + exit_policy_remove_redundancies(*dest); + } +} + +/** + * Iterate through <b>policy</b> looking for redundant entries. Log a + * warning message with the first redundant entry, if any is found. + */ +static void +policies_log_first_redundant_entry(const smartlist_t *policy) +{ int found_final_effective_entry = 0; int first_redundant_entry = 0; - for (int i = 0; i < smartlist_len(*dest); ++i) { + tor_assert(policy); + SMARTLIST_FOREACH_BEGIN(policy, const addr_policy_t *, p) { sa_family_t family; - addr_policy_t *p; int found_ipv4_wildcard = 0, found_ipv6_wildcard = 0; - - p = smartlist_get(*dest, i); + const int i = p_sl_idx; /* Look for accept/reject *[4|6|]:* entires */ if (p->prt_min <= 1 && p->prt_max == 65535 && p->maskbits == 0) { @@ -1122,22 +1168,23 @@ policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest, if (found_ipv4_wildcard && found_ipv6_wildcard) { found_final_effective_entry = 1; /* if we're not on the final entry in the list */ - if (i < smartlist_len(*dest) - 1) { + if (i < smartlist_len(policy) - 1) { first_redundant_entry = i + 1; } break; } - } + } SMARTLIST_FOREACH_END(p); + /* Work out if there are redundant trailing entries in the policy list */ if (found_final_effective_entry && first_redundant_entry > 0) { - addr_policy_t *p; + const addr_policy_t *p; /* Longest possible policy is * "accept6 ffff:ffff:..255/128:10000-65535", * which contains a max-length IPv6 address, plus 24 characters. */ char line[TOR_ADDR_BUF_LEN + 32]; - tor_assert(first_redundant_entry < smartlist_len(*dest)); - p = smartlist_get(*dest, first_redundant_entry); + tor_assert(first_redundant_entry < smartlist_len(policy)); + p = smartlist_get(policy, first_redundant_entry); /* since we've already parsed the policy into an addr_policy_t struct, * we might not log exactly what the user typed in */ policy_write_item(line, TOR_ADDR_BUF_LEN + 32, p, 0); @@ -1147,6 +1194,62 @@ policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest, "accept/reject *:* as the last entry in any exit policy.)", line); } +} + +#define DEFAULT_EXIT_POLICY \ + "reject *:25,reject *:119,reject *:135-139,reject *:445," \ + "reject *:563,reject *:1214,reject *:4661-4666," \ + "reject *:6346-6429,reject *:6699,reject *:6881-6999,accept *:*" + +/** Parse the exit policy <b>cfg</b> into the linked list *<b>dest</b>. + * + * If <b>ipv6_exit</b> is false, prepend "reject *6:*" to the policy. + * + * If <b>rejectprivate</b> is true: + * - prepend "reject private:*" to the policy. + * - prepend entries that reject publicly routable addresses on this exit + * relay by calling policies_parse_exit_policy_reject_private + * + * If cfg doesn't end in an absolute accept or reject and if + * <b>add_default_policy</b> is true, add the default exit + * policy afterwards. + * + * Return -1 if we can't parse cfg, else return 0. + * + * This function is used to parse the exit policy from our torrc. For + * the functions used to parse the exit policy from a router descriptor, + * see router_add_exit_policy. + */ +static int +policies_parse_exit_policy_internal(config_line_t *cfg, + smartlist_t **dest, + int ipv6_exit, + int rejectprivate, + const smartlist_t *configured_addresses, + int reject_interface_addresses, + int reject_configured_port_addresses, + int add_default_policy) +{ + if (!ipv6_exit) { + append_exit_policy_string(dest, "reject *6:*"); + } + if (rejectprivate) { + /* Reject IPv4 and IPv6 reserved private netblocks */ + append_exit_policy_string(dest, "reject private:*"); + /* Reject IPv4 and IPv6 publicly routable addresses on this exit relay */ + policies_parse_exit_policy_reject_private( + dest, ipv6_exit, + configured_addresses, + reject_interface_addresses, + reject_configured_port_addresses); + } + if (parse_addr_policy(cfg, dest, -1)) + return -1; + + /* Before we add the default policy and final rejects, check to see if + * there are any lines after accept *:* or reject *:*. These lines have no + * effect, and are most likely an error. */ + policies_log_first_redundant_entry(*dest); if (add_default_policy) { append_exit_policy_string(dest, DEFAULT_EXIT_POLICY); @@ -1167,12 +1270,8 @@ policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest, * If <b>EXIT_POLICY_REJECT_PRIVATE</b> bit is set in <b>options</b>: * - prepend an entry that rejects all destinations in all netblocks * reserved for private use. - * - if local_address is non-zero, treat it as a host-order IPv4 address, - * and prepend an entry that rejects it as a destination. - * - if ipv6_local_address is non-NULL, prepend an entry that rejects it as - * a destination. - * - if reject_interface_addresses is true, prepend entries that reject each - * public IPv4 and IPv6 address of each interface on this machine. + * - prepend entries that reject publicly routable addresses on this exit + * relay by calling policies_parse_exit_policy_internal * * If <b>EXIT_POLICY_ADD_DEFAULT</b> bit is set in <b>options</b>, append * default exit policy entries to <b>result</b> smartlist. @@ -1180,9 +1279,7 @@ policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest, int policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, exit_policy_parser_cfg_t options, - uint32_t local_address, - tor_addr_t *ipv6_local_address, - int reject_interface_addresses) + const smartlist_t *configured_addresses) { int ipv6_enabled = (options & EXIT_POLICY_IPV6_ENABLED) ? 1 : 0; int reject_private = (options & EXIT_POLICY_REJECT_PRIVATE) ? 1 : 0; @@ -1190,12 +1287,62 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, return policies_parse_exit_policy_internal(cfg,dest,ipv6_enabled, reject_private, - local_address, - ipv6_local_address, - reject_interface_addresses, + configured_addresses, + reject_private, + reject_private, add_default); } +/** Helper function that adds a copy of addr to a smartlist as long as it is + * non-NULL and not tor_addr_is_null(). + * + * The caller is responsible for freeing all the tor_addr_t* in the smartlist. + */ +static void +policies_copy_addr_to_smartlist(smartlist_t *addr_list, const tor_addr_t *addr) +{ + if (addr && !tor_addr_is_null(addr)) { + tor_addr_t *addr_copy = tor_malloc(sizeof(tor_addr_t)); + tor_addr_copy(addr_copy, addr); + smartlist_add(addr_list, addr_copy); + } +} + +/** Helper function that adds ipv4h_addr to a smartlist as a tor_addr_t *, + * as long as it is not tor_addr_is_null(), by converting it to a tor_addr_t + * and passing it to policies_add_addr_to_smartlist. + * + * The caller is responsible for freeing all the tor_addr_t* in the smartlist. + */ +static void +policies_copy_ipv4h_to_smartlist(smartlist_t *addr_list, uint32_t ipv4h_addr) +{ + if (ipv4h_addr) { + tor_addr_t ipv4_tor_addr; + tor_addr_from_ipv4h(&ipv4_tor_addr, ipv4h_addr); + policies_copy_addr_to_smartlist(addr_list, &ipv4_tor_addr); + } +} + +/** Helper function that adds copies of + * or_options->OutboundBindAddressIPv[4|6]_ to a smartlist as tor_addr_t *, as + * long as or_options is non-NULL, and the addresses are not + * tor_addr_is_null(), by passing them to policies_add_addr_to_smartlist. + * + * The caller is responsible for freeing all the tor_addr_t* in the smartlist. + */ +static void +policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list, + const or_options_t *or_options) +{ + if (or_options) { + policies_copy_addr_to_smartlist(addr_list, + &or_options->OutboundBindAddressIPv4_); + policies_copy_addr_to_smartlist(addr_list, + &or_options->OutboundBindAddressIPv6_); + } +} + /** Parse <b>ExitPolicy</b> member of <b>or_options</b> into <b>result</b> * smartlist. * If <b>or_options->IPv6Exit</b> is false, prepend an entry that @@ -1205,11 +1352,13 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, * - prepend an entry that rejects all destinations in all netblocks reserved * for private use. * - if local_address is non-zero, treat it as a host-order IPv4 address, and - * prepend an entry that rejects it as a destination. - * - if ipv6_local_address is non-NULL, prepend an entry that rejects it as a - * destination. - * - if reject_interface_addresses is true, prepend entries that reject each - * public IPv4 and IPv6 address of each interface on this machine. + * add it to the list of configured addresses. + * - if ipv6_local_address is non-NULL, and not the null tor_addr_t, add it + * to the list of configured addresses. + * - if or_options->OutboundBindAddressIPv4_ is not the null tor_addr_t, add + * it to the list of configured addresses. + * - if or_options->OutboundBindAddressIPv6_ is not the null tor_addr_t, add + * it to the list of configured addresses. * * If <b>or_options->BridgeRelay</b> is false, append entries of default * Tor exit policy into <b>result</b> smartlist. @@ -1220,18 +1369,23 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, int policies_parse_exit_policy_from_options(const or_options_t *or_options, uint32_t local_address, - tor_addr_t *ipv6_local_address, - int reject_interface_addresses, + const tor_addr_t *ipv6_local_address, smartlist_t **result) { exit_policy_parser_cfg_t parser_cfg = 0; + smartlist_t *configured_addresses = NULL; + int rv = 0; + /* Short-circuit for non-exit relays */ if (or_options->ExitRelay == 0) { append_exit_policy_string(result, "reject *4:*"); append_exit_policy_string(result, "reject *6:*"); return 0; } + configured_addresses = smartlist_new(); + + /* Configure the parser */ if (or_options->IPv6Exit) { parser_cfg |= EXIT_POLICY_IPV6_ENABLED; } @@ -1244,10 +1398,19 @@ policies_parse_exit_policy_from_options(const or_options_t *or_options, parser_cfg |= EXIT_POLICY_ADD_DEFAULT; } - return policies_parse_exit_policy(or_options->ExitPolicy,result, - parser_cfg,local_address, - ipv6_local_address, - reject_interface_addresses); + /* Copy the configured addresses into the tor_addr_t* list */ + policies_copy_ipv4h_to_smartlist(configured_addresses, local_address); + policies_copy_addr_to_smartlist(configured_addresses, ipv6_local_address); + policies_copy_outbound_addresses_to_smartlist(configured_addresses, + or_options); + + rv = policies_parse_exit_policy(or_options->ExitPolicy, result, parser_cfg, + configured_addresses); + + SMARTLIST_FOREACH(configured_addresses, tor_addr_t *, a, tor_free(a)); + smartlist_free(configured_addresses); + + return rv; } /** Add "reject *:*" to the end of the policy in *<b>dest</b>, allocating @@ -1355,7 +1518,7 @@ policy_is_reject_star(const smartlist_t *policy, sa_family_t family) /** Write a single address policy to the buf_len byte buffer at buf. Return * the number of characters written, or -1 on failure. */ int -policy_write_item(char *buf, size_t buflen, addr_policy_t *policy, +policy_write_item(char *buf, size_t buflen, const addr_policy_t *policy, int format_for_desc) { size_t written = 0; @@ -1934,6 +2097,53 @@ compare_tor_addr_to_node_policy(const tor_addr_t *addr, uint16_t port, } } +/** + * Given <b>policy_list</b>, a list of addr_policy_t, produce a string + * representation of the list. + * If <b>include_ipv4</b> is true, include IPv4 entries. + * If <b>include_ipv6</b> is true, include IPv6 entries. + */ +char * +policy_dump_to_string(const smartlist_t *policy_list, + int include_ipv4, + int include_ipv6) +{ + smartlist_t *policy_string_list; + char *policy_string = NULL; + + policy_string_list = smartlist_new(); + + SMARTLIST_FOREACH_BEGIN(policy_list, addr_policy_t *, tmpe) { + char *pbuf; + int bytes_written_to_pbuf; + if ((tor_addr_family(&tmpe->addr) == AF_INET6) && (!include_ipv6)) { + continue; /* Don't include IPv6 parts of address policy */ + } + if ((tor_addr_family(&tmpe->addr) == AF_INET) && (!include_ipv4)) { + continue; /* Don't include IPv4 parts of address policy */ + } + + pbuf = tor_malloc(POLICY_BUF_LEN); + bytes_written_to_pbuf = policy_write_item(pbuf,POLICY_BUF_LEN, tmpe, 1); + + if (bytes_written_to_pbuf < 0) { + log_warn(LD_BUG, "policy_dump_to_string ran out of room!"); + tor_free(pbuf); + goto done; + } + + smartlist_add(policy_string_list,pbuf); + } SMARTLIST_FOREACH_END(tmpe); + + policy_string = smartlist_join_strings(policy_string_list, "\n", 0, NULL); + + done: + SMARTLIST_FOREACH(policy_string_list, char *, str, tor_free(str)); + smartlist_free(policy_string_list); + + return policy_string; +} + /** Implementation for GETINFO control command: knows the answer for questions * about "exit-policy/..." */ int @@ -1945,6 +2155,57 @@ getinfo_helper_policies(control_connection_t *conn, (void) errmsg; if (!strcmp(question, "exit-policy/default")) { *answer = tor_strdup(DEFAULT_EXIT_POLICY); + } else if (!strcmp(question, "exit-policy/reject-private/default")) { + smartlist_t *private_policy_strings; + const char **priv = private_nets; + + private_policy_strings = smartlist_new(); + + while (*priv != NULL) { + /* IPv6 addresses are in "[]" and contain ":", + * IPv4 addresses are not in "[]" and contain "." */ + smartlist_add_asprintf(private_policy_strings, "reject %s:*", *priv); + priv++; + } + + *answer = smartlist_join_strings(private_policy_strings, + ",", 0, NULL); + + SMARTLIST_FOREACH(private_policy_strings, char *, str, tor_free(str)); + smartlist_free(private_policy_strings); + } else if (!strcmp(question, "exit-policy/reject-private/relay")) { + const or_options_t *options = get_options(); + const routerinfo_t *me = router_get_my_routerinfo(); + + if (!me) { + *errmsg = "router_get_my_routerinfo returned NULL"; + return -1; + } + + if (!options->ExitPolicyRejectPrivate) { + *answer = tor_strdup(""); + return 0; + } + + smartlist_t *private_policy_list = smartlist_new(); + smartlist_t *configured_addresses = smartlist_new(); + + /* Copy the configured addresses into the tor_addr_t* list */ + policies_copy_ipv4h_to_smartlist(configured_addresses, me->addr); + policies_copy_addr_to_smartlist(configured_addresses, &me->ipv6_addr); + policies_copy_outbound_addresses_to_smartlist(configured_addresses, + options); + + policies_parse_exit_policy_reject_private( + &private_policy_list, + options->IPv6Exit, + configured_addresses, + 1, 1); + *answer = policy_dump_to_string(private_policy_list, 1, 1); + + addr_policy_list_free(private_policy_list); + SMARTLIST_FOREACH(configured_addresses, tor_addr_t *, a, tor_free(a)); + smartlist_free(configured_addresses); } else if (!strcmpstart(question, "exit-policy/")) { const routerinfo_t *me = router_get_my_routerinfo(); diff --git a/src/or/policies.h b/src/or/policies.h index f200d7babe..007f494482 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -44,30 +44,38 @@ addr_policy_t *addr_policy_get_canonical_entry(addr_policy_t *ent); int cmp_addr_policies(smartlist_t *a, smartlist_t *b); MOCK_DECL(addr_policy_result_t, compare_tor_addr_to_addr_policy, (const tor_addr_t *addr, uint16_t port, const smartlist_t *policy)); - addr_policy_result_t compare_tor_addr_to_node_policy(const tor_addr_t *addr, uint16_t port, const node_t *node); -int policies_parse_exit_policy_from_options(const or_options_t *or_options, - uint32_t local_address, - tor_addr_t *ipv6_local_address, - int reject_interface_addresses, - smartlist_t **result); +int policies_parse_exit_policy_from_options( + const or_options_t *or_options, + uint32_t local_address, + const tor_addr_t *ipv6_local_address, + smartlist_t **result); int policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest, exit_policy_parser_cfg_t options, - uint32_t local_address, - tor_addr_t *ipv6_local_address, - int reject_interface_addresses); + const smartlist_t *configured_addresses); +void policies_parse_exit_policy_reject_private( + smartlist_t **dest, + int ipv6_exit, + const smartlist_t *configured_addresses, + int reject_interface_addresses, + int reject_configured_port_addresses); void policies_exit_policy_append_reject_star(smartlist_t **dest); void addr_policy_append_reject_addr(smartlist_t **dest, const tor_addr_t *addr); +void addr_policy_append_reject_addr_list(smartlist_t **dest, + const smartlist_t *addrs); void policies_set_node_exitpolicy_to_reject_all(node_t *exitrouter); int exit_policy_is_general_exit(smartlist_t *policy); int policy_is_reject_star(const smartlist_t *policy, sa_family_t family); +char * policy_dump_to_string(const smartlist_t *policy_list, + int include_ipv4, + int include_ipv6); int getinfo_helper_policies(control_connection_t *conn, const char *question, char **answer, const char **errmsg); -int policy_write_item(char *buf, size_t buflen, addr_policy_t *item, +int policy_write_item(char *buf, size_t buflen, const addr_policy_t *item, int format_for_desc); void addr_policy_list_free(smartlist_t *p); @@ -84,5 +92,9 @@ addr_policy_result_t compare_tor_addr_to_short_policy( const tor_addr_t *addr, uint16_t port, const short_policy_t *policy); +#ifdef POLICIES_PRIVATE +STATIC void append_exit_policy_string(smartlist_t **policy, const char *more); +#endif + #endif diff --git a/src/or/relay.c b/src/or/relay.c index eddad6a0cb..ee2f041dbd 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -1304,6 +1304,7 @@ connection_edge_process_relay_cell_not_open( "Got 'connected' while not in state connect_wait. Dropping."); return 0; } + CONNECTION_AP_EXPECT_NONPENDING(entry_conn); conn->base_.state = AP_CONN_STATE_OPEN; log_info(LD_APP,"'connected' received for circid %u streamid %d " "after %d seconds.", @@ -2255,7 +2256,7 @@ circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint) static size_t total_cells_allocated = 0; /** Release storage held by <b>cell</b>. */ -static INLINE void +static inline void packed_cell_free_unchecked(packed_cell_t *cell) { --total_cells_allocated; @@ -2299,7 +2300,7 @@ dump_cell_pool_usage(int severity) } /** Allocate a new copy of packed <b>cell</b>. */ -static INLINE packed_cell_t * +static inline packed_cell_t * packed_cell_copy(const cell_t *cell, int wide_circ_ids) { packed_cell_t *c = packed_cell_new(); diff --git a/src/or/rendcache.c b/src/or/rendcache.c index d4bdd68698..c69671e289 100644 --- a/src/or/rendcache.c +++ b/src/or/rendcache.c @@ -3,9 +3,10 @@ /** * \file rendcache.c - * \brief Hidden service desriptor cache. + * \brief Hidden service descriptor cache. **/ +#define RENDCACHE_PRIVATE #include "rendcache.h" #include "config.h" @@ -15,11 +16,14 @@ /** Map from service id (as generated by rend_get_service_id) to * rend_cache_entry_t. */ -static strmap_t *rend_cache = NULL; +STATIC strmap_t *rend_cache = NULL; + +/** Map from service id to rend_cache_entry_t; only for hidden services. */ +static strmap_t *rend_cache_local_service = NULL; /** Map from descriptor id to rend_cache_entry_t; only for hidden service * directories. */ -static digestmap_t *rend_cache_v2_dir = NULL; +STATIC digestmap_t *rend_cache_v2_dir = NULL; /** (Client side only) Map from service id to rend_cache_failure_t. This * cache is used to track intro point(IP) failures so we know when to keep @@ -46,10 +50,10 @@ static digestmap_t *rend_cache_v2_dir = NULL; * This scheme allows us to not realy on the descriptor's timestamp (which * is rounded down to the hour) to know if we have a newer descriptor. We * only rely on the usability of intro points from an internal state. */ -static strmap_t *rend_cache_failure = NULL; +STATIC strmap_t *rend_cache_failure = NULL; /** DOCDOC */ -static size_t rend_cache_total_allocation = 0; +STATIC size_t rend_cache_total_allocation = 0; /** Initializes the service descriptor cache. */ @@ -58,11 +62,12 @@ rend_cache_init(void) { rend_cache = strmap_new(); rend_cache_v2_dir = digestmap_new(); + rend_cache_local_service = strmap_new(); rend_cache_failure = strmap_new(); } /** Return the approximate number of bytes needed to hold <b>e</b>. */ -static size_t +STATIC size_t rend_cache_entry_allocation(const rend_cache_entry_t *e) { if (!e) @@ -80,7 +85,7 @@ rend_cache_get_total_allocation(void) } /** Decrement the total bytes attributed to the rendezvous cache by n. */ -static void +STATIC void rend_cache_decrement_allocation(size_t n) { static int have_underflowed = 0; @@ -97,7 +102,7 @@ rend_cache_decrement_allocation(size_t n) } /** Increase the total bytes attributed to the rendezvous cache by n. */ -static void +STATIC void rend_cache_increment_allocation(size_t n) { static int have_overflowed = 0; @@ -113,7 +118,7 @@ rend_cache_increment_allocation(size_t n) } /** Helper: free a rend cache failure intro object. */ -static void +STATIC void rend_cache_failure_intro_entry_free(rend_cache_failure_intro_t *entry) { if (entry == NULL) { @@ -130,7 +135,7 @@ rend_cache_failure_intro_entry_free_(void *entry) /** Allocate a rend cache failure intro object and return it. <b>failure</b> * is set into the object. This function can not fail. */ -static rend_cache_failure_intro_t * +STATIC rend_cache_failure_intro_t * rend_cache_failure_intro_entry_new(rend_intro_point_failure_t failure) { rend_cache_failure_intro_t *entry = tor_malloc(sizeof(*entry)); @@ -140,7 +145,7 @@ rend_cache_failure_intro_entry_new(rend_intro_point_failure_t failure) } /** Helper: free a rend cache failure object. */ -static void +STATIC void rend_cache_failure_entry_free(rend_cache_failure_t *entry) { if (entry == NULL) { @@ -156,7 +161,7 @@ rend_cache_failure_entry_free(rend_cache_failure_t *entry) /** Helper: deallocate a rend_cache_failure_t. (Used with strmap_free(), * which requires a function pointer whose argument is void*). */ -static void +STATIC void rend_cache_failure_entry_free_(void *entry) { rend_cache_failure_entry_free(entry); @@ -164,7 +169,7 @@ rend_cache_failure_entry_free_(void *entry) /** Allocate a rend cache failure object and return it. This function can * not fail. */ -static rend_cache_failure_t * +STATIC rend_cache_failure_t * rend_cache_failure_entry_new(void) { rend_cache_failure_t *entry = tor_malloc(sizeof(*entry)); @@ -174,7 +179,7 @@ rend_cache_failure_entry_new(void) /** Remove failure cache entry for the service ID in the given descriptor * <b>desc</b>. */ -static void +STATIC void rend_cache_failure_remove(rend_service_descriptor_t *desc) { char service_id[REND_SERVICE_ID_LEN_BASE32 + 1]; @@ -194,7 +199,7 @@ rend_cache_failure_remove(rend_service_descriptor_t *desc) } /** Helper: free storage held by a single service descriptor cache entry. */ -static void +STATIC void rend_cache_entry_free(rend_cache_entry_t *e) { if (!e) @@ -222,9 +227,11 @@ rend_cache_free_all(void) { strmap_free(rend_cache, rend_cache_entry_free_); digestmap_free(rend_cache_v2_dir, rend_cache_entry_free_); + strmap_free(rend_cache_local_service, rend_cache_entry_free_); strmap_free(rend_cache_failure, rend_cache_failure_entry_free_); rend_cache = NULL; rend_cache_v2_dir = NULL; + rend_cache_local_service = NULL; rend_cache_failure = NULL; rend_cache_total_allocation = 0; } @@ -258,24 +265,33 @@ rend_cache_failure_clean(time_t now) } STRMAP_FOREACH_END; } -/** Removes all old entries from the service descriptor cache. +/** Removes all old entries from the client or service descriptor cache. */ void -rend_cache_clean(time_t now) +rend_cache_clean(time_t now, rend_cache_type_t cache_type) { strmap_iter_t *iter; const char *key; void *val; rend_cache_entry_t *ent; time_t cutoff = now - REND_CACHE_MAX_AGE - REND_CACHE_MAX_SKEW; - for (iter = strmap_iter_init(rend_cache); !strmap_iter_done(iter); ) { + strmap_t *cache = NULL; + + if (cache_type == REND_CACHE_TYPE_CLIENT) { + cache = rend_cache; + } else if (cache_type == REND_CACHE_TYPE_SERVICE) { + cache = rend_cache_local_service; + } + tor_assert(cache); + + for (iter = strmap_iter_init(cache); !strmap_iter_done(iter); ) { strmap_iter_get(iter, &key, &val); ent = (rend_cache_entry_t*)val; if (ent->parsed->timestamp < cutoff) { - iter = strmap_iter_next_rmv(rend_cache, iter); + iter = strmap_iter_next_rmv(cache, iter); rend_cache_entry_free(ent); } else { - iter = strmap_iter_next(rend_cache, iter); + iter = strmap_iter_next(cache, iter); } } } @@ -305,10 +321,10 @@ rend_cache_failure_purge(void) } /** Lookup the rend failure cache using a relay identity digest in - * <b>identity</b> and service ID <b>service_id</b>. If found, the intro - * failure is set in <b>intro_entry</b> else it stays untouched. Return 1 - * iff found else 0. */ -static int + * <b>identity</b> which has DIGEST_LEN bytes and service ID <b>service_id</b> + * which is a null-terminated string. If found, the intro failure is set in + * <b>intro_entry</b> else it stays untouched. Return 1 iff found else 0. */ +STATIC int cache_failure_intro_lookup(const uint8_t *identity, const char *service_id, rend_cache_failure_intro_t **intro_entry) { @@ -352,7 +368,7 @@ cache_failure_intro_dup(const rend_cache_failure_intro_t *entry) /** Add an intro point failure to the failure cache using the relay * <b>identity</b> and service ID <b>service_id</b>. Record the * <b>failure</b> in that object. */ -static void +STATIC void cache_failure_intro_add(const uint8_t *identity, const char *service_id, rend_intro_point_failure_t failure) { @@ -379,7 +395,7 @@ cache_failure_intro_add(const uint8_t *identity, const char *service_id, * descriptor and kept into the failure cache. Then, each intro points that * are NOT in the descriptor but in the failure cache for the given * <b>service_id</b> are removed from the failure cache. */ -static void +STATIC void validate_intro_point_failure(const rend_service_descriptor_t *desc, const char *service_id) { @@ -535,6 +551,42 @@ rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **e) return ret; } +/* + * Lookup the v2 service descriptor with the service ID <b>query</b> in the + * local service descriptor cache. Return 0 if found and if <b>e</b> is + * non NULL, set it with the entry found. Else, a negative value is returned + * and <b>e</b> is untouched. + * -EINVAL means that <b>query</b> is not a valid service id. + * -ENOENT means that no entry in the cache was found. */ +int +rend_cache_lookup_v2_desc_as_service(const char *query, rend_cache_entry_t **e) +{ + int ret = 0; + rend_cache_entry_t *entry = NULL; + + tor_assert(rend_cache_local_service); + tor_assert(query); + + if (!rend_valid_service_id(query)) { + ret = -EINVAL; + goto end; + } + + /* Lookup descriptor and return. */ + entry = strmap_get_lc(rend_cache_local_service, query); + if (!entry) { + ret = -ENOENT; + goto end; + } + + if (e) { + *e = entry; + } + + end: + return ret; +} + /** Lookup the v2 service descriptor with base32-encoded <b>desc_id</b> and * copy the pointer to it to *<b>desc</b>. Return 1 on success, 0 on * well-formed-but-not-found, and -1 on failure. @@ -660,7 +712,6 @@ rend_cache_store_v2_desc_as_dir(const char *desc) log_info(LD_REND, "Successfully stored service descriptor with desc ID " "'%s' and len %d.", safe_str(desc_id_base32), (int)encoded_size); - /* Statistics: Note down this potentially new HS. */ if (options->HiddenServiceStatistics) { rep_hist_stored_maybe_new_hs(e->parsed->pk); @@ -687,6 +738,80 @@ rend_cache_store_v2_desc_as_dir(const char *desc) return RCS_OKAY; } +/** Parse the v2 service descriptor in <b>desc</b> and store it to the +* local service rend cache. Don't attempt to decrypt the included list of +* introduction points. +* +* If we have a newer descriptor with the same ID, ignore this one. +* If we have an older descriptor with the same ID, replace it. +* +* Return an appropriate rend_cache_store_status_t. +*/ +rend_cache_store_status_t +rend_cache_store_v2_desc_as_service(const char *desc) +{ + rend_service_descriptor_t *parsed = NULL; + char desc_id[DIGEST_LEN]; + char *intro_content = NULL; + size_t intro_size; + size_t encoded_size; + const char *next_desc; + char service_id[REND_SERVICE_ID_LEN_BASE32+1]; + rend_cache_entry_t *e; + rend_cache_store_status_t retval = RCS_BADDESC; + tor_assert(rend_cache_local_service); + tor_assert(desc); + + /* Parse the descriptor. */ + if (rend_parse_v2_service_descriptor(&parsed, desc_id, &intro_content, + &intro_size, &encoded_size, + &next_desc, desc, 0) < 0) { + log_warn(LD_REND, "Could not parse descriptor."); + goto err; + } + /* Compute service ID from public key. */ + if (rend_get_service_id(parsed->pk, service_id)<0) { + log_warn(LD_REND, "Couldn't compute service ID."); + goto err; + } + + /* Do we already have a newer descriptor? Allow new descriptors with a + rounded timestamp equal to or newer than the current descriptor */ + e = (rend_cache_entry_t*) strmap_get_lc(rend_cache_local_service, + service_id); + if (e && e->parsed->timestamp > parsed->timestamp) { + log_info(LD_REND, "We already have a newer service descriptor for " + "service ID %s.", safe_str_client(service_id)); + goto okay; + } + /* We don't care about the introduction points. */ + tor_free(intro_content); + if (!e) { + e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + strmap_set_lc(rend_cache_local_service, service_id, e); + } else { + rend_cache_decrement_allocation(rend_cache_entry_allocation(e)); + rend_service_descriptor_free(e->parsed); + tor_free(e->desc); + } + e->parsed = parsed; + e->desc = tor_malloc_zero(encoded_size + 1); + strlcpy(e->desc, desc, encoded_size + 1); + e->len = encoded_size; + rend_cache_increment_allocation(rend_cache_entry_allocation(e)); + log_debug(LD_REND,"Successfully stored rend desc '%s', len %d.", + safe_str_client(service_id), (int)encoded_size); + return RCS_OKAY; + + okay: + retval = RCS_OKAY; + + err: + rend_service_descriptor_free(parsed); + tor_free(intro_content); + return retval; +} + /** Parse the v2 service descriptor in <b>desc</b>, decrypt the included list * of introduction points with <b>descriptor_cookie</b> (which may also be * <b>NULL</b> if decryption is not necessary), and store the descriptor to diff --git a/src/or/rendcache.h b/src/or/rendcache.h index 0512058054..decb040ee7 100644 --- a/src/or/rendcache.h +++ b/src/or/rendcache.h @@ -48,14 +48,21 @@ typedef struct rend_cache_failure_t { digestmap_t *intro_failures; } rend_cache_failure_t; +typedef enum { + REND_CACHE_TYPE_CLIENT = 1, + REND_CACHE_TYPE_SERVICE = 2, +} rend_cache_type_t; + void rend_cache_init(void); -void rend_cache_clean(time_t now); +void rend_cache_clean(time_t now, rend_cache_type_t cache_type); void rend_cache_failure_clean(time_t now); void rend_cache_clean_v2_descs_as_dir(time_t now, size_t min_to_remove); void rend_cache_purge(void); void rend_cache_free_all(void); int rend_cache_lookup_entry(const char *query, int version, rend_cache_entry_t **entry_out); +int rend_cache_lookup_v2_desc_as_service(const char *query, + rend_cache_entry_t **entry_out); int rend_cache_lookup_v2_desc_as_dir(const char *query, const char **desc); /** Return value from rend_cache_store_v2_desc_as_{dir,client}. */ typedef enum { @@ -65,6 +72,8 @@ typedef enum { } rend_cache_store_status_t; rend_cache_store_status_t rend_cache_store_v2_desc_as_dir(const char *desc); +rend_cache_store_status_t rend_cache_store_v2_desc_as_service( + const char *desc); rend_cache_store_status_t rend_cache_store_v2_desc_as_client(const char *desc, const char *desc_id_base32, const rend_data_t *rend_query, @@ -76,5 +85,31 @@ void rend_cache_intro_failure_note(rend_intro_point_failure_t failure, const char *service_id); void rend_cache_failure_purge(void); +#ifdef RENDCACHE_PRIVATE + +STATIC size_t rend_cache_entry_allocation(const rend_cache_entry_t *e); +STATIC void rend_cache_entry_free(rend_cache_entry_t *e); +STATIC void rend_cache_failure_intro_entry_free(rend_cache_failure_intro_t + *entry); +STATIC void rend_cache_failure_entry_free(rend_cache_failure_t *entry); +STATIC int cache_failure_intro_lookup(const uint8_t *identity, + const char *service_id, + rend_cache_failure_intro_t + **intro_entry); +STATIC void rend_cache_decrement_allocation(size_t n); +STATIC void rend_cache_increment_allocation(size_t n); +STATIC rend_cache_failure_intro_t *rend_cache_failure_intro_entry_new( + rend_intro_point_failure_t failure); +STATIC rend_cache_failure_t *rend_cache_failure_entry_new(void); +STATIC void rend_cache_failure_remove(rend_service_descriptor_t *desc); +STATIC void cache_failure_intro_add(const uint8_t *identity, + const char *service_id, + rend_intro_point_failure_t failure); +STATIC void validate_intro_point_failure(const rend_service_descriptor_t *desc, + const char *service_id); + +STATIC void rend_cache_failure_entry_free_(void *entry); +#endif + #endif /* TOR_RENDCACHE_H */ diff --git a/src/or/rendclient.c b/src/or/rendclient.c index a39e518e99..d9cea53c04 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -52,7 +52,7 @@ rend_client_introcirc_has_opened(origin_circuit_t *circ) tor_assert(circ->cpath); log_info(LD_REND,"introcirc is open"); - connection_ap_attach_pending(); + connection_ap_attach_pending(1); } /** Send the establish-rendezvous cell along a rendezvous circuit. if @@ -65,11 +65,7 @@ rend_client_send_establish_rendezvous(origin_circuit_t *circ) tor_assert(circ->rend_data); log_info(LD_REND, "Sending an ESTABLISH_RENDEZVOUS cell"); - if (crypto_rand(circ->rend_data->rend_cookie, REND_COOKIE_LEN) < 0) { - log_warn(LD_BUG, "Internal error: Couldn't produce random cookie."); - circuit_mark_for_close(TO_CIRCUIT(circ), END_CIRC_REASON_INTERNAL); - return -1; - } + crypto_rand(circ->rend_data->rend_cookie, REND_COOKIE_LEN); /* Set timestamp_dirty, because circuit_expire_building expects it, * and the rend cookie also means we've used the circ. */ @@ -177,6 +173,7 @@ rend_client_send_introduction(origin_circuit_t *introcirc, while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT, introcirc->rend_data->onion_address))) { + connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn)); conn->state = AP_CONN_STATE_RENDDESC_WAIT; } } @@ -1059,9 +1056,11 @@ rend_client_report_intro_point_failure(extend_info_t *failed_intro, rend_client_refetch_v2_renddesc(rend_query); /* move all pending streams back to renddesc_wait */ + /* NOTE: We can now do this faster, if we use pending_entry_connections */ while ((conn = connection_get_by_type_state_rendquery(CONN_TYPE_AP, AP_CONN_STATE_CIRCUIT_WAIT, rend_query->onion_address))) { + connection_ap_mark_as_non_pending_circuit(TO_ENTRY_CONN(conn)); conn->state = AP_CONN_STATE_RENDDESC_WAIT; } @@ -1107,7 +1106,7 @@ rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request, * than trying to attach them all. See comments bug 743. */ /* If we already have the introduction circuit built, make sure we send * the INTRODUCE cell _now_ */ - connection_ap_attach_pending(); + connection_ap_attach_pending(1); return 0; } @@ -1226,12 +1225,7 @@ rend_client_desc_trynow(const char *query) base_conn->timestamp_lastread = now; base_conn->timestamp_lastwritten = now; - if (connection_ap_handshake_attach_circuit(conn) < 0) { - /* it will never work */ - log_warn(LD_REND,"Rendezvous attempt failed. Closing."); - if (!base_conn->marked_for_close) - connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); - } + connection_ap_mark_as_pending_circuit(conn); } else { /* 404, or fetch didn't get that far */ log_notice(LD_REND,"Closing stream for '%s.onion': hidden service is " "unavailable (try again later).", diff --git a/src/or/rendcommon.c b/src/or/rendcommon.c index 22599e9830..8c02b67556 100644 --- a/src/or/rendcommon.c +++ b/src/or/rendcommon.c @@ -11,6 +11,7 @@ #include "or.h" #include "circuitbuild.h" #include "config.h" +#include "control.h" #include "rendclient.h" #include "rendcommon.h" #include "rendmid.h" @@ -268,11 +269,7 @@ rend_encrypt_v2_intro_points_basic(char **encrypted_out, tor_assert(client_cookies && smartlist_len(client_cookies) > 0); /* Generate session key. */ - if (crypto_rand(session_key, CIPHER_KEY_LEN) < 0) { - log_warn(LD_REND, "Unable to generate random session key to encrypt " - "introduction point string."); - goto done; - } + crypto_rand(session_key, CIPHER_KEY_LEN); /* Determine length of encrypted introduction points including session * keys. */ @@ -334,11 +331,7 @@ rend_encrypt_v2_intro_points_basic(char **encrypted_out, REND_BASIC_AUTH_CLIENT_MULTIPLE; i < REND_BASIC_AUTH_CLIENT_MULTIPLE - 1; i++) { client_part = tor_malloc_zero(REND_BASIC_AUTH_CLIENT_ENTRY_LEN); - if (crypto_rand(client_part, REND_BASIC_AUTH_CLIENT_ENTRY_LEN) < 0) { - log_warn(LD_REND, "Unable to generate fake client entry."); - tor_free(client_part); - goto done; - } + crypto_rand(client_part, REND_BASIC_AUTH_CLIENT_ENTRY_LEN); smartlist_add(encrypted_session_keys, client_part); } /* Sort smartlist and put elements in result in order. */ @@ -461,6 +454,7 @@ rend_encode_v2_descriptors(smartlist_t *descs_out, smartlist_t *client_cookies) { char service_id[DIGEST_LEN]; + char service_id_base32[REND_SERVICE_ID_LEN_BASE32+1]; uint32_t time_period; char *ipos_base64 = NULL, *ipos = NULL, *ipos_encrypted = NULL, *descriptor_cookie = NULL; @@ -655,6 +649,11 @@ rend_encode_v2_descriptors(smartlist_t *descs_out, goto err; } smartlist_add(descs_out, enc); + /* Add the uploaded descriptor to the local service's descriptor cache */ + rend_cache_store_v2_desc_as_service(enc->desc_str); + base32_encode(service_id_base32, sizeof(service_id_base32), + service_id, REND_SERVICE_ID_LEN); + control_event_hs_descriptor_created(service_id_base32, desc_id_base32, k); } log_info(LD_REND, "Successfully encoded a v2 descriptor and " diff --git a/src/or/rendcommon.h b/src/or/rendcommon.h index 3b2f86d614..04e34af453 100644 --- a/src/or/rendcommon.h +++ b/src/or/rendcommon.h @@ -19,7 +19,7 @@ typedef enum rend_intro_point_failure_t { } rend_intro_point_failure_t; /** Free all storage associated with <b>data</b> */ -static INLINE void +static inline void rend_data_free(rend_data_t *data) { if (!data) { diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 77d8b716a2..15d98bfde5 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -3203,39 +3203,72 @@ upload_service_descriptor(rend_service_t *service) rendpostperiod = get_options()->RendPostPeriod; - /* Upload descriptor? */ - if (get_options()->PublishHidServDescriptors) { - networkstatus_t *c = networkstatus_get_latest_consensus(); - if (c && smartlist_len(c->routerstatus_list) > 0) { - int seconds_valid, i, j, num_descs; - smartlist_t *descs = smartlist_new(); - smartlist_t *client_cookies = smartlist_new(); - /* Either upload a single descriptor (including replicas) or one - * descriptor for each authorized client in case of authorization - * type 'stealth'. */ - num_descs = service->auth_type == REND_STEALTH_AUTH ? - smartlist_len(service->clients) : 1; - for (j = 0; j < num_descs; j++) { - crypto_pk_t *client_key = NULL; - rend_authorized_client_t *client = NULL; - smartlist_clear(client_cookies); - switch (service->auth_type) { - case REND_NO_AUTH: - /* Do nothing here. */ - break; - case REND_BASIC_AUTH: - SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, - cl, smartlist_add(client_cookies, cl->descriptor_cookie)); - break; - case REND_STEALTH_AUTH: - client = smartlist_get(service->clients, j); - client_key = client->client_key; - smartlist_add(client_cookies, client->descriptor_cookie); - break; - } - /* Encode the current descriptor. */ + networkstatus_t *c = networkstatus_get_latest_consensus(); + if (c && smartlist_len(c->routerstatus_list) > 0) { + int seconds_valid, i, j, num_descs; + smartlist_t *descs = smartlist_new(); + smartlist_t *client_cookies = smartlist_new(); + /* Either upload a single descriptor (including replicas) or one + * descriptor for each authorized client in case of authorization + * type 'stealth'. */ + num_descs = service->auth_type == REND_STEALTH_AUTH ? + smartlist_len(service->clients) : 1; + for (j = 0; j < num_descs; j++) { + crypto_pk_t *client_key = NULL; + rend_authorized_client_t *client = NULL; + smartlist_clear(client_cookies); + switch (service->auth_type) { + case REND_NO_AUTH: + /* Do nothing here. */ + break; + case REND_BASIC_AUTH: + SMARTLIST_FOREACH(service->clients, rend_authorized_client_t *, + cl, smartlist_add(client_cookies, cl->descriptor_cookie)); + break; + case REND_STEALTH_AUTH: + client = smartlist_get(service->clients, j); + client_key = client->client_key; + smartlist_add(client_cookies, client->descriptor_cookie); + break; + } + /* Encode the current descriptor. */ + seconds_valid = rend_encode_v2_descriptors(descs, service->desc, + now, 0, + service->auth_type, + client_key, + client_cookies); + if (seconds_valid < 0) { + log_warn(LD_BUG, "Internal error: couldn't encode service " + "descriptor; not uploading."); + smartlist_free(descs); + smartlist_free(client_cookies); + return; + } + rend_get_service_id(service->desc->pk, serviceid); + if (get_options()->PublishHidServDescriptors) { + /* Post the current descriptors to the hidden service directories. */ + log_info(LD_REND, "Launching upload for hidden service %s", + serviceid); + directory_post_to_hs_dir(service->desc, descs, NULL, serviceid, + seconds_valid); + } + /* Free memory for descriptors. */ + for (i = 0; i < smartlist_len(descs); i++) + rend_encoded_v2_service_descriptor_free(smartlist_get(descs, i)); + smartlist_clear(descs); + /* Update next upload time. */ + if (seconds_valid - REND_TIME_PERIOD_OVERLAPPING_V2_DESCS + > rendpostperiod) + service->next_upload_time = now + rendpostperiod; + else if (seconds_valid < REND_TIME_PERIOD_OVERLAPPING_V2_DESCS) + service->next_upload_time = now + seconds_valid + 1; + else + service->next_upload_time = now + seconds_valid - + REND_TIME_PERIOD_OVERLAPPING_V2_DESCS + 1; + /* Post also the next descriptors, if necessary. */ + if (seconds_valid < REND_TIME_PERIOD_OVERLAPPING_V2_DESCS) { seconds_valid = rend_encode_v2_descriptors(descs, service->desc, - now, 0, + now, 1, service->auth_type, client_key, client_cookies); @@ -3246,51 +3279,23 @@ upload_service_descriptor(rend_service_t *service) smartlist_free(client_cookies); return; } - /* Post the current descriptors to the hidden service directories. */ - rend_get_service_id(service->desc->pk, serviceid); - log_info(LD_REND, "Launching upload for hidden service %s", - serviceid); - directory_post_to_hs_dir(service->desc, descs, NULL, serviceid, - seconds_valid); + if (get_options()->PublishHidServDescriptors) { + directory_post_to_hs_dir(service->desc, descs, NULL, serviceid, + seconds_valid); + } /* Free memory for descriptors. */ for (i = 0; i < smartlist_len(descs); i++) rend_encoded_v2_service_descriptor_free(smartlist_get(descs, i)); smartlist_clear(descs); - /* Update next upload time. */ - if (seconds_valid - REND_TIME_PERIOD_OVERLAPPING_V2_DESCS - > rendpostperiod) - service->next_upload_time = now + rendpostperiod; - else if (seconds_valid < REND_TIME_PERIOD_OVERLAPPING_V2_DESCS) - service->next_upload_time = now + seconds_valid + 1; - else - service->next_upload_time = now + seconds_valid - - REND_TIME_PERIOD_OVERLAPPING_V2_DESCS + 1; - /* Post also the next descriptors, if necessary. */ - if (seconds_valid < REND_TIME_PERIOD_OVERLAPPING_V2_DESCS) { - seconds_valid = rend_encode_v2_descriptors(descs, service->desc, - now, 1, - service->auth_type, - client_key, - client_cookies); - if (seconds_valid < 0) { - log_warn(LD_BUG, "Internal error: couldn't encode service " - "descriptor; not uploading."); - smartlist_free(descs); - smartlist_free(client_cookies); - return; - } - directory_post_to_hs_dir(service->desc, descs, NULL, serviceid, - seconds_valid); - /* Free memory for descriptors. */ - for (i = 0; i < smartlist_len(descs); i++) - rend_encoded_v2_service_descriptor_free(smartlist_get(descs, i)); - smartlist_clear(descs); - } } - smartlist_free(descs); - smartlist_free(client_cookies); - uploaded = 1; + } + smartlist_free(descs); + smartlist_free(client_cookies); + uploaded = 1; + if (get_options()->PublishHidServDescriptors) { log_info(LD_REND, "Successfully uploaded v2 rend descriptors!"); + } else { + log_info(LD_REND, "Successfully stored created v2 rend descriptors!"); } } @@ -3635,9 +3640,6 @@ rend_consider_services_upload(time_t now) MIN_REND_INITIAL_POST_DELAY_TESTING : MIN_REND_INITIAL_POST_DELAY); - if (!get_options()->PublishHidServDescriptors) - return; - for (i=0; i < smartlist_len(rend_service_list); ++i) { service = smartlist_get(rend_service_list, i); if (!service->next_upload_time) { /* never been uploaded yet */ diff --git a/src/or/rephist.c b/src/or/rephist.c index fe0997c891..d55317947c 100644 --- a/src/or/rephist.c +++ b/src/or/rephist.c @@ -148,7 +148,7 @@ get_link_history(const char *from_id, const char *to_id) return NULL; if (tor_digest_is_zero(to_id)) return NULL; - lhist = (link_history_t*) digestmap_get(orhist->link_history_map, to_id); + lhist = digestmap_get(orhist->link_history_map, to_id); if (!lhist) { lhist = tor_malloc_zero(sizeof(link_history_t)); rephist_total_alloc += sizeof(link_history_t); @@ -920,7 +920,7 @@ parse_possibly_bad_iso_time(const char *s, time_t *time_out) * that's about as much before <b>now</b> as <b>t</b> was before * <b>stored_at</b>. */ -static INLINE time_t +static inline time_t correct_time(time_t t, time_t now, time_t stored_at, time_t started_measuring) { if (t < started_measuring - 24*60*60*365) @@ -1190,7 +1190,7 @@ commit_max(bw_array_t *b) } /** Shift the current observation time of <b>b</b> forward by one second. */ -static INLINE void +static inline void advance_obs(bw_array_t *b) { int nextidx; @@ -1216,7 +1216,7 @@ advance_obs(bw_array_t *b) /** Add <b>n</b> bytes to the number of bytes in <b>b</b> for second * <b>when</b>. */ -static INLINE void +static inline void add_obs(bw_array_t *b, time_t when, uint64_t n) { if (when < b->cur_obs_time) @@ -1250,6 +1250,18 @@ bw_array_new(void) return b; } +/** Free storage held by bandwidth array <b>b</b>. */ +static void +bw_array_free(bw_array_t *b) +{ + if (!b) { + return; + } + + rephist_total_alloc -= sizeof(bw_array_t); + tor_free(b); +} + /** Recent history of bandwidth observations for read operations. */ static bw_array_t *read_array = NULL; /** Recent history of bandwidth observations for write operations. */ @@ -1266,10 +1278,11 @@ static bw_array_t *dir_write_array = NULL; static void bw_arrays_init(void) { - tor_free(read_array); - tor_free(write_array); - tor_free(dir_read_array); - tor_free(dir_write_array); + bw_array_free(read_array); + bw_array_free(write_array); + bw_array_free(dir_read_array); + bw_array_free(dir_write_array); + read_array = bw_array_new(); write_array = bw_array_new(); dir_read_array = bw_array_new(); @@ -3026,21 +3039,21 @@ rep_hist_stored_maybe_new_hs(const crypto_pk_t *pubkey) /* The number of cells that are supposed to be hidden from the adversary * by adding noise from the Laplace distribution. This value, divided by - * EPSILON, is Laplace parameter b. */ + * EPSILON, is Laplace parameter b. It must be greather than 0. */ #define REND_CELLS_DELTA_F 2048 /* Security parameter for obfuscating number of cells with a value between - * 0 and 1. Smaller values obfuscate observations more, but at the same + * ]0.0, 1.0]. Smaller values obfuscate observations more, but at the same * time make statistics less usable. */ #define REND_CELLS_EPSILON 0.3 /* The number of cells that are supposed to be hidden from the adversary * by rounding up to the next multiple of this number. */ #define REND_CELLS_BIN_SIZE 1024 -/* The number of service identities that are supposed to be hidden from - * the adversary by adding noise from the Laplace distribution. This - * value, divided by EPSILON, is Laplace parameter b. */ +/* The number of service identities that are supposed to be hidden from the + * adversary by adding noise from the Laplace distribution. This value, + * divided by EPSILON, is Laplace parameter b. It must be greater than 0. */ #define ONIONS_SEEN_DELTA_F 8 /* Security parameter for obfuscating number of service identities with a - * value between 0 and 1. Smaller values obfuscate observations more, but + * value between ]0.0, 1.0]. Smaller values obfuscate observations more, but * at the same time make statistics less usable. */ #define ONIONS_SEEN_EPSILON 0.3 /* The number of service identities that are supposed to be hidden from @@ -3172,10 +3185,19 @@ rep_hist_free_all(void) { hs_stats_free(hs_stats); digestmap_free(history_map, free_or_history); - tor_free(read_array); - tor_free(write_array); - tor_free(dir_read_array); - tor_free(dir_write_array); + + bw_array_free(read_array); + read_array = NULL; + + bw_array_free(write_array); + write_array = NULL; + + bw_array_free(dir_read_array); + dir_read_array = NULL; + + bw_array_free(dir_write_array); + dir_write_array = NULL; + tor_free(exit_bytes_read); tor_free(exit_bytes_written); tor_free(exit_streams); @@ -3190,5 +3212,8 @@ rep_hist_free_all(void) } rep_hist_desc_stats_term(); total_descriptor_downloads = 0; + + tor_assert(rephist_total_alloc == 0); + tor_assert(rephist_total_num == 0); } diff --git a/src/or/replaycache.c b/src/or/replaycache.c index 569e0736cb..82e5c44d3d 100644 --- a/src/or/replaycache.c +++ b/src/or/replaycache.c @@ -23,7 +23,7 @@ replaycache_free(replaycache_t *r) return; } - if (r->digests_seen) digestmap_free(r->digests_seen, tor_free_); + if (r->digests_seen) digest256map_free(r->digests_seen, tor_free_); tor_free(r); } @@ -54,7 +54,7 @@ replaycache_new(time_t horizon, time_t interval) r->scrub_interval = interval; r->scrubbed = 0; r->horizon = horizon; - r->digests_seen = digestmap_new(); + r->digests_seen = digest256map_new(); err: return r; @@ -69,7 +69,7 @@ replaycache_add_and_test_internal( time_t *elapsed) { int rv = 0; - char digest[DIGEST_LEN]; + uint8_t digest[DIGEST256_LEN]; time_t *access_time; /* sanity check */ @@ -80,10 +80,10 @@ replaycache_add_and_test_internal( } /* compute digest */ - crypto_digest(digest, (const char *)data, len); + crypto_digest256((char *)digest, (const char *)data, len, DIGEST_SHA256); /* check map */ - access_time = digestmap_get(r->digests_seen, digest); + access_time = digest256map_get(r->digests_seen, digest); /* seen before? */ if (access_time != NULL) { @@ -114,7 +114,7 @@ replaycache_add_and_test_internal( /* No, so no hit and update the digest map with the current time */ access_time = tor_malloc(sizeof(*access_time)); *access_time = present; - digestmap_set(r->digests_seen, digest, access_time); + digest256map_set(r->digests_seen, digest, access_time); } /* now scrub the cache if it's time */ @@ -130,8 +130,8 @@ replaycache_add_and_test_internal( STATIC void replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) { - digestmap_iter_t *itr = NULL; - const char *digest; + digest256map_iter_t *itr = NULL; + const uint8_t *digest; void *valp; time_t *access_time; @@ -149,19 +149,19 @@ replaycache_scrub_if_needed_internal(time_t present, replaycache_t *r) if (r->horizon == 0) return; /* okay, scrub time */ - itr = digestmap_iter_init(r->digests_seen); - while (!digestmap_iter_done(itr)) { - digestmap_iter_get(itr, &digest, &valp); + itr = digest256map_iter_init(r->digests_seen); + while (!digest256map_iter_done(itr)) { + digest256map_iter_get(itr, &digest, &valp); access_time = (time_t *)valp; /* aged out yet? */ if (*access_time < present - r->horizon) { /* Advance the iterator and remove this one */ - itr = digestmap_iter_next_rmv(r->digests_seen, itr); + itr = digest256map_iter_next_rmv(r->digests_seen, itr); /* Free the value removed */ tor_free(access_time); } else { /* Just advance the iterator */ - itr = digestmap_iter_next(r->digests_seen, itr); + itr = digest256map_iter_next(r->digests_seen, itr); } } diff --git a/src/or/replaycache.h b/src/or/replaycache.h index 9b9daf3831..9c409f2fd7 100644 --- a/src/or/replaycache.h +++ b/src/or/replaycache.h @@ -26,7 +26,7 @@ struct replaycache_s { /* * Digest map: keys are digests, values are times the digest was last seen */ - digestmap_t *digests_seen; + digest256map_t *digests_seen; }; #endif /* REPLAYCACHE_PRIVATE */ diff --git a/src/or/router.c b/src/or/router.c index 841f6fde1b..8cde4a1a35 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -269,8 +269,8 @@ client_identity_key_is_set(void) /** Return the key certificate for this v3 (voting) authority, or NULL * if we have no such certificate. */ -authority_cert_t * -get_my_v3_authority_cert(void) +MOCK_IMPL(authority_cert_t *, +get_my_v3_authority_cert, (void)) { return authority_key_certificate; } @@ -1026,6 +1026,7 @@ init_keys(void) ds = trusted_dir_server_new(options->Nickname, NULL, router_get_advertised_dir_port(options, 0), router_get_advertised_or_port(options), + NULL, digest, v3_digest, type, 0.0); @@ -1098,43 +1099,47 @@ check_whether_dirport_reachable(void) can_reach_dir_port; } -/** Look at a variety of factors, and return 0 if we don't want to - * advertise the fact that we have a DirPort open. Else return the - * DirPort we want to advertise. - * - * Log a helpful message if we change our mind about whether to publish - * a DirPort. +/** The lower threshold of remaining bandwidth required to advertise (or + * automatically provide) directory services */ +/* XXX Should this be increased? */ +#define MIN_BW_TO_ADVERTISE_DIRSERVER 51200 + +/** Return true iff we have enough configured bandwidth to cache directory + * information. */ +static int +router_has_bandwidth_to_be_dirserver(const or_options_t *options) +{ + if (options->BandwidthRate < MIN_BW_TO_ADVERTISE_DIRSERVER) { + return 0; + } + if (options->RelayBandwidthRate > 0 && + options->RelayBandwidthRate < MIN_BW_TO_ADVERTISE_DIRSERVER) { + return 0; + } + return 1; +} + +/** Helper: Return 1 if we have sufficient resources for serving directory + * requests, return 0 otherwise. + * dir_port is either 0 or the configured DirPort number. + * If AccountingMax is set less than our advertised bandwidth, then don't + * serve requests. Likewise, if our advertised bandwidth is less than + * MIN_BW_TO_ADVERTISE_DIRSERVER, don't bother trying to serve requests. */ static int -decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) +router_should_be_directory_server(const or_options_t *options, int dir_port) { static int advertising=1; /* start out assuming we will advertise */ int new_choice=1; const char *reason = NULL; - /* Section one: reasons to publish or not publish that aren't - * worth mentioning to the user, either because they're obvious - * or because they're normal behavior. */ - - if (!dir_port) /* short circuit the rest of the function */ - return 0; - if (authdir_mode(options)) /* always publish */ - return dir_port; - if (net_is_disabled()) - return 0; - if (!check_whether_dirport_reachable()) - return 0; - if (!router_get_advertised_dir_port(options, dir_port)) - return 0; - - /* Section two: reasons to publish or not publish that the user - * might find surprising. These are generally config options that - * make us choose not to publish. */ - - if (accounting_is_enabled(options)) { + if (accounting_is_enabled(options) && + get_options()->AccountingRule != ACCT_IN) { /* Don't spend bytes for directory traffic if we could end up hibernating, * but allow DirPort otherwise. Some people set AccountingMax because - * they're confused or to get statistics. */ + * they're confused or to get statistics. Directory traffic has a much + * larger effect on output than input so there is no reason to turn it + * off if using AccountingRule in. */ int interval_length = accounting_get_interval_length(); uint32_t effective_bw = get_effective_bwrate(options); uint64_t acc_bytes; @@ -1157,10 +1162,7 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) new_choice = 0; reason = "AccountingMax enabled"; } -#define MIN_BW_TO_ADVERTISE_DIRPORT 51200 - } else if (options->BandwidthRate < MIN_BW_TO_ADVERTISE_DIRPORT || - (options->RelayBandwidthRate > 0 && - options->RelayBandwidthRate < MIN_BW_TO_ADVERTISE_DIRPORT)) { + } else if (! router_has_bandwidth_to_be_dirserver(options)) { /* if we're advertising a small amount */ new_choice = 0; reason = "BandwidthRate under 50KB"; @@ -1168,15 +1170,63 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) if (advertising != new_choice) { if (new_choice == 1) { - log_notice(LD_DIR, "Advertising DirPort as %d", dir_port); + if (dir_port > 0) + log_notice(LD_DIR, "Advertising DirPort as %d", dir_port); + else + log_notice(LD_DIR, "Advertising directory service support"); } else { tor_assert(reason); - log_notice(LD_DIR, "Not advertising DirPort (Reason: %s)", reason); + log_notice(LD_DIR, "Not advertising Dir%s (Reason: %s)", + dir_port ? "Port" : "ectory Service support", reason); } advertising = new_choice; } - return advertising ? dir_port : 0; + return advertising; +} + +/** Return 1 if we are configured to accept either relay or directory requests + * from clients and we aren't at risk of exceeding our bandwidth limits, thus + * we should be a directory server. If not, return 0. + */ +int +dir_server_mode(const or_options_t *options) +{ + if (!options->DirCache) + return 0; + return options->DirPort_set || + (server_mode(options) && router_has_bandwidth_to_be_dirserver(options)); +} + +/** Look at a variety of factors, and return 0 if we don't want to + * advertise the fact that we have a DirPort open, else return the + * DirPort we want to advertise. + * + * Log a helpful message if we change our mind about whether to publish + * a DirPort. + */ +static int +decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) +{ + /* Part one: reasons to publish or not publish that aren't + * worth mentioning to the user, either because they're obvious + * or because they're normal behavior. */ + + if (!dir_port) /* short circuit the rest of the function */ + return 0; + if (authdir_mode(options)) /* always publish */ + return dir_port; + if (net_is_disabled()) + return 0; + if (!check_whether_dirport_reachable()) + return 0; + if (!router_get_advertised_dir_port(options, dir_port)) + return 0; + + /* Part two: reasons to publish or not publish that the user + * might find surprising. router_should_be_directory_server() + * considers config options that make us choose not to publish. */ + return router_should_be_directory_server(options, dir_port) ? dir_port : 0; } /** Allocate and return a new extend_info_t that can be used to build @@ -1460,8 +1510,8 @@ static int server_is_advertised=0; /** Return true iff we have published our descriptor lately. */ -int -advertised_server_mode(void) +MOCK_IMPL(int, +advertised_server_mode,(void)) { return server_is_advertised; } @@ -1719,8 +1769,8 @@ router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port) /** Return true iff my exit policy is reject *:*. Return -1 if we don't * have a descriptor */ -int -router_my_exit_policy_is_reject_star(void) +MOCK_IMPL(int, +router_my_exit_policy_is_reject_star,(void)) { if (!router_get_my_routerinfo()) /* make sure desc_routerinfo exists */ return -1; @@ -1786,9 +1836,9 @@ router_get_my_descriptor(void) const char *body; if (!router_get_my_routerinfo()) return NULL; - /* Make sure this is nul-terminated. */ tor_assert(desc_routerinfo->cache_info.saved_location == SAVED_NOWHERE); body = signed_descriptor_get_body(&desc_routerinfo->cache_info); + /* Make sure this is nul-terminated. */ tor_assert(!body[desc_routerinfo->cache_info.signed_descriptor_len]); log_debug(LD_GENERAL,"my desc is '%s'", body); return body; @@ -1824,8 +1874,8 @@ static int router_guess_address_from_dir_headers(uint32_t *guess); * it's configured in torrc, or because we've learned it from * dirserver headers. Place the answer in *<b>addr</b> and return * 0 on success, else return -1 if we have no guess. */ -int -router_pick_published_address(const or_options_t *options, uint32_t *addr) +MOCK_IMPL(int, +router_pick_published_address,(const or_options_t *options, uint32_t *addr)) { *addr = get_last_resolved_addr(); if (!*addr && @@ -1870,6 +1920,8 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) ri->addr = addr; ri->or_port = router_get_advertised_or_port(options); ri->dir_port = router_get_advertised_dir_port(options, 0); + ri->supports_tunnelled_dir_requests = dir_server_mode(options) && + router_should_be_directory_server(options, ri->dir_port); ri->cache_info.published_on = time(NULL); ri->onion_pkey = crypto_pk_dup_key(get_onion_key()); /* must invoke from * main thread */ @@ -1927,7 +1979,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e) /* DNS is screwed up; don't claim to be an exit. */ policies_exit_policy_append_reject_star(&ri->exit_policy); } else { - policies_parse_exit_policy_from_options(options,ri->addr,&ri->ipv6_addr,1, + policies_parse_exit_policy_from_options(options,ri->addr,&ri->ipv6_addr, &ri->exit_policy); } ri->policy_is_reject_star = @@ -2646,6 +2698,10 @@ router_dump_router_to_string(routerinfo_t *router, tor_free(p6); } + if (router->supports_tunnelled_dir_requests) { + smartlist_add(chunks, tor_strdup("tunnelled-dir-server\n")); + } + /* Sign the descriptor with Ed25519 */ if (emit_ed_sigs) { smartlist_add(chunks, tor_strdup("router-sig-ed25519 ")); @@ -2733,44 +2789,13 @@ router_dump_exit_policy_to_string(const routerinfo_t *router, int include_ipv4, int include_ipv6) { - smartlist_t *exit_policy_strings; - char *policy_string = NULL; - if ((!router->exit_policy) || (router->policy_is_reject_star)) { return tor_strdup("reject *:*"); } - exit_policy_strings = smartlist_new(); - - SMARTLIST_FOREACH_BEGIN(router->exit_policy, addr_policy_t *, tmpe) { - char *pbuf; - int bytes_written_to_pbuf; - if ((tor_addr_family(&tmpe->addr) == AF_INET6) && (!include_ipv6)) { - continue; /* Don't include IPv6 parts of address policy */ - } - if ((tor_addr_family(&tmpe->addr) == AF_INET) && (!include_ipv4)) { - continue; /* Don't include IPv4 parts of address policy */ - } - - pbuf = tor_malloc(POLICY_BUF_LEN); - bytes_written_to_pbuf = policy_write_item(pbuf,POLICY_BUF_LEN, tmpe, 1); - - if (bytes_written_to_pbuf < 0) { - log_warn(LD_BUG, "router_dump_exit_policy_to_string ran out of room!"); - tor_free(pbuf); - goto done; - } - - smartlist_add(exit_policy_strings,pbuf); - } SMARTLIST_FOREACH_END(tmpe); - - policy_string = smartlist_join_strings(exit_policy_strings, "\n", 0, NULL); - - done: - SMARTLIST_FOREACH(exit_policy_strings, char *, str, tor_free(str)); - smartlist_free(exit_policy_strings); - - return policy_string; + return policy_dump_to_string(router->exit_policy, + include_ipv4, + include_ipv6); } /** Copy the primary (IPv4) OR port (IP address and TCP port) for diff --git a/src/or/router.h b/src/or/router.h index d8fcf0a9ad..ca590e3217 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -22,7 +22,7 @@ int server_identity_key_is_set(void); void set_client_identity_key(crypto_pk_t *k); crypto_pk_t *get_tlsclient_identity_key(void); int client_identity_key_is_set(void); -authority_cert_t *get_my_v3_authority_cert(void); +MOCK_DECL(authority_cert_t *, get_my_v3_authority_cert, (void)); crypto_pk_t *get_my_v3_authority_signing_key(void); authority_cert_t *get_my_v3_legacy_cert(void); crypto_pk_t *get_my_v3_legacy_signing_key(void); @@ -41,6 +41,7 @@ int init_keys_client(void); int check_whether_orport_reachable(void); int check_whether_dirport_reachable(void); +int dir_server_mode(const or_options_t *options); void consider_testing_reachability(int test_or, int test_dir); void router_orport_found_reachable(void); void router_dirport_found_reachable(void); @@ -67,7 +68,7 @@ uint16_t router_get_advertised_dir_port(const or_options_t *options, MOCK_DECL(int, server_mode, (const or_options_t *options)); MOCK_DECL(int, public_server_mode, (const or_options_t *options)); -int advertised_server_mode(void); +MOCK_DECL(int, advertised_server_mode, (void)); int proxy_mode(const or_options_t *options); void consider_publishable_server(int force); int should_refuse_unknown_exits(const or_options_t *options); @@ -80,7 +81,7 @@ void check_descriptor_ipaddress_changed(time_t now); void router_new_address_suggestion(const char *suggestion, const dir_connection_t *d_conn); int router_compare_to_my_exit_policy(const tor_addr_t *addr, uint16_t port); -int router_my_exit_policy_is_reject_star(void); +MOCK_DECL(int, router_my_exit_policy_is_reject_star,(void)); MOCK_DECL(const routerinfo_t *, router_get_my_routerinfo, (void)); extrainfo_t *router_get_my_extrainfo(void); const char *router_get_my_descriptor(void); @@ -89,7 +90,8 @@ int router_digest_is_me(const char *digest); const uint8_t *router_get_my_id_digest(void); int router_extrainfo_digest_is_me(const char *digest); int router_is_me(const routerinfo_t *router); -int router_pick_published_address(const or_options_t *options, uint32_t *addr); +MOCK_DECL(int,router_pick_published_address,(const or_options_t *options, + uint32_t *addr)); int router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e); int router_rebuild_descriptor(int force); char *router_dump_router_to_string(routerinfo_t *router, diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 79a5bb3910..139a2924fc 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -67,8 +67,6 @@ typedef struct cert_list_t cert_list_t; static int compute_weighted_bandwidths(const smartlist_t *sl, bandwidth_weight_rule_t rule, u64_dbl_t **bandwidths_out); -static const routerstatus_t *router_pick_directory_server_impl( - dirinfo_type_t auth, int flags, int *n_busy_out); static const routerstatus_t *router_pick_trusteddirserver_impl( const smartlist_t *sourcelist, dirinfo_type_t auth, int flags, int *n_busy_out); @@ -278,7 +276,7 @@ trusted_dirs_reload_certs(void) /** Helper: return true iff we already have loaded the exact cert * <b>cert</b>. */ -static INLINE int +static inline int already_have_cert(authority_cert_t *cert) { cert_list_t *cl = get_cert_list(cert->cache_info.identity_digest); @@ -897,8 +895,10 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) if (smartlist_len(fps) > 1) { resource = smartlist_join_strings(fps, "", 0, NULL); + /* XXX - do we want certs from authorities or mirrors? - teor */ directory_get_from_dirserver(DIR_PURPOSE_FETCH_CERTIFICATE, 0, - resource, PDS_RETRY_IF_NO_SERVERS); + resource, PDS_RETRY_IF_NO_SERVERS, + DL_WANT_ANY_DIRSERVER); tor_free(resource); } /* else we didn't add any: they were all pending */ @@ -941,8 +941,10 @@ authority_certs_fetch_missing(networkstatus_t *status, time_t now) if (smartlist_len(fp_pairs) > 1) { resource = smartlist_join_strings(fp_pairs, "", 0, NULL); + /* XXX - do we want certs from authorities or mirrors? - teor */ directory_get_from_dirserver(DIR_PURPOSE_FETCH_CERTIFICATE, 0, - resource, PDS_RETRY_IF_NO_SERVERS); + resource, PDS_RETRY_IF_NO_SERVERS, + DL_WANT_ANY_DIRSERVER); tor_free(resource); } /* else they were all pending */ @@ -985,7 +987,7 @@ router_should_rebuild_store(desc_store_t *store) /** Return the desc_store_t in <b>rl</b> that should be used to store * <b>sd</b>. */ -static INLINE desc_store_t * +static inline desc_store_t * desc_get_store(routerlist_t *rl, const signed_descriptor_t *sd) { if (sd->is_extrainfo) @@ -1295,8 +1297,8 @@ router_get_fallback_dir_servers(void) /** Try to find a running dirserver that supports operations of <b>type</b>. * * If there are no running dirservers in our routerlist and the - * <b>PDS_RETRY_IF_NO_SERVERS</b> flag is set, set all the authoritative ones - * as running again, and pick one. + * <b>PDS_RETRY_IF_NO_SERVERS</b> flag is set, set all the fallback ones + * (including authorities) as running again, and pick one. * * If the <b>PDS_IGNORE_FASCISTFIREWALL</b> flag is set, then include * dirservers that we can't reach. @@ -1304,8 +1306,9 @@ router_get_fallback_dir_servers(void) * If the <b>PDS_ALLOW_SELF</b> flag is not set, then don't include ourself * (if we're a dirserver). * - * Don't pick an authority if any non-authority is viable; try to avoid using - * servers that have returned 503 recently. + * Don't pick a fallback directory mirror if any non-fallback is viable; + * (the fallback directory mirrors include the authorities) + * try to avoid using servers that have returned 503 recently. */ const routerstatus_t * router_pick_directory_server(dirinfo_type_t type, int flags) @@ -1332,7 +1335,7 @@ router_pick_directory_server(dirinfo_type_t type, int flags) log_info(LD_DIR, "No reachable router entries for dirservers. " "Trying them all again."); - /* mark all authdirservers as up again */ + /* mark all fallback directory mirrors as up again */ mark_all_dirservers_up(fallback_dir_servers); /* try again */ choice = router_pick_directory_server_impl(type, flags, NULL); @@ -1358,15 +1361,20 @@ router_get_trusteddirserver_by_digest(const char *digest) } /** Return the dir_server_t for the fallback dirserver whose identity - * key hashes to <b>digest</b>, or NULL if no such authority is known. + * key hashes to <b>digest</b>, or NULL if no such fallback is in the list of + * fallback_dir_servers. (fallback_dir_servers is affected by the FallbackDir + * and UseDefaultFallbackDirs torrc options.) */ dir_server_t * router_get_fallback_dirserver_by_digest(const char *digest) { - if (!trusted_dir_servers) + if (!fallback_dir_servers) return NULL; - SMARTLIST_FOREACH(trusted_dir_servers, dir_server_t *, ds, + if (!digest) + return NULL; + + SMARTLIST_FOREACH(fallback_dir_servers, dir_server_t *, ds, { if (tor_memeq(ds->digest, digest, DIGEST_LEN)) return ds; @@ -1375,6 +1383,17 @@ router_get_fallback_dirserver_by_digest(const char *digest) return NULL; } +/** Return 1 if any fallback dirserver's identity key hashes to <b>digest</b>, + * or 0 if no such fallback is in the list of fallback_dir_servers. + * (fallback_dir_servers is affected by the FallbackDir and + * UseDefaultFallbackDirs torrc options.) + */ +int +router_digest_is_fallback_dir(const char *digest) +{ + return (router_get_fallback_dirserver_by_digest(digest) != NULL); +} + /** Return the dir_server_t for the directory authority whose * v3 identity key hashes to <b>digest</b>, or NULL if no such authority * is known. @@ -1451,7 +1470,7 @@ router_pick_dirserver_generic(smartlist_t *sourcelist, * directories that we excluded for no other reason than * PDS_NO_EXISTING_SERVERDESC_FETCH or PDS_NO_EXISTING_MICRODESC_FETCH. */ -static const routerstatus_t * +STATIC const routerstatus_t * router_pick_directory_server_impl(dirinfo_type_t type, int flags, int *n_busy_out) { @@ -1491,7 +1510,7 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, if (!status) continue; - if (!node->is_running || !status->dir_port || !node->is_valid) + if (!node->is_running || !node_is_dir(node) || !node->is_valid) continue; if (requireother && router_digest_is_me(node->identity)) continue; @@ -1718,22 +1737,24 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, result = &selection->fake_status; } - if (n_busy_out) - *n_busy_out = n_busy; - smartlist_free(direct); smartlist_free(tunnel); smartlist_free(overloaded_direct); smartlist_free(overloaded_tunnel); - if (result == NULL && try_excluding && !options->StrictNodes && n_excluded) { + if (result == NULL && try_excluding && !options->StrictNodes && n_excluded + && !n_busy) { /* If we got no result, and we are excluding nodes, and StrictNodes is * not set, try again without excluding nodes. */ try_excluding = 0; n_excluded = 0; + n_busy = 0; goto retry_without_exclude; } + if (n_busy_out) + *n_busy_out = n_busy; + return result; } @@ -1897,7 +1918,7 @@ scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, #if SIZEOF_VOID_P == 8 #define gt_i64_timei(a,b) ((a) > (b)) #else -static INLINE int +static inline int gt_i64_timei(uint64_t a, uint64_t b) { int64_t diff = (int64_t) (b - a); @@ -1975,7 +1996,7 @@ bridge_get_advertised_bandwidth_bounded(routerinfo_t *router) /** Return bw*1000, unless bw*1000 would overflow, in which case return * INT32_MAX. */ -static INLINE int32_t +static inline int32_t kb_to_bytes(uint32_t bw) { return (bw > (INT32_MAX/1000)) ? INT32_MAX : bw*1000; @@ -2790,7 +2811,7 @@ dump_routerlist_mem_usage(int severity) * in <b>sl</b> at position <b>idx</b>. Otherwise, search <b>sl</b> for * <b>ri</b>. Return the index of <b>ri</b> in <b>sl</b>, or -1 if <b>ri</b> * is not in <b>sl</b>. */ -static INLINE int +static inline int routerlist_find_elt_(smartlist_t *sl, void *ri, int idx) { if (idx < 0) { @@ -3215,7 +3236,11 @@ routerlist_reparse_old(routerlist_t *rl, signed_descriptor_t *sd) return ri; } -/** Free all memory held by the routerlist module. */ +/** Free all memory held by the routerlist module. + * Note: Calling routerlist_free_all() should always be paired with + * a call to nodelist_free_all(). These should only be called during + * cleanup. + */ void routerlist_free_all(void) { @@ -4034,15 +4059,16 @@ router_exit_policy_rejects_all(const routerinfo_t *router) } /** Create an directory server at <b>address</b>:<b>port</b>, with OR identity - * key <b>digest</b>. If <b>address</b> is NULL, add ourself. If - * <b>is_authority</b>, this is a directory authority. Return the new - * directory server entry on success or NULL on failure. */ + * key <b>digest</b> which has DIGEST_LEN bytes. If <b>address</b> is NULL, + * add ourself. If <b>is_authority</b>, this is a directory authority. Return + * the new directory server entry on success or NULL on failure. */ static dir_server_t * dir_server_new(int is_authority, const char *nickname, const tor_addr_t *addr, const char *hostname, uint16_t dir_port, uint16_t or_port, + const tor_addr_port_t *addrport_ipv6, const char *digest, const char *v3_auth_digest, dirinfo_type_t type, double weight) @@ -4051,13 +4077,15 @@ dir_server_new(int is_authority, uint32_t a; char *hostname_ = NULL; + tor_assert(digest); + if (weight < 0) return NULL; if (tor_addr_family(addr) == AF_INET) a = tor_addr_to_ipv4h(addr); else - return NULL; /*XXXX Support IPv6 */ + return NULL; if (!hostname) hostname_ = tor_dup_addr(addr); @@ -4074,6 +4102,18 @@ dir_server_new(int is_authority, ent->is_authority = is_authority; ent->type = type; ent->weight = weight; + if (addrport_ipv6) { + if (tor_addr_family(&addrport_ipv6->addr) != AF_INET6) { + log_warn(LD_BUG, "Hey, I got a non-ipv6 addr as addrport_ipv6."); + tor_addr_make_unspec(&ent->ipv6_addr); + } else { + tor_addr_copy(&ent->ipv6_addr, &addrport_ipv6->addr); + ent->ipv6_orport = addrport_ipv6->port; + } + } else { + tor_addr_make_unspec(&ent->ipv6_addr); + } + memcpy(ent->digest, digest, DIGEST_LEN); if (v3_auth_digest && (type & V3_DIRINFO)) memcpy(ent->v3_identity_digest, v3_auth_digest, DIGEST_LEN); @@ -4086,6 +4126,7 @@ dir_server_new(int is_authority, hostname, (int)dir_port); ent->fake_status.addr = ent->addr; + tor_addr_copy(&ent->fake_status.ipv6_addr, &ent->ipv6_addr); memcpy(ent->fake_status.identity_digest, digest, DIGEST_LEN); if (nickname) strlcpy(ent->fake_status.nickname, nickname, @@ -4094,6 +4135,7 @@ dir_server_new(int is_authority, ent->fake_status.nickname[0] = '\0'; ent->fake_status.dir_port = ent->dir_port; ent->fake_status.or_port = ent->or_port; + ent->fake_status.ipv6_orport = ent->ipv6_orport; return ent; } @@ -4105,6 +4147,7 @@ dir_server_new(int is_authority, dir_server_t * trusted_dir_server_new(const char *nickname, const char *address, uint16_t dir_port, uint16_t or_port, + const tor_addr_port_t *ipv6_addrport, const char *digest, const char *v3_auth_digest, dirinfo_type_t type, double weight) { @@ -4135,7 +4178,9 @@ trusted_dir_server_new(const char *nickname, const char *address, tor_addr_from_ipv4h(&addr, a); result = dir_server_new(1, nickname, &addr, hostname, - dir_port, or_port, digest, + dir_port, or_port, + ipv6_addrport, + digest, v3_auth_digest, type, weight); tor_free(hostname); return result; @@ -4147,9 +4192,12 @@ trusted_dir_server_new(const char *nickname, const char *address, dir_server_t * fallback_dir_server_new(const tor_addr_t *addr, uint16_t dir_port, uint16_t or_port, + const tor_addr_port_t *addrport_ipv6, const char *id_digest, double weight) { - return dir_server_new(0, NULL, addr, NULL, dir_port, or_port, id_digest, + return dir_server_new(0, NULL, addr, NULL, dir_port, or_port, + addrport_ipv6, + id_digest, NULL, ALL_DIRINFO, weight); } @@ -4374,14 +4422,14 @@ MOCK_IMPL(STATIC void, initiate_descriptor_downloads, tor_free(cp); if (source) { - /* We know which authority we want. */ + /* We know which authority or directory mirror we want. */ directory_initiate_command_routerstatus(source, purpose, ROUTER_PURPOSE_GENERAL, DIRIND_ONEHOP, resource, NULL, 0, 0); } else { directory_get_from_dirserver(purpose, ROUTER_PURPOSE_GENERAL, resource, - pds_flags); + pds_flags, DL_WANT_ANY_DIRSERVER); } tor_free(resource); } @@ -4392,13 +4440,20 @@ static int max_dl_per_request(const or_options_t *options, int purpose) { /* Since squid does not like URLs >= 4096 bytes we limit it to 96. - * 4096 - strlen(http://255.255.255.255/tor/server/d/.z) == 4058 - * 4058/41 (40 for the hash and 1 for the + that separates them) => 98 + * 4096 - strlen(http://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535 + * /tor/server/d/.z) == 4026 + * 4026/41 (40 for the hash and 1 for the + that separates them) => 98 * So use 96 because it's a nice number. + * + * For microdescriptors, the calculation is + * 4096 - strlen(http://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535 + * /tor/micro/d/.z) == 4027 + * 4027/44 (43 for the hash and 1 for the - that separates them) => 91 + * So use 90 because it's a nice number. */ int max = 96; if (purpose == DIR_PURPOSE_FETCH_MICRODESC) { - max = 92; + max = 90; } /* If we're going to tunnel our connections, we can ask for a lot more * in a request. */ @@ -4663,9 +4718,14 @@ launch_dummy_descriptor_download_as_needed(time_t now, last_descriptor_download_attempted + DUMMY_DOWNLOAD_INTERVAL < now && last_dummy_download + DUMMY_DOWNLOAD_INTERVAL < now) { last_dummy_download = now; + /* XX/teor - do we want an authority here, because they are less likely + * to give us the wrong address? (See #17782) + * I'm leaving the previous behaviour intact, because I don't like + * the idea of some relays contacting an authority every 20 minutes. */ directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_GENERAL, "authority.z", - PDS_RETRY_IF_NO_SERVERS); + PDS_RETRY_IF_NO_SERVERS, + DL_WANT_ANY_DIRSERVER); } } @@ -4851,7 +4911,9 @@ router_differences_are_cosmetic(const routerinfo_t *r1, const routerinfo_t *r2) (r1->contact_info && r2->contact_info && strcasecmp(r1->contact_info, r2->contact_info)) || r1->is_hibernating != r2->is_hibernating || - cmp_addr_policies(r1->exit_policy, r2->exit_policy)) + cmp_addr_policies(r1->exit_policy, r2->exit_policy) || + (r1->supports_tunnelled_dir_requests != + r2->supports_tunnelled_dir_requests)) return 0; if ((r1->declared_family == NULL) != (r2->declared_family == NULL)) return 0; @@ -5196,8 +5258,8 @@ hid_serv_acting_as_directory(void) /** Return true if this node is responsible for storing the descriptor ID * in <b>query</b> and false otherwise. */ -int -hid_serv_responsible_for_desc_id(const char *query) +MOCK_IMPL(int, hid_serv_responsible_for_desc_id, + (const char *query)) { const routerinfo_t *me; routerstatus_t *last_rs; diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 200533fe91..dd88aeb179 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -50,6 +50,7 @@ const routerstatus_t *router_pick_directory_server(dirinfo_type_t type, dir_server_t *router_get_trusteddirserver_by_digest(const char *d); dir_server_t *router_get_fallback_dirserver_by_digest( const char *digest); +int router_digest_is_fallback_dir(const char *digest); dir_server_t *trusteddirserver_get_by_v3_auth_digest(const char *d); const routerstatus_t *router_pick_trusteddirserver(dirinfo_type_t type, int flags); @@ -109,7 +110,7 @@ static int WRA_NEVER_DOWNLOADABLE(was_router_added_t s); * was added. It might still be necessary to check whether the descriptor * generator should be notified. */ -static INLINE int +static inline int WRA_WAS_ADDED(was_router_added_t s) { return s == ROUTER_ADDED_SUCCESSFULLY || s == ROUTER_ADDED_NOTIFY_GENERATOR; } @@ -120,7 +121,7 @@ WRA_WAS_ADDED(was_router_added_t s) { * - it was outdated. * - its certificates were expired. */ -static INLINE int WRA_WAS_OUTDATED(was_router_added_t s) +static inline int WRA_WAS_OUTDATED(was_router_added_t s) { return (s == ROUTER_WAS_TOO_OLD || s == ROUTER_IS_ALREADY_KNOWN || @@ -130,13 +131,13 @@ static INLINE int WRA_WAS_OUTDATED(was_router_added_t s) } /** Return true iff the outcome code in <b>s</b> indicates that the descriptor * was flat-out rejected. */ -static INLINE int WRA_WAS_REJECTED(was_router_added_t s) +static inline int WRA_WAS_REJECTED(was_router_added_t s) { return (s == ROUTER_AUTHDIR_REJECTS); } /** Return true iff the outcome code in <b>s</b> indicates that the descriptor * was flat-out rejected. */ -static INLINE int WRA_NEVER_DOWNLOADABLE(was_router_added_t s) +static inline int WRA_NEVER_DOWNLOADABLE(was_router_added_t s) { return (s == ROUTER_AUTHDIR_REJECTS || s == ROUTER_BAD_EI || @@ -170,10 +171,12 @@ int router_exit_policy_rejects_all(const routerinfo_t *router); dir_server_t *trusted_dir_server_new(const char *nickname, const char *address, uint16_t dir_port, uint16_t or_port, + const tor_addr_port_t *addrport_ipv6, const char *digest, const char *v3_auth_digest, dirinfo_type_t type, double weight); dir_server_t *fallback_dir_server_new(const tor_addr_t *addr, uint16_t dir_port, uint16_t or_port, + const tor_addr_port_t *addrport_ipv6, const char *id_digest, double weight); void dir_server_add(dir_server_t *ent); @@ -201,7 +204,7 @@ void refresh_all_country_info(void); int hid_serv_get_responsible_directories(smartlist_t *responsible_dirs, const char *id); int hid_serv_acting_as_directory(void); -int hid_serv_responsible_for_desc_id(const char *id); +MOCK_DECL(int, hid_serv_responsible_for_desc_id, (const char *id)); void list_pending_microdesc_downloads(digest256map_t *result); void launch_descriptor_downloads(int purpose, @@ -230,6 +233,9 @@ STATIC int choose_array_element_by_weight(const u64_dbl_t *entries, int n_entries); STATIC void scale_array_elements_to_u64(u64_dbl_t *entries, int n_entries, uint64_t *total_out); +STATIC const routerstatus_t *router_pick_directory_server_impl( + dirinfo_type_t auth, int flags, + int *n_busy_out); MOCK_DECL(int, router_descriptor_is_older_than, (const routerinfo_t *router, int seconds)); diff --git a/src/or/routerparse.c b/src/or/routerparse.c index f898ef8aef..fafba96e95 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -35,8 +35,9 @@ /****************************************************************************/ /** Enumeration of possible token types. The ones starting with K_ correspond - * to directory 'keywords'. ERR_ is an error in the tokenizing process, EOF_ - * is an end-of-file marker, and NIL_ is used to encode not-a-token. + * to directory 'keywords'. A_ is for an annotation, R or C is related to + * hidden services, ERR_ is an error in the tokenizing process, EOF_ is an + * end-of-file marker, and NIL_ is used to encode not-a-token. */ typedef enum { K_ACCEPT = 0, @@ -125,6 +126,7 @@ typedef enum { K_DIR_KEY_CERTIFICATION, K_DIR_KEY_CROSSCERT, K_DIR_ADDRESS, + K_DIR_TUNNELLED, K_VOTE_STATUS, K_VALID_AFTER, @@ -318,6 +320,7 @@ static token_rule_t routerdesc_token_table[] = { T0N("opt", K_OPT, CONCAT_ARGS, OBJ_OK ), T1( "bandwidth", K_BANDWIDTH, GE(3), NO_OBJ ), A01("@purpose", A_PURPOSE, GE(1), NO_OBJ ), + T01("tunnelled-dir-server",K_DIR_TUNNELLED, NO_ARGS, NO_OBJ ), END_OF_TABLE }; @@ -1609,6 +1612,12 @@ router_parse_entry_from_string(const char *s, const char *end, router->wants_to_be_hs_dir = 1; } + /* This router accepts tunnelled directory requests via begindir if it has + * an open dirport or it included "tunnelled-dir-server". */ + if (find_opt_by_keyword(tokens, K_DIR_TUNNELLED) || router->dir_port > 0) { + router->supports_tunnelled_dir_requests = 1; + } + tok = find_by_keyword(tokens, K_ROUTER_SIGNATURE); note_crypto_pk_op(VERIFY_RTR); #ifdef COUNT_DISTINCT_DIGESTS @@ -2061,7 +2070,7 @@ authority_cert_parse_from_string(const char *s, const char **end_of_string) * object (starting with "r " at the start of a line). If none is found, * return the start of the directory footer, or the next directory signature. * If none is found, return the end of the string. */ -static INLINE const char * +static inline const char * find_start_of_next_routerstatus(const char *s) { const char *eos, *footer, *sig; @@ -2294,6 +2303,8 @@ routerstatus_parse_entry_from_string(memarea_t *area, rs->is_unnamed = 1; } else if (!strcmp(tok->args[i], "HSDir")) { rs->is_hs_dir = 1; + } else if (!strcmp(tok->args[i], "V2Dir")) { + rs->is_v2_dir = 1; } } } @@ -3668,10 +3679,10 @@ networkstatus_parse_detached_signatures(const char *s, const char *eos) * * Returns NULL on policy errors. * - * If there is a policy error, malformed_list is set to true if the entire - * policy list should be discarded. Otherwise, it is set to false, and only - * this item should be ignored - the rest of the policy list can continue to - * be processed and used. + * Set *<b>malformed_list>/b> to true if the entire policy list should be + * discarded. Otherwise, set it to false, and only this item should be ignored + * on error - the rest of the policy list can continue to be processed and + * used. * * The addr_policy_t returned by this function can have its address set to * AF_UNSPEC for '*'. Use policy_expand_unspec() to turn this into a pair @@ -3684,8 +3695,8 @@ router_parse_addr_policy_item_from_string,(const char *s, int assume_action, directory_token_t *tok = NULL; const char *cp, *eos; /* Longest possible policy is - * "accept6 ffff:ffff:..255/128:10000-65535", - * which contains a max-length IPv6 address, plus 24 characters. + * "accept6 [ffff:ffff:..255]/128:10000-65535", + * which contains a max-length IPv6 address, plus 26 characters. * But note that there can be an arbitrary amount of space between the * accept and the address:mask/port element. * We don't need to multiply TOR_ADDR_BUF_LEN by 2, as there is only one @@ -3697,9 +3708,12 @@ router_parse_addr_policy_item_from_string,(const char *s, int assume_action, memarea_t *area = NULL; tor_assert(malformed_list); + *malformed_list = 0; s = eat_whitespace(s); - if ((*s == '*' || TOR_ISDIGIT(*s)) && assume_action >= 0) { + /* We can only do assume_action on []-quoted IPv6, as "a" (accept) + * and ":" (port separator) are ambiguous */ + if ((*s == '*' || *s == '[' || TOR_ISDIGIT(*s)) && assume_action >= 0) { if (tor_snprintf(line, sizeof(line), "%s %s", assume_action == ADDR_POLICY_ACCEPT?"accept":"reject", s)<0) { log_warn(LD_DIR, "Policy %s is too long.", escaped(s)); @@ -3930,7 +3944,7 @@ token_clear(directory_token_t *tok) * Return <b>tok</b> on success, or a new ERR_ token if the token didn't * conform to the syntax we wanted. **/ -static INLINE directory_token_t * +static inline directory_token_t * token_check_object(memarea_t *area, const char *kwd, directory_token_t *tok, obj_syntax o_syn) { @@ -3995,7 +4009,7 @@ token_check_object(memarea_t *area, const char *kwd, * number of parsed elements into the n_args field of <b>tok</b>. Allocate * all storage in <b>area</b>. Return the number of arguments parsed, or * return -1 if there was an insanely high number of arguments. */ -static INLINE int +static inline int get_token_arguments(memarea_t *area, directory_token_t *tok, const char *s, const char *eol) { diff --git a/src/or/routerset.c b/src/or/routerset.c index 3be55d3404..debe9ec6e1 100644 --- a/src/or/routerset.c +++ b/src/or/routerset.c @@ -107,10 +107,12 @@ routerset_parse(routerset_t *target, const char *s, const char *description) description); smartlist_add(target->country_names, countryname); added_countries = 1; - } else if ((strchr(nick,'.') || strchr(nick, '*')) && - (p = router_parse_addr_policy_item_from_string( + } else if ((strchr(nick,'.') || strchr(nick, ':') || strchr(nick, '*')) + && (p = router_parse_addr_policy_item_from_string( nick, ADDR_POLICY_REJECT, &malformed_list))) { + /* IPv4 addresses contain '.', IPv6 addresses contain ':', + * and wildcard addresses contain '*'. */ log_debug(LD_CONFIG, "Adding address %s to %s", nick, description); smartlist_add(target->policies, p); } else if (malformed_list) { diff --git a/src/or/statefile.c b/src/or/statefile.c index dd1894beb7..7fe8dc5077 100644 --- a/src/or/statefile.c +++ b/src/or/statefile.c @@ -9,6 +9,7 @@ #include "circuitstats.h" #include "config.h" #include "confparse.h" +#include "connection.h" #include "entrynodes.h" #include "hibernate.h" #include "rephist.h" @@ -372,6 +373,12 @@ or_state_load(void) new_state = or_state_new(); } else if (contents) { log_info(LD_GENERAL, "Loaded state from \"%s\"", fname); + /* Warn the user if their clock has been set backwards, + * they could be tricked into using old consensuses */ + time_t apparent_skew = new_state->LastWritten - time(NULL); + if (apparent_skew > 0) + clock_skew_warning(NULL, (long)apparent_skew, 1, LD_GENERAL, + "local state file", fname); } else { log_info(LD_GENERAL, "Initialized state"); } diff --git a/src/or/status.c b/src/or/status.c index 8f7be0aa3c..69d10721d2 100644 --- a/src/or/status.c +++ b/src/or/status.c @@ -164,24 +164,38 @@ log_accounting(const time_t now, const or_options_t *options) or_state_t *state = get_or_state(); char *acc_rcvd = bytes_to_usage(state->AccountingBytesReadInInterval); char *acc_sent = bytes_to_usage(state->AccountingBytesWrittenInInterval); + char *acc_used = bytes_to_usage(get_accounting_bytes()); uint64_t acc_bytes = options->AccountingMax; char *acc_max; time_t interval_end = accounting_get_end_time(); char end_buf[ISO_TIME_LEN + 1]; char *remaining = NULL; - if (options->AccountingRule == ACCT_SUM) - acc_bytes *= 2; acc_max = bytes_to_usage(acc_bytes); format_local_iso_time(end_buf, interval_end); remaining = secs_to_uptime(interval_end - now); + const char *acc_rule; + switch (options->AccountingRule) { + case ACCT_MAX: acc_rule = "max"; + break; + case ACCT_SUM: acc_rule = "sum"; + break; + case ACCT_OUT: acc_rule = "out"; + break; + case ACCT_IN: acc_rule = "in"; + break; + default: acc_rule = "max"; + break; + } + log_notice(LD_HEARTBEAT, "Heartbeat: Accounting enabled. " - "Sent: %s / %s, Received: %s / %s. The " + "Sent: %s, Received: %s, Used: %s / %s, Rule: %s. The " "current accounting interval ends on %s, in %s.", - acc_sent, acc_max, acc_rcvd, acc_max, end_buf, remaining); + acc_sent, acc_rcvd, acc_used, acc_max, acc_rule, end_buf, remaining); tor_free(acc_rcvd); tor_free(acc_sent); + tor_free(acc_used); tor_free(acc_max); tor_free(remaining); } diff --git a/src/or/transports.c b/src/or/transports.c index ba2c784c2c..81b8db2508 100644 --- a/src/or/transports.c +++ b/src/or/transports.c @@ -105,7 +105,7 @@ static process_environment_t * create_managed_proxy_environment(const managed_proxy_t *mp); -static INLINE int proxy_configuration_finished(const managed_proxy_t *mp); +static inline int proxy_configuration_finished(const managed_proxy_t *mp); static void handle_finished_proxy(managed_proxy_t *mp); static void parse_method_error(const char *line, int is_server_method); @@ -713,7 +713,7 @@ register_client_proxy(const managed_proxy_t *mp) } /** Register the transports of managed proxy <b>mp</b>. */ -static INLINE void +static inline void register_proxy(const managed_proxy_t *mp) { if (mp->is_server) @@ -828,7 +828,7 @@ handle_finished_proxy(managed_proxy_t *mp) /** Return true if the configuration of the managed proxy <b>mp</b> is finished. */ -static INLINE int +static inline int proxy_configuration_finished(const managed_proxy_t *mp) { return (mp->conf_state == PT_PROTO_CONFIGURED || diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index 0435617683..0ba56d7036 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -14,7 +14,8 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ TEST_OBJECTS = test.obj test_addr.obj test_channel.obj test_channeltls.obj \ test_containers.obj \ test_controller_events.obj test_crypto.obj test_data.obj test_dir.obj \ - test_checkdir.obj test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ + test_checkdir.obj test_microdesc.obj test_pt.obj test_util.obj \ + test_config.obj test_connection.obj \ test_cell_formats.obj test_relay.obj test_replay.obj \ test_scheduler.obj test_introduce.obj test_hs.obj tinytest.obj diff --git a/src/test/bench.c b/src/test/bench.c index 2a27377c80..1ef54042b2 100644 --- a/src/test/bench.c +++ b/src/test/bench.c @@ -443,6 +443,45 @@ bench_siphash(void) } static void +bench_digest(void) +{ + char buf[8192]; + char out[DIGEST512_LEN]; + const int lens[] = { 1, 16, 32, 64, 128, 512, 1024, 2048, -1 }; + const int N = 300000; + uint64_t start, end; + crypto_rand(buf, sizeof(buf)); + + for (int alg = 0; alg < N_DIGEST_ALGORITHMS; alg++) { + for (int i = 0; lens[i] > 0; ++i) { + reset_perftime(); + start = perftime(); + for (int j = 0; j < N; ++j) { + switch (alg) { + case DIGEST_SHA1: + crypto_digest(out, buf, lens[i]); + break; + case DIGEST_SHA256: + case DIGEST_SHA3_256: + crypto_digest256(out, buf, lens[i], alg); + break; + case DIGEST_SHA512: + case DIGEST_SHA3_512: + crypto_digest512(out, buf, lens[i], alg); + break; + default: + tor_assert(0); + } + } + end = perftime(); + printf("%s(%d): %.2f ns per call\n", + crypto_digest_algorithm_get_name(alg), + lens[i], NANOCOUNT(start,end,N)); + } + } +} + +static void bench_cell_ops(void) { const int iters = 1<<16; @@ -589,6 +628,7 @@ typedef struct benchmark_t { static struct benchmark_t benchmarks[] = { ENT(dmap), ENT(siphash), + ENT(digest), ENT(aes), ENT(onion_TAP), ENT(onion_ntor), @@ -643,7 +683,10 @@ main(int argc, const char **argv) reset_perftime(); - crypto_seed_rng(); + if (crypto_seed_rng() < 0) { + printf("Couldn't seed RNG; exiting.\n"); + return 1; + } crypto_init_siphash_key(); options = options_new(); init_logging(1); diff --git a/src/test/bt_test.py b/src/test/bt_test.py index e694361703..dab02d7699 100755 --- a/src/test/bt_test.py +++ b/src/test/bt_test.py @@ -15,6 +15,7 @@ OK """ +from __future__ import print_function import sys @@ -37,6 +38,8 @@ for I in range(len(LINES)): if matches(LINES[I:], FUNCNAMES): print("OK") sys.exit(0) -else: - print("BAD") - sys.exit(1) + +for l in LINES: + print("{}".format(l), end="") + +sys.exit(1) diff --git a/src/test/include.am b/src/test/include.am index 7a02a4e2f2..786fb77dd0 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -8,14 +8,16 @@ TESTS_ENVIRONMENT = \ export builddir="$(builddir)"; \ export TESTING_TOR_BINARY="$(TESTING_TOR_BINARY)"; -TESTSCRIPTS = src/test/test_zero_length_keys.sh +TESTSCRIPTS = src/test/test_zero_length_keys.sh \ + src/test/test_switch_id.sh if USEPYTHON TESTSCRIPTS += src/test/test_ntor.sh src/test/test_bt.sh endif TESTS += src/test/test src/test/test-slow src/test/test-memwipe \ - src/test/test_workqueue src/test/test_keygen.sh $(TESTSCRIPTS) + src/test/test_workqueue src/test/test_keygen.sh \ + $(TESTSCRIPTS) # These flavors are run using automake's test-driver and test-network.sh TEST_CHUTNEY_FLAVORS = basic-min bridges-min hs-min bridges+hs @@ -37,7 +39,8 @@ noinst_PROGRAMS+= \ src/test/test-slow \ src/test/test-memwipe \ src/test/test-child \ - src/test/test_workqueue + src/test/test_workqueue \ + src/test/test-switch-id endif src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ @@ -53,6 +56,8 @@ src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ # matters a lot there, and is quite hard to debug if you forget to do it. src_test_test_SOURCES = \ + src/test/log_test_helpers.c \ + src/test/rend_test_helpers.c \ src/test/test.c \ src/test/test_accounting.c \ src/test/test_addr.c \ @@ -65,13 +70,17 @@ src_test_test_SOURCES = \ src/test/test_checkdir.c \ src/test/test_circuitlist.c \ src/test/test_circuitmux.c \ + src/test/test_compat_libevent.c \ src/test/test_config.c \ + src/test/test_connection.c \ src/test/test_containers.c \ src/test/test_controller.c \ src/test/test_controller_events.c \ src/test/test_crypto.c \ src/test/test_data.c \ src/test/test_dir.c \ + src/test/test_dir_common.c \ + src/test/test_dir_handle_get.c \ src/test/test_entryconn.c \ src/test/test_entrynodes.c \ src/test/test_guardfraction.c \ @@ -86,9 +95,11 @@ src_test_test_SOURCES = \ src/test/test_oom.c \ src/test/test_options.c \ src/test/test_policy.c \ + src/test/test_procmon.c \ src/test/test_pt.c \ src/test/test_relay.c \ src/test/test_relaycell.c \ + src/test/test_rendcache.c \ src/test/test_replay.c \ src/test/test_routerkeys.c \ src/test/test_routerlist.c \ @@ -97,9 +108,12 @@ src_test_test_SOURCES = \ src/test/test_socks.c \ src/test/test_status.c \ src/test/test_threads.c \ + src/test/test_tortls.c \ src/test/test_util.c \ + src/test/test_util_format.c \ + src/test/test_util_process.c \ src/test/test_helpers.c \ - src/test/test_dns.c \ + src/test/test_dns.c \ src/test/testing_common.c \ src/ext/tinytest.c @@ -126,11 +140,21 @@ src_test_test_workqueue_SOURCES = \ src_test_test_workqueue_CPPFLAGS= $(src_test_AM_CPPFLAGS) src_test_test_workqueue_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_test_test_switch_id_SOURCES = \ + src/test/test_switch_id.c +src_test_test_switch_id_CPPFLAGS= $(src_test_AM_CPPFLAGS) +src_test_test_switch_id_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) +src_test_test_switch_id_LDFLAGS = @TOR_LDFLAGS_zlib@ +src_test_test_switch_id_LDADD = \ + src/common/libor-testing.a \ + @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ + src_test_test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ src_test_test_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \ - src/common/libor-crypto-testing.a $(LIBDONNA) src/common/libor.a \ - src/common/libor-event-testing.a src/trunnel/libor-trunnel-testing.a \ + src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \ + src/common/libor.a src/common/libor-event-testing.a \ + src/trunnel/libor-trunnel-testing.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ @TOR_SYSTEMD_LIBS@ @@ -148,7 +172,7 @@ src_test_test_memwipe_LDFLAGS = $(src_test_test_LDFLAGS) src_test_bench_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ src_test_bench_LDADD = src/or/libtor.a src/common/libor.a \ - src/common/libor-crypto.a $(LIBDONNA) \ + src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event.a src/trunnel/libor-trunnel.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ \ @@ -158,26 +182,30 @@ src_test_test_workqueue_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ src_test_test_workqueue_LDADD = src/or/libtor-testing.a \ src/common/libor-testing.a \ - src/common/libor-crypto-testing.a $(LIBDONNA) \ + src/common/libor-crypto-testing.a $(LIBKECCAK_TINY) $(LIBDONNA) \ src/common/libor-event-testing.a \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ @TOR_LIBEVENT_LIBS@ \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ noinst_HEADERS+= \ src/test/fakechans.h \ + src/test/log_test_helpers.h \ + src/test/rend_test_helpers.h \ src/test/test.h \ src/test/test_helpers.h \ + src/test/test_dir_common.h \ src/test/test_descriptors.inc \ src/test/example_extrainfo.inc \ src/test/failing_routerdescs.inc \ src/test/ed25519_vectors.inc \ - src/test/test_descriptors.inc + src/test/test_descriptors.inc \ + src/test/vote_descriptors.inc noinst_PROGRAMS+= src/test/test-ntor-cl src_test_test_ntor_cl_SOURCES = src/test/test_ntor_cl.c src_test_test_ntor_cl_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ src_test_test_ntor_cl_LDADD = src/or/libtor.a src/common/libor.a \ - src/common/libor-crypto.a $(LIBDONNA) \ + src/common/libor-crypto.a $(LIBKECCAK_TINY) $(LIBDONNA) \ @TOR_ZLIB_LIBS@ @TOR_LIB_MATH@ \ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ src_test_test_ntor_cl_AM_CPPFLAGS = \ @@ -199,4 +227,5 @@ EXTRA_DIST += \ src/test/test_keygen.sh \ src/test/test_zero_length_keys.sh \ src/test/test_ntor.sh src/test/test_bt.sh \ - src/test/test-network.sh + src/test/test-network.sh \ + src/test/test_switch_id.sh diff --git a/src/test/log_test_helpers.c b/src/test/log_test_helpers.c new file mode 100644 index 0000000000..51b5f9b7b1 --- /dev/null +++ b/src/test/log_test_helpers.c @@ -0,0 +1,111 @@ +/* Copyright (c) 2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ +#define LOG_PRIVATE +#include "torlog.h" +#include "log_test_helpers.h" + +static smartlist_t *saved_logs = NULL; + +int +setup_capture_of_logs(int new_level) +{ + int previous_log = log_global_min_severity_; + log_global_min_severity_ = new_level; + mock_clean_saved_logs(); + MOCK(logv, mock_saving_logv); + return previous_log; +} + +void +teardown_capture_of_logs(int prev) +{ + UNMOCK(logv); + log_global_min_severity_ = prev; + mock_clean_saved_logs(); +} + +void +mock_clean_saved_logs(void) +{ + if (!saved_logs) + return; + SMARTLIST_FOREACH(saved_logs, mock_saved_log_entry_t *, m, + { tor_free(m->generated_msg); tor_free(m); }); + smartlist_free(saved_logs); + saved_logs = NULL; +} + +static mock_saved_log_entry_t * +mock_get_log_entry(int ix) +{ + int saved_log_count = mock_saved_log_number(); + if (ix < 0) { + ix = saved_log_count + ix; + } + + if (saved_log_count <= ix) + return NULL; + + return smartlist_get(saved_logs, ix); +} + +const char * +mock_saved_log_at(int ix) +{ + mock_saved_log_entry_t *ent = mock_get_log_entry(ix); + if (ent) + return ent->generated_msg; + else + return ""; +} + +int +mock_saved_severity_at(int ix) +{ + mock_saved_log_entry_t *ent = mock_get_log_entry(ix); + if (ent) + return ent->severity; + else + return -1; +} + +int +mock_saved_log_number(void) +{ + if (!saved_logs) + return 0; + return smartlist_len(saved_logs); +} + +const smartlist_t * +mock_saved_logs(void) +{ + return saved_logs; +} + +void +mock_saving_logv(int severity, log_domain_mask_t domain, + const char *funcname, const char *suffix, + const char *format, va_list ap) +{ + (void)domain; + char *buf = tor_malloc_zero(10240); + int n; + n = tor_vsnprintf(buf,10240,format,ap); + tor_assert(n < 10240-1); + buf[n]='\n'; + buf[n+1]='\0'; + + mock_saved_log_entry_t *e = tor_malloc_zero(sizeof(mock_saved_log_entry_t)); + e->severity = severity; + e->funcname = funcname; + e->suffix = suffix; + e->format = format; + e->generated_msg = tor_strdup(buf); + tor_free(buf); + + if (!saved_logs) + saved_logs = smartlist_new(); + smartlist_add(saved_logs, e); +} + diff --git a/src/test/log_test_helpers.h b/src/test/log_test_helpers.h new file mode 100644 index 0000000000..af8e8a60e7 --- /dev/null +++ b/src/test/log_test_helpers.h @@ -0,0 +1,31 @@ +/* Copyright (c) 2014-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" + +#ifndef TOR_LOG_TEST_HELPERS_H +#define TOR_LOG_TEST_HELPERS_H + +typedef struct mock_saved_log_entry_t { + int severity; + const char *funcname; + const char *suffix; + const char *format; + char *generated_msg; + struct mock_saved_log_entry_t *next; +} mock_saved_log_entry_t; + +void mock_saving_logv(int severity, log_domain_mask_t domain, + const char *funcname, const char *suffix, + const char *format, va_list ap) + CHECK_PRINTF(5, 0); +void mock_clean_saved_logs(void); +const smartlist_t *mock_saved_logs(void); +int setup_capture_of_logs(int new_level); +void teardown_capture_of_logs(int prev); +const char *mock_saved_log_at(int ix); +int mock_saved_severity_at(int ix); +int mock_saved_log_number(void); + +#endif + diff --git a/src/test/rend_test_helpers.c b/src/test/rend_test_helpers.c new file mode 100644 index 0000000000..f16d67fa1a --- /dev/null +++ b/src/test/rend_test_helpers.c @@ -0,0 +1,73 @@ +/* Copyright (c) 2014-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "test.h" +#include "rendcommon.h" +#include "rend_test_helpers.h" + +void +generate_desc(int time_diff, rend_encoded_v2_service_descriptor_t **desc, + char **service_id, int intro_points) +{ + rend_service_descriptor_t *generated = NULL; + smartlist_t *descs = smartlist_new(); + time_t now; + + now = time(NULL) + time_diff; + create_descriptor(&generated, service_id, intro_points); + generated->timestamp = now; + + rend_encode_v2_descriptors(descs, generated, now, 0, REND_NO_AUTH, NULL, + NULL); + tor_assert(smartlist_len(descs) > 1); + *desc = smartlist_get(descs, 0); + smartlist_set(descs, 0, NULL); + + SMARTLIST_FOREACH(descs, rend_encoded_v2_service_descriptor_t *, d, + rend_encoded_v2_service_descriptor_free(d)); + smartlist_free(descs); + rend_service_descriptor_free(generated); +} + +void +create_descriptor(rend_service_descriptor_t **generated, char **service_id, + int intro_points) +{ + crypto_pk_t *pk1 = NULL; + crypto_pk_t *pk2 = NULL; + int i; + + *service_id = tor_malloc(REND_SERVICE_ID_LEN_BASE32+1); + pk1 = pk_generate(0); + pk2 = pk_generate(1); + + *generated = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + (*generated)->pk = crypto_pk_dup_key(pk1); + rend_get_service_id((*generated)->pk, *service_id); + + (*generated)->version = 2; + (*generated)->protocols = 42; + (*generated)->intro_nodes = smartlist_new(); + + for (i = 0; i < intro_points; i++) { + rend_intro_point_t *intro = tor_malloc_zero(sizeof(rend_intro_point_t)); + crypto_pk_t *okey = pk_generate(2 + i); + intro->extend_info = tor_malloc_zero(sizeof(extend_info_t)); + intro->extend_info->onion_key = okey; + crypto_pk_get_digest(intro->extend_info->onion_key, + intro->extend_info->identity_digest); + intro->extend_info->nickname[0] = '$'; + base16_encode(intro->extend_info->nickname + 1, + sizeof(intro->extend_info->nickname) - 1, + intro->extend_info->identity_digest, DIGEST_LEN); + tor_addr_from_ipv4h(&intro->extend_info->addr, crypto_rand_int(65536)); + intro->extend_info->port = 1 + crypto_rand_int(65535); + intro->intro_key = crypto_pk_dup_key(pk2); + smartlist_add((*generated)->intro_nodes, intro); + } + + crypto_pk_free(pk1); + crypto_pk_free(pk2); +} + diff --git a/src/test/rend_test_helpers.h b/src/test/rend_test_helpers.h new file mode 100644 index 0000000000..1ef03747d7 --- /dev/null +++ b/src/test/rend_test_helpers.h @@ -0,0 +1,15 @@ +/* Copyright (c) 2014-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" + +#ifndef TOR_REND_TEST_HELPERS_H +#define TOR_REND_TEST_HELPERS_H + +void generate_desc(int time_diff, rend_encoded_v2_service_descriptor_t **desc, + char **service_id, int intro_points); +void create_descriptor(rend_service_descriptor_t **generated, + char **service_id, int intro_points); + +#endif + diff --git a/src/test/test-memwipe.c b/src/test/test-memwipe.c index a39bad1540..5d4fcec664 100644 --- a/src/test/test-memwipe.c +++ b/src/test/test-memwipe.c @@ -62,7 +62,7 @@ fill_a_buffer_nothing(void) return sum; } -static INLINE int +static inline int vmemeq(volatile char *a, const char *b, size_t n) { while (n--) { diff --git a/src/test/test.c b/src/test/test.c index e10e260266..f12ae21ff0 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -28,6 +28,7 @@ #define ROUTER_PRIVATE #define CIRCUITSTATS_PRIVATE #define CIRCUITLIST_PRIVATE +#define MAIN_PRIVATE #define STATEFILE_PRIVATE /* @@ -47,8 +48,10 @@ double fabs(double x); #include "connection_edge.h" #include "geoip.h" #include "rendcommon.h" +#include "rendcache.h" #include "test.h" #include "torgzip.h" +#include "main.h" #include "memarea.h" #include "onion.h" #include "onion_ntor.h" @@ -316,6 +319,13 @@ test_circuit_timeout(void *arg) int i, runs; double close_ms; (void)arg; + tor_libevent_cfg cfg; + + memset(&cfg, 0, sizeof(cfg)); + + tor_libevent_initialize(&cfg); + initialize_periodic_events(); + circuit_build_times_init(&initial); circuit_build_times_init(&estimate); circuit_build_times_init(&final); @@ -455,6 +465,7 @@ test_circuit_timeout(void *arg) circuit_build_times_free_timeouts(&estimate); circuit_build_times_free_timeouts(&final); or_state_free(state); + teardown_periodic_events(); } /** Test encoding and parsing of rendezvous service descriptors. */ @@ -494,6 +505,9 @@ test_rend_fns(void *arg) tt_str_op(address6,OP_EQ, "abcdefghijklmnop"); tt_assert(BAD_HOSTNAME == parse_extended_hostname(address7)); + /* Initialize the service cache. */ + rend_cache_init(); + pk1 = pk_generate(0); pk2 = pk_generate(1); generated = tor_malloc_zero(sizeof(rend_service_descriptor_t)); @@ -1105,8 +1119,8 @@ static struct testcase_t test_array[] = { { "bad_onion_handshake", test_bad_onion_handshake, 0, NULL, NULL }, ENT(onion_queues), { "ntor_handshake", test_ntor_handshake, 0, NULL, NULL }, - ENT(circuit_timeout), - ENT(rend_fns), + FORK(circuit_timeout), + FORK(rend_fns), ENT(geoip), FORK(geoip_with_pt), FORK(stats), @@ -1125,12 +1139,15 @@ extern struct testcase_t channeltls_tests[]; extern struct testcase_t checkdir_tests[]; extern struct testcase_t circuitlist_tests[]; extern struct testcase_t circuitmux_tests[]; +extern struct testcase_t compat_libevent_tests[]; extern struct testcase_t config_tests[]; +extern struct testcase_t connection_tests[]; extern struct testcase_t container_tests[]; extern struct testcase_t controller_tests[]; extern struct testcase_t controller_event_tests[]; extern struct testcase_t crypto_tests[]; extern struct testcase_t dir_tests[]; +extern struct testcase_t dir_handle_get_tests[]; extern struct testcase_t entryconn_tests[]; extern struct testcase_t entrynodes_tests[]; extern struct testcase_t guardfraction_tests[]; @@ -1145,9 +1162,11 @@ extern struct testcase_t nodelist_tests[]; extern struct testcase_t oom_tests[]; extern struct testcase_t options_tests[]; extern struct testcase_t policy_tests[]; +extern struct testcase_t procmon_tests[]; extern struct testcase_t pt_tests[]; extern struct testcase_t relay_tests[]; extern struct testcase_t relaycell_tests[]; +extern struct testcase_t rend_cache_tests[]; extern struct testcase_t replaycache_tests[]; extern struct testcase_t router_tests[]; extern struct testcase_t routerkeys_tests[]; @@ -1157,7 +1176,10 @@ extern struct testcase_t scheduler_tests[]; extern struct testcase_t socks_tests[]; extern struct testcase_t status_tests[]; extern struct testcase_t thread_tests[]; +extern struct testcase_t tortls_tests[]; extern struct testcase_t util_tests[]; +extern struct testcase_t util_format_tests[]; +extern struct testcase_t util_process_tests[]; extern struct testcase_t dns_tests[]; struct testgroup_t testgroups[] = { @@ -1173,12 +1195,15 @@ struct testgroup_t testgroups[] = { { "checkdir/", checkdir_tests }, { "circuitlist/", circuitlist_tests }, { "circuitmux/", circuitmux_tests }, + { "compat/libevent/", compat_libevent_tests }, { "config/", config_tests }, + { "connection/", connection_tests }, { "container/", container_tests }, { "control/", controller_tests }, { "control/event/", controller_event_tests }, { "crypto/", crypto_tests }, { "dir/", dir_tests }, + { "dir_handle_get/", dir_handle_get_tests }, { "dir/md/", microdesc_tests }, { "entryconn/", entryconn_tests }, { "entrynodes/", entrynodes_tests }, @@ -1192,9 +1217,11 @@ struct testgroup_t testgroups[] = { { "oom/", oom_tests }, { "options/", options_tests }, { "policy/" , policy_tests }, + { "procmon/", procmon_tests }, { "pt/", pt_tests }, { "relay/" , relay_tests }, { "relaycell/", relaycell_tests }, + { "rend_cache/", rend_cache_tests }, { "replaycache/", replaycache_tests }, { "routerkeys/", routerkeys_tests }, { "routerlist/", routerlist_tests }, @@ -1202,8 +1229,11 @@ struct testgroup_t testgroups[] = { { "scheduler/", scheduler_tests }, { "socks/", socks_tests }, { "status/" , status_tests }, + { "tortls/", tortls_tests }, { "util/", util_tests }, + { "util/format/", util_format_tests }, { "util/logging/", logging_tests }, + { "util/process/", util_process_tests }, { "util/thread/", thread_tests }, { "dns/", dns_tests }, END_OF_GROUPS diff --git a/src/test/test_accounting.c b/src/test/test_accounting.c index 25908e942c..7edba988a6 100644 --- a/src/test/test_accounting.c +++ b/src/test/test_accounting.c @@ -61,6 +61,32 @@ test_accounting_limits(void *arg) fake_time += 1; consider_hibernation(fake_time); tor_assert(we_are_hibernating() == 1); + + options->AccountingRule = ACCT_OUT; + + accounting_add_bytes(100, 10, 1); + fake_time += 1; + consider_hibernation(fake_time); + tor_assert(we_are_hibernating() == 0); + + accounting_add_bytes(0, 90, 1); + fake_time += 1; + consider_hibernation(fake_time); + tor_assert(we_are_hibernating() == 1); + + options->AccountingMax = 300; + options->AccountingRule = ACCT_IN; + + accounting_add_bytes(10, 100, 1); + fake_time += 1; + consider_hibernation(fake_time); + tor_assert(we_are_hibernating() == 0); + + accounting_add_bytes(90, 0, 1); + fake_time += 1; + consider_hibernation(fake_time); + tor_assert(we_are_hibernating() == 1); + goto done; done: NS_UNMOCK(get_or_state); diff --git a/src/test/test_addr.c b/src/test/test_addr.c index 2c25c1ef7d..85130db4fe 100644 --- a/src/test/test_addr.c +++ b/src/test/test_addr.c @@ -302,6 +302,7 @@ test_addr_ip6_helpers(void *arg) //test_ntop6_reduces("0:0:0:0:0:0:c0a8:0101", "::192.168.1.1"); test_ntop6_reduces("0:0:0:0:0:ffff:c0a8:0101", "::ffff:192.168.1.1"); + test_ntop6_reduces("0:0:0:0:0:0:c0a8:0101", "::192.168.1.1"); test_ntop6_reduces("002:0:0000:0:3::4", "2::3:0:0:4"); test_ntop6_reduces("0:0::1:0:3", "::1:0:3"); test_ntop6_reduces("008:0::0", "8::"); diff --git a/src/test/test_address.c b/src/test/test_address.c index 3e73c3e27b..3ddf2476e3 100644 --- a/src/test/test_address.c +++ b/src/test/test_address.c @@ -119,6 +119,21 @@ smartlist_contains_internal_tor_addr(smartlist_t *smartlist) } /** Return 1 iff <b>smartlist</b> contains a tor_addr_t structure + * that is NULL or the null tor_addr_t. Otherwise, return 0. + */ +static int +smartlist_contains_null_tor_addr(smartlist_t *smartlist) +{ + SMARTLIST_FOREACH_BEGIN(smartlist, tor_addr_t *, tor_addr) { + if (tor_addr == NULL || tor_addr_is_null(tor_addr)) { + return 1; + } + } SMARTLIST_FOREACH_END(tor_addr); + + return 0; +} + +/** Return 1 iff <b>smartlist</b> contains a tor_addr_t structure * that is an IPv4 address. Otherwise, return 0. */ static int @@ -205,7 +220,7 @@ test_address_ifaddrs_to_smartlist(void *arg) ifa_ipv6->ifa_dstaddr = NULL; ifa_ipv6->ifa_data = NULL; - smartlist = ifaddrs_to_smartlist(ifa); + smartlist = ifaddrs_to_smartlist(ifa, AF_UNSPEC); tt_assert(smartlist); tt_assert(smartlist_len(smartlist) == 3); @@ -266,10 +281,20 @@ test_address_get_if_addrs_ifaddrs(void *arg) (void)arg; - results = get_interface_addresses_ifaddrs(LOG_ERR); + results = get_interface_addresses_ifaddrs(LOG_ERR, AF_UNSPEC); - tt_int_op(smartlist_len(results),>=,1); - tt_assert(smartlist_contains_localhost_tor_addr(results)); + tt_assert(results); + /* Some FreeBSD jails don't have localhost IP address. Instead, they only + * have the address assigned to the jail (whatever that may be). + * And a jail without a network connection might not have any addresses at + * all. */ + tt_assert(!smartlist_contains_null_tor_addr(results)); + + /* If there are addresses, they must be IPv4 or IPv6 */ + if (smartlist_len(results) > 0) { + tt_assert(smartlist_contains_ipv4_tor_addr(results) + || smartlist_contains_ipv6_tor_addr(results)); + } done: SMARTLIST_FOREACH(results, tor_addr_t *, t, tor_free(t)); @@ -289,10 +314,17 @@ test_address_get_if_addrs_win32(void *arg) (void)arg; - results = get_interface_addresses_win32(LOG_ERR); + results = get_interface_addresses_win32(LOG_ERR, AF_UNSPEC); tt_int_op(smartlist_len(results),>=,1); tt_assert(smartlist_contains_localhost_tor_addr(results)); + tt_assert(!smartlist_contains_null_tor_addr(results)); + + /* If there are addresses, they must be IPv4 or IPv6 */ + if (smartlist_len(results) > 0) { + tt_assert(smartlist_contains_ipv4_tor_addr(results) + || smartlist_contains_ipv6_tor_addr(results)); + } done: SMARTLIST_FOREACH(results, tor_addr_t *, t, tor_free(t)); @@ -479,12 +511,26 @@ test_address_get_if_addrs_ioctl(void *arg) (void)arg; - result = get_interface_addresses_ioctl(LOG_ERR); + result = get_interface_addresses_ioctl(LOG_ERR, AF_INET); + /* On an IPv6-only system, this will fail and return NULL tt_assert(result); - tt_int_op(smartlist_len(result),>=,1); + */ + + /* Some FreeBSD jails don't have localhost IP address. Instead, they only + * have the address assigned to the jail (whatever that may be). + * And a jail without a network connection might not have any addresses at + * all. */ + if (result) { + tt_assert(!smartlist_contains_null_tor_addr(result)); - tt_assert(smartlist_contains_localhost_tor_addr(result)); + /* If there are addresses, they must be IPv4 or IPv6. + * (AIX supports IPv6 from SIOCGIFCONF.) */ + if (smartlist_len(result) > 0) { + tt_assert(smartlist_contains_ipv4_tor_addr(result) + || smartlist_contains_ipv6_tor_addr(result)); + } + } done: if (result) { @@ -696,12 +742,13 @@ test_address_get_if_addrs_list_internal(void *arg) tt_assert(!smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_multicast_tor_addr(results)); /* The list may or may not contain internal addresses */ + tt_assert(!smartlist_contains_null_tor_addr(results)); - /* Allow unit tests to pass on IPv6-only machines */ + /* if there are any addresses, they must be IPv4 */ if (smartlist_len(results) > 0) { - tt_assert(smartlist_contains_ipv4_tor_addr(results) - || smartlist_contains_ipv6_tor_addr(results)); + tt_assert(smartlist_contains_ipv4_tor_addr(results)); } + tt_assert(!smartlist_contains_ipv6_tor_addr(results)); done: free_interface_address_list(results); @@ -724,6 +771,7 @@ test_address_get_if_addrs_list_no_internal(void *arg) tt_assert(!smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_multicast_tor_addr(results)); tt_assert(!smartlist_contains_internal_tor_addr(results)); + tt_assert(!smartlist_contains_null_tor_addr(results)); /* if there are any addresses, they must be IPv4 */ if (smartlist_len(results) > 0) { @@ -752,6 +800,7 @@ test_address_get_if_addrs6_list_internal(void *arg) tt_assert(!smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_multicast_tor_addr(results)); /* The list may or may not contain internal addresses */ + tt_assert(!smartlist_contains_null_tor_addr(results)); /* if there are any addresses, they must be IPv6 */ tt_assert(!smartlist_contains_ipv4_tor_addr(results)); @@ -780,7 +829,9 @@ test_address_get_if_addrs6_list_no_internal(void *arg) tt_assert(!smartlist_contains_localhost_tor_addr(results)); tt_assert(!smartlist_contains_multicast_tor_addr(results)); tt_assert(!smartlist_contains_internal_tor_addr(results)); + tt_assert(!smartlist_contains_null_tor_addr(results)); + /* if there are any addresses, they must be IPv6 */ tt_assert(!smartlist_contains_ipv4_tor_addr(results)); if (smartlist_len(results) > 0) { tt_assert(smartlist_contains_ipv6_tor_addr(results)); @@ -794,9 +845,10 @@ test_address_get_if_addrs6_list_no_internal(void *arg) static int called_get_interface_addresses_raw = 0; static smartlist_t * -mock_get_interface_addresses_raw_fail(int severity) +mock_get_interface_addresses_raw_fail(int severity, sa_family_t family) { (void)severity; + (void)family; called_get_interface_addresses_raw++; return smartlist_new(); @@ -848,7 +900,7 @@ test_address_get_if_addrs_internal_fail(void *arg) rv = get_interface_address(LOG_ERR, &ipv4h_addr); tt_assert(rv == -1); -done: + done: UNMOCK(get_interface_addresses_raw); UNMOCK(get_interface_address6_via_udp_socket_hack); free_interface_address6_list(results1); @@ -876,7 +928,7 @@ test_address_get_if_addrs_no_internal_fail(void *arg) tt_assert(results2 != NULL); tt_int_op(smartlist_len(results2),==,0); -done: + done: UNMOCK(get_interface_addresses_raw); UNMOCK(get_interface_address6_via_udp_socket_hack); free_interface_address6_list(results1); @@ -935,6 +987,118 @@ test_address_get_if_addrs6(void *arg) return; } +static void +test_address_tor_addr_to_in6(void *ignored) +{ + (void)ignored; + tor_addr_t *a = tor_malloc_zero(sizeof(tor_addr_t)); + const struct in6_addr *res; + uint8_t expected[16] = {42, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15}; + + a->family = AF_INET; + res = tor_addr_to_in6(a); + tt_assert(!res); + + a->family = AF_INET6; + memcpy(a->addr.in6_addr.s6_addr, expected, 16); + res = tor_addr_to_in6(a); + tt_assert(res); + tt_mem_op(res->s6_addr, OP_EQ, expected, 16); + + done: + tor_free(a); +} + +static void +test_address_tor_addr_to_in(void *ignored) +{ + (void)ignored; + tor_addr_t *a = tor_malloc_zero(sizeof(tor_addr_t)); + const struct in_addr *res; + + a->family = AF_INET6; + res = tor_addr_to_in(a); + tt_assert(!res); + + a->family = AF_INET; + a->addr.in_addr.s_addr = 44; + res = tor_addr_to_in(a); + tt_assert(res); + tt_int_op(res->s_addr, OP_EQ, 44); + + done: + tor_free(a); +} + +static void +test_address_tor_addr_to_ipv4n(void *ignored) +{ + (void)ignored; + tor_addr_t *a = tor_malloc_zero(sizeof(tor_addr_t)); + uint32_t res; + + a->family = AF_INET6; + res = tor_addr_to_ipv4n(a); + tt_assert(!res); + + a->family = AF_INET; + a->addr.in_addr.s_addr = 43; + res = tor_addr_to_ipv4n(a); + tt_assert(res); + tt_int_op(res, OP_EQ, 43); + + done: + tor_free(a); +} + +static void +test_address_tor_addr_to_mapped_ipv4h(void *ignored) +{ + (void)ignored; + tor_addr_t *a = tor_malloc_zero(sizeof(tor_addr_t)); + uint32_t res; + uint8_t toset[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 42}; + + a->family = AF_INET; + res = tor_addr_to_mapped_ipv4h(a); + tt_assert(!res); + + a->family = AF_INET6; + + memcpy(a->addr.in6_addr.s6_addr, toset, 16); + res = tor_addr_to_mapped_ipv4h(a); + tt_assert(res); + tt_int_op(res, OP_EQ, 42); + + done: + tor_free(a); +} + +static void +test_address_tor_addr_eq_ipv4h(void *ignored) +{ + (void)ignored; + tor_addr_t *a = tor_malloc_zero(sizeof(tor_addr_t)); + int res; + + a->family = AF_INET6; + res = tor_addr_eq_ipv4h(a, 42); + tt_assert(!res); + + a->family = AF_INET; + a->addr.in_addr.s_addr = 52; + res = tor_addr_eq_ipv4h(a, 42); + tt_assert(!res); + + a->addr.in_addr.s_addr = 52; + res = tor_addr_eq_ipv4h(a, ntohl(52)); + tt_assert(res); + + done: + tor_free(a); +} + #define ADDRESS_TEST(name, flags) \ { #name, test_address_ ## name, flags, NULL, NULL } @@ -961,6 +1125,11 @@ struct testcase_t address_tests[] = { ADDRESS_TEST(get_if_addrs_ioctl, TT_FORK), ADDRESS_TEST(ifreq_to_smartlist, 0), #endif + ADDRESS_TEST(tor_addr_to_in6, 0), + ADDRESS_TEST(tor_addr_to_in, 0), + ADDRESS_TEST(tor_addr_to_ipv4n, 0), + ADDRESS_TEST(tor_addr_to_mapped_ipv4h, 0), + ADDRESS_TEST(tor_addr_eq_ipv4h, 0), END_OF_TESTCASES }; diff --git a/src/test/test_bt_cl.c b/src/test/test_bt_cl.c index dabaee6e0a..c43143ffe9 100644 --- a/src/test/test_bt_cl.c +++ b/src/test/test_bt_cl.c @@ -119,6 +119,7 @@ main(int argc, char **argv) printf("%d\n", we_weave(2)); clean_up_backtrace_handler(); + logs_free_all(); return 0; } diff --git a/src/test/test_channeltls.c b/src/test/test_channeltls.c index 016e504ab3..dff1dde87e 100644 --- a/src/test/test_channeltls.c +++ b/src/test/test_channeltls.c @@ -123,7 +123,7 @@ test_channeltls_num_bytes_queued(void *arg) /* * Next, we have to test ch->num_bytes_queued, which is * channel_tls_num_bytes_queued_method. We can't mock - * connection_get_outbuf_len() directly because it's static INLINE + * connection_get_outbuf_len() directly because it's static inline * in connection.h, but we can mock buf_datalen(). Note that * if bufferevents ever work, this will break with them enabled. */ diff --git a/src/test/test_compat_libevent.c b/src/test/test_compat_libevent.c new file mode 100644 index 0000000000..96502df308 --- /dev/null +++ b/src/test/test_compat_libevent.c @@ -0,0 +1,238 @@ +/* Copyright (c) 2010-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define COMPAT_LIBEVENT_PRIVATE +#include "orconfig.h" +#include "or.h" + +#include "test.h" + +#include "compat_libevent.h" + +#ifdef HAVE_EVENT2_EVENT_H +#include <event2/event.h> +#include <event2/thread.h> +#ifdef USE_BUFFEREVENTS +#include <event2/bufferevent.h> +#endif +#else +#include <event.h> +#endif + +#include "log_test_helpers.h" + +#define NS_MODULE compat_libevent + +static void +test_compat_libevent_logging_callback(void *ignored) +{ + (void)ignored; + int previous_log = setup_capture_of_logs(LOG_DEBUG); + + libevent_logging_callback(_EVENT_LOG_DEBUG, "hello world"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Message from libevent: hello world\n"); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_DEBUG); + + mock_clean_saved_logs(); + libevent_logging_callback(_EVENT_LOG_MSG, "hello world another time"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Message from libevent: hello world another time\n"); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + + mock_clean_saved_logs(); + libevent_logging_callback(_EVENT_LOG_WARN, "hello world a third time"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Warning from libevent: hello world a third time\n"); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_WARN); + + mock_clean_saved_logs(); + libevent_logging_callback(_EVENT_LOG_ERR, "hello world a fourth time"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Error from libevent: hello world a fourth time\n"); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_ERR); + + mock_clean_saved_logs(); + libevent_logging_callback(42, "hello world a fifth time"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Message [42] from libevent: hello world a fifth time\n"); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_WARN); + + mock_clean_saved_logs(); + libevent_logging_callback(_EVENT_LOG_DEBUG, + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + ); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "Message from libevent: " + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789" + "012345678901234567890123456789\n"); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_DEBUG); + + mock_clean_saved_logs(); + libevent_logging_callback(42, "xxx\n"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "Message [42] from libevent: xxx\n"); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_WARN); + + suppress_libevent_log_msg("something"); + mock_clean_saved_logs(); + libevent_logging_callback(_EVENT_LOG_MSG, "hello there"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Message from libevent: hello there\n"); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + + mock_clean_saved_logs(); + libevent_logging_callback(_EVENT_LOG_MSG, "hello there something else"); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + + // No way of verifying the result of this, it seems =/ + configure_libevent_logging(); + + done: + suppress_libevent_log_msg(NULL); + teardown_capture_of_logs(previous_log); +} + +static void +test_compat_libevent_le_versions_compatibility(void *ignored) +{ + (void)ignored; + int res; + + res = le_versions_compatibility(LE_OTHER); + tt_int_op(res, OP_EQ, 0); + + res = le_versions_compatibility(V_OLD(0,9,'c')); + tt_int_op(res, OP_EQ, 1); + + res = le_versions_compatibility(V(1,3,98)); + tt_int_op(res, OP_EQ, 2); + + res = le_versions_compatibility(V(1,4,98)); + tt_int_op(res, OP_EQ, 3); + + res = le_versions_compatibility(V(1,5,0)); + tt_int_op(res, OP_EQ, 4); + + res = le_versions_compatibility(V(2,0,0)); + tt_int_op(res, OP_EQ, 4); + + res = le_versions_compatibility(V(2,0,2)); + tt_int_op(res, OP_EQ, 5); + + done: + (void)0; +} + +static void +test_compat_libevent_tor_decode_libevent_version(void *ignored) +{ + (void)ignored; + le_version_t res; + + res = tor_decode_libevent_version("SOMETHING WRONG"); + tt_int_op(res, OP_EQ, LE_OTHER); + + res = tor_decode_libevent_version("1.4.11"); + tt_int_op(res, OP_EQ, V(1,4,11)); + + res = tor_decode_libevent_version("1.4.12b-stable"); + tt_int_op(res, OP_EQ, V(1,4,12)); + + res = tor_decode_libevent_version("1.4.17b_stable"); + tt_int_op(res, OP_EQ, V(1,4,17)); + + res = tor_decode_libevent_version("1.4.12!stable"); + tt_int_op(res, OP_EQ, LE_OTHER); + + res = tor_decode_libevent_version("1.4.12b!stable"); + tt_int_op(res, OP_EQ, LE_OTHER); + + res = tor_decode_libevent_version("1.4.13-"); + tt_int_op(res, OP_EQ, V(1,4,13)); + + res = tor_decode_libevent_version("1.4.14_"); + tt_int_op(res, OP_EQ, V(1,4,14)); + + res = tor_decode_libevent_version("1.4.15c-"); + tt_int_op(res, OP_EQ, V(1,4,15)); + + res = tor_decode_libevent_version("1.4.16c_"); + tt_int_op(res, OP_EQ, V(1,4,16)); + + res = tor_decode_libevent_version("1.4.17-s"); + tt_int_op(res, OP_EQ, V(1,4,17)); + + res = tor_decode_libevent_version("1.5"); + tt_int_op(res, OP_EQ, V(1,5,0)); + + res = tor_decode_libevent_version("1.2"); + tt_int_op(res, OP_EQ, V(1,2,0)); + + res = tor_decode_libevent_version("1.2-"); + tt_int_op(res, OP_EQ, LE_OTHER); + + res = tor_decode_libevent_version("1.6e"); + tt_int_op(res, OP_EQ, V_OLD(1,6,'e')); + + done: + (void)0; +} + +#if defined(LIBEVENT_VERSION) +#define HEADER_VERSION LIBEVENT_VERSION +#elif defined(_EVENT_VERSION) +#define HEADER_VERSION _EVENT_VERSION +#endif + +static void +test_compat_libevent_header_version(void *ignored) +{ + (void)ignored; + const char *res; + + res = tor_libevent_get_header_version_str(); + tt_str_op(res, OP_EQ, HEADER_VERSION); + + done: + (void)0; +} + +struct testcase_t compat_libevent_tests[] = { + { "logging_callback", test_compat_libevent_logging_callback, + TT_FORK, NULL, NULL }, + { "le_versions_compatibility", + test_compat_libevent_le_versions_compatibility, 0, NULL, NULL }, + { "tor_decode_libevent_version", + test_compat_libevent_tor_decode_libevent_version, 0, NULL, NULL }, + { "header_version", test_compat_libevent_header_version, 0, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_config.c b/src/test/test_config.c index 28e9fa0f32..097fe15adf 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -18,6 +18,9 @@ #include "entrynodes.h" #include "transports.h" #include "routerlist.h" +#include "networkstatus.h" +#include "router.h" +#include "dirserv.h" static void test_config_addressmap(void *arg) @@ -1444,6 +1447,176 @@ test_config_resolve_my_address(void *arg) UNMOCK(tor_gethostname); } +static void +test_config_adding_trusted_dir_server(void *arg) +{ + (void)arg; + + const char digest[DIGEST_LEN] = ""; + dir_server_t *ds = NULL; + tor_addr_port_t ipv6; + int rv = -1; + + clear_dir_servers(); + routerlist_free_all(); + + /* create a trusted ds without an IPv6 address and port */ + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, NULL, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + dir_server_add(ds); + tt_assert(get_n_authorities(V3_DIRINFO) == 1); + tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 1); + + /* create a trusted ds with an IPv6 address and port */ + rv = tor_addr_port_parse(LOG_WARN, "[::1]:9061", &ipv6.addr, &ipv6.port, -1); + tt_assert(rv == 0); + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, &ipv6, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + dir_server_add(ds); + tt_assert(get_n_authorities(V3_DIRINFO) == 2); + tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 2); + + done: + clear_dir_servers(); + routerlist_free_all(); +} + +static void +test_config_adding_fallback_dir_server(void *arg) +{ + (void)arg; + + const char digest[DIGEST_LEN] = ""; + dir_server_t *ds = NULL; + tor_addr_t ipv4; + tor_addr_port_t ipv6; + int rv = -1; + + clear_dir_servers(); + routerlist_free_all(); + + rv = tor_addr_parse(&ipv4, "127.0.0.1"); + tt_assert(rv == AF_INET); + + /* create a trusted ds without an IPv6 address and port */ + ds = fallback_dir_server_new(&ipv4, 9059, 9060, NULL, digest, 1.0); + tt_assert(ds); + dir_server_add(ds); + tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 1); + + /* create a trusted ds with an IPv6 address and port */ + rv = tor_addr_port_parse(LOG_WARN, "[::1]:9061", &ipv6.addr, &ipv6.port, -1); + tt_assert(rv == 0); + ds = fallback_dir_server_new(&ipv4, 9059, 9060, &ipv6, digest, 1.0); + tt_assert(ds); + dir_server_add(ds); + tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 2); + + done: + clear_dir_servers(); + routerlist_free_all(); +} + +/* No secrets here: + * v3ident is `echo "onion" | shasum | cut -d" " -f1 | tr "a-f" "A-F"` + * fingerprint is `echo "unionem" | shasum | cut -d" " -f1 | tr "a-f" "A-F"` + * with added spaces + */ +#define TEST_DIR_AUTH_LINE_START \ + "foobar orport=12345 " \ + "v3ident=14C131DFC5C6F93646BE72FA1401C02A8DF2E8B4 " +#define TEST_DIR_AUTH_LINE_END \ + "1.2.3.4:54321 " \ + "FDB2 FBD2 AAA5 25FA 2999 E617 5091 5A32 C777 3B17" +#define TEST_DIR_AUTH_IPV6_FLAG \ + "ipv6=[feed::beef]:9 " + +static void +test_config_parsing_trusted_dir_server(void *arg) +{ + (void)arg; + int rv = -1; + + /* parse a trusted dir server without an IPv6 address and port */ + rv = parse_dir_authority_line(TEST_DIR_AUTH_LINE_START + TEST_DIR_AUTH_LINE_END, + V3_DIRINFO, 1); + tt_assert(rv == 0); + + /* parse a trusted dir server with an IPv6 address and port */ + rv = parse_dir_authority_line(TEST_DIR_AUTH_LINE_START + TEST_DIR_AUTH_IPV6_FLAG + TEST_DIR_AUTH_LINE_END, + V3_DIRINFO, 1); + tt_assert(rv == 0); + + /* Since we are only validating, there is no cleanup. */ + done: + ; +} + +#undef TEST_DIR_AUTH_LINE_START +#undef TEST_DIR_AUTH_LINE_END +#undef TEST_DIR_AUTH_IPV6_FLAG + +/* No secrets here: + * id is `echo "syn-propanethial-S-oxide" | shasum | cut -d" " -f1` + */ +#define TEST_DIR_FALLBACK_LINE \ + "1.2.3.4:54321 orport=12345 " \ + "id=50e643986f31ea1235bcc1af17a1c5c5cfc0ee54 " +#define TEST_DIR_FALLBACK_IPV6_FLAG \ + "ipv6=[2015:c0de::deed]:9" + +static void +test_config_parsing_fallback_dir_server(void *arg) +{ + (void)arg; + int rv = -1; + + /* parse a trusted dir server without an IPv6 address and port */ + rv = parse_dir_fallback_line(TEST_DIR_FALLBACK_LINE, 1); + tt_assert(rv == 0); + + /* parse a trusted dir server with an IPv6 address and port */ + rv = parse_dir_fallback_line(TEST_DIR_FALLBACK_LINE + TEST_DIR_FALLBACK_IPV6_FLAG, + 1); + tt_assert(rv == 0); + + /* Since we are only validating, there is no cleanup. */ + done: + ; +} + +#undef TEST_DIR_FALLBACK_LINE +#undef TEST_DIR_FALLBACK_IPV6_FLAG + +static void +test_config_adding_default_trusted_dir_servers(void *arg) +{ + (void)arg; + + clear_dir_servers(); + routerlist_free_all(); + + /* Assume we only have one bridge authority */ + add_default_trusted_dir_authorities(BRIDGE_DIRINFO); + tt_assert(get_n_authorities(BRIDGE_DIRINFO) == 1); + tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 1); + + /* Assume we have nine V3 authorities */ + add_default_trusted_dir_authorities(V3_DIRINFO); + tt_assert(get_n_authorities(V3_DIRINFO) == 9); + tt_assert(smartlist_len(router_get_fallback_dir_servers()) == 10); + + done: + clear_dir_servers(); + routerlist_free_all(); +} + static int n_add_default_fallback_dir_servers_known_default = 0; /** @@ -1471,13 +1644,14 @@ add_default_fallback_dir_servers_known_default(void) n_add_default_fallback_dir_servers_known_default++; } +/* Test all the different combinations of adding dir servers */ static void test_config_adding_dir_servers(void *arg) { (void)arg; /* allocate options */ - or_options_t *options = tor_malloc(sizeof(or_options_t)); + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); /* Allocate and populate configuration lines: * @@ -1486,8 +1660,7 @@ test_config_adding_dir_servers(void *arg) * Zeroing the structure has the same effect as initialising to: * { NULL, NULL, NULL, CONFIG_LINE_NORMAL, 0}; */ - config_line_t *test_dir_authority = tor_malloc(sizeof(config_line_t)); - memset(test_dir_authority, 0, sizeof(config_line_t)); + config_line_t *test_dir_authority = tor_malloc_zero(sizeof(config_line_t)); test_dir_authority->key = tor_strdup("DirAuthority"); test_dir_authority->value = tor_strdup( "D0 orport=9000 " @@ -1495,16 +1668,16 @@ test_config_adding_dir_servers(void *arg) "127.0.0.1:60090 0123 4567 8901 2345 6789 0123 4567 8901 2345 6789" ); - config_line_t *test_alt_bridge_authority = tor_malloc(sizeof(config_line_t)); - memset(test_alt_bridge_authority, 0, sizeof(config_line_t)); + config_line_t *test_alt_bridge_authority = tor_malloc_zero( + sizeof(config_line_t)); test_alt_bridge_authority->key = tor_strdup("AlternateBridgeAuthority"); test_alt_bridge_authority->value = tor_strdup( "B1 orport=9001 bridge " "127.0.0.1:60091 1123 4567 8901 2345 6789 0123 4567 8901 2345 6789" ); - config_line_t *test_alt_dir_authority = tor_malloc(sizeof(config_line_t)); - memset(test_alt_dir_authority, 0, sizeof(config_line_t)); + config_line_t *test_alt_dir_authority = tor_malloc_zero( + sizeof(config_line_t)); test_alt_dir_authority->key = tor_strdup("AlternateDirAuthority"); test_alt_dir_authority->value = tor_strdup( "A2 orport=9002 " @@ -1513,23 +1686,23 @@ test_config_adding_dir_servers(void *arg) ); /* Use the format specified in the manual page */ - config_line_t *test_fallback_directory = tor_malloc(sizeof(config_line_t)); - memset(test_fallback_directory, 0, sizeof(config_line_t)); + config_line_t *test_fallback_directory = tor_malloc_zero( + sizeof(config_line_t)); test_fallback_directory->key = tor_strdup("FallbackDir"); test_fallback_directory->value = tor_strdup( "127.0.0.1:60093 orport=9003 id=0323456789012345678901234567890123456789" ); /* We need to know if add_default_fallback_dir_servers is called, + * whatever the size of the list in fallback_dirs.inc, * so we use a version of add_default_fallback_dir_servers that adds - * one known default fallback directory. - * There doesn't appear to be any need to test it unmocked. */ + * one known default fallback directory. */ MOCK(add_default_fallback_dir_servers, add_default_fallback_dir_servers_known_default); /* There are 16 different cases, covering each combination of set/NULL for: * DirAuthorities, AlternateBridgeAuthority, AlternateDirAuthority & - * FallbackDir. + * FallbackDir. (We always set UseDefaultFallbackDirs to 1.) * But validate_dir_servers() ensures that: * "You cannot set both DirAuthority and Alternate*Authority." * This reduces the number of cases to 10. @@ -1543,8 +1716,6 @@ test_config_adding_dir_servers(void *arg) * The valid cases are cases 0-9 counting using this method, as every case * greater than or equal to 10 = 1010 is invalid. * - * After #15642 - Disable default fallback dirs when any custom dirs set - * * 1. Outcome: Use Set Directory Authorities * - No Default Authorities * - Use AlternateBridgeAuthority, AlternateDirAuthority, and FallbackDir @@ -1581,20 +1752,6 @@ test_config_adding_dir_servers(void *arg) * Cases expected to yield this outcome: * 0 (DirAuthorities, AlternateBridgeAuthority, AlternateDirAuthority * and FallbackDir are all NULL) - * - * Before #15642 but after #13163 - Stop using default authorities when both - * Alternate Dir and Bridge Authority are set - * (#13163 was committed in 0.2.6 as c1dd43d823c7) - * - * The behaviour is different in the following cases - * where FallbackDir is NULL: - * 2, 6, 8 - * - * In these cases, the Default Fallback Directories are applied, even when - * DirAuthorities or AlternateDirAuthority are set. - * - * However, as the list of default fallback directories is currently empty, - * this change doesn't modify any user-visible behaviour. */ /* @@ -1628,6 +1785,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = NULL; options->AlternateDirAuthority = NULL; options->FallbackDir = NULL; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -1637,6 +1795,9 @@ test_config_adding_dir_servers(void *arg) /* we must have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* fallback_dir_servers */ const smartlist_t *fallback_servers = router_get_fallback_dir_servers(); @@ -1669,7 +1830,10 @@ test_config_adding_dir_servers(void *arg) n_default_fallback_dir = (smartlist_len(fallback_servers) - n_default_alt_bridge_authority - n_default_alt_dir_authority); - /* If we have a negative count, something has gone really wrong */ + /* If we have a negative count, something has gone really wrong, + * or some authorities aren't being added as fallback directories. + * (networkstatus_consensus_can_use_extra_fallbacks depends on all + * authorities being fallback directories.) */ tt_assert(n_default_fallback_dir >= 0); } } @@ -1703,6 +1867,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = NULL; options->AlternateDirAuthority = NULL; options->FallbackDir = test_fallback_directory; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -1712,6 +1877,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -1840,6 +2008,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = NULL; options->AlternateDirAuthority = NULL; options->FallbackDir = NULL; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -1849,6 +2018,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we just have the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -1977,6 +2149,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = test_alt_bridge_authority; options->AlternateDirAuthority = test_alt_dir_authority; options->FallbackDir = test_fallback_directory; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -1986,6 +2159,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2115,6 +2291,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = test_alt_bridge_authority; options->AlternateDirAuthority = test_alt_dir_authority; options->FallbackDir = NULL; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -2124,6 +2301,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2263,6 +2443,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = test_alt_bridge_authority; options->AlternateDirAuthority = NULL; options->FallbackDir = test_fallback_directory; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -2272,6 +2453,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2413,6 +2597,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = test_alt_bridge_authority; options->AlternateDirAuthority = NULL; options->FallbackDir = NULL; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -2422,6 +2607,9 @@ test_config_adding_dir_servers(void *arg) /* we must have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2572,6 +2760,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = NULL; options->AlternateDirAuthority = test_alt_dir_authority; options->FallbackDir = test_fallback_directory; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -2581,6 +2770,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2725,6 +2917,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = NULL; options->AlternateDirAuthority = test_alt_dir_authority; options->FallbackDir = NULL; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -2734,6 +2927,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we just have the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 0); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -2887,6 +3083,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = NULL; options->AlternateDirAuthority = NULL; options->FallbackDir = test_fallback_directory; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -2896,6 +3093,9 @@ test_config_adding_dir_servers(void *arg) /* we must not have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 0); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -3046,6 +3246,7 @@ test_config_adding_dir_servers(void *arg) options->AlternateBridgeAuthority = NULL; options->AlternateDirAuthority = NULL; options->FallbackDir = NULL; + options->UseDefaultFallbackDirs = 1; /* parse options - ensure we always update by passing NULL old_options */ consider_adding_dir_servers(options, NULL); @@ -3055,6 +3256,9 @@ test_config_adding_dir_servers(void *arg) /* we must have added the default fallback dirs */ tt_assert(n_add_default_fallback_dir_servers_known_default == 1); + /* we have more fallbacks than just the authorities */ + tt_assert(networkstatus_consensus_can_use_extra_fallbacks(options) == 1); + { /* trusted_dir_servers */ const smartlist_t *dir_servers = router_get_trusted_dir_servers(); @@ -3209,11 +3413,292 @@ test_config_adding_dir_servers(void *arg) UNMOCK(add_default_fallback_dir_servers); } +static void +test_config_default_dir_servers(void *arg) +{ + or_options_t *opts = NULL; + (void)arg; + int trusted_count = 0; + int fallback_count = 0; + + /* new set of options should stop fallback parsing */ + opts = tor_malloc_zero(sizeof(or_options_t)); + opts->UseDefaultFallbackDirs = 0; + /* set old_options to NULL to force dir update */ + consider_adding_dir_servers(opts, NULL); + trusted_count = smartlist_len(router_get_trusted_dir_servers()); + fallback_count = smartlist_len(router_get_fallback_dir_servers()); + or_options_free(opts); + opts = NULL; + + /* assume a release will never go out with less than 7 authorities */ + tt_assert(trusted_count >= 7); + /* if we disable the default fallbacks, there must not be any extra */ + tt_assert(fallback_count == trusted_count); + + opts = tor_malloc_zero(sizeof(or_options_t)); + opts->UseDefaultFallbackDirs = 1; + consider_adding_dir_servers(opts, opts); + trusted_count = smartlist_len(router_get_trusted_dir_servers()); + fallback_count = smartlist_len(router_get_fallback_dir_servers()); + or_options_free(opts); + opts = NULL; + + /* assume a release will never go out with less than 7 authorities */ + tt_assert(trusted_count >= 7); + /* XX/teor - allow for default fallbacks to be added without breaking + * the unit tests. Set a minimum fallback count once the list is stable. */ + tt_assert(fallback_count >= trusted_count); + + done: + or_options_free(opts); +} + +static int mock_router_pick_published_address_result = 0; + +static int +mock_router_pick_published_address(const or_options_t *options, uint32_t *addr) +{ + (void)options; + (void)addr; + return mock_router_pick_published_address_result; +} + +static int mock_router_my_exit_policy_is_reject_star_result = 0; + +static int +mock_router_my_exit_policy_is_reject_star(void) +{ + return mock_router_my_exit_policy_is_reject_star_result; +} + +static int mock_advertised_server_mode_result = 0; + +static int +mock_advertised_server_mode(void) +{ + return mock_advertised_server_mode_result; +} + +static routerinfo_t *mock_router_get_my_routerinfo_result = NULL; + +static const routerinfo_t * +mock_router_get_my_routerinfo(void) +{ + return mock_router_get_my_routerinfo_result; +} + +static void +test_config_directory_fetch(void *arg) +{ + (void)arg; + + /* Test Setup */ + or_options_t *options = tor_malloc_zero(sizeof(or_options_t)); + routerinfo_t routerinfo; + memset(&routerinfo, 0, sizeof(routerinfo)); + mock_router_pick_published_address_result = -1; + mock_router_my_exit_policy_is_reject_star_result = 1; + mock_advertised_server_mode_result = 0; + mock_router_get_my_routerinfo_result = NULL; + MOCK(router_pick_published_address, mock_router_pick_published_address); + MOCK(router_my_exit_policy_is_reject_star, + mock_router_my_exit_policy_is_reject_star); + MOCK(advertised_server_mode, mock_advertised_server_mode); + MOCK(router_get_my_routerinfo, mock_router_get_my_routerinfo); + + /* Clients can use multiple directory mirrors for bootstrap */ + memset(options, 0, sizeof(or_options_t)); + options->ClientOnly = 1; + tt_assert(server_mode(options) == 0); + tt_assert(public_server_mode(options) == 0); + tt_assert(directory_fetches_from_authorities(options) == 0); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 1); + + /* Bridge Clients can use multiple directory mirrors for bootstrap */ + memset(options, 0, sizeof(or_options_t)); + options->UseBridges = 1; + tt_assert(server_mode(options) == 0); + tt_assert(public_server_mode(options) == 0); + tt_assert(directory_fetches_from_authorities(options) == 0); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 1); + + /* Bridge Relays (Bridges) must act like clients, and use multiple + * directory mirrors for bootstrap */ + memset(options, 0, sizeof(or_options_t)); + options->BridgeRelay = 1; + options->ORPort_set = 1; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 0); + tt_assert(directory_fetches_from_authorities(options) == 0); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 1); + + /* Clients set to FetchDirInfoEarly must fetch it from the authorities, + * but can use multiple authorities for bootstrap */ + memset(options, 0, sizeof(or_options_t)); + options->FetchDirInfoEarly = 1; + tt_assert(server_mode(options) == 0); + tt_assert(public_server_mode(options) == 0); + tt_assert(directory_fetches_from_authorities(options) == 1); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 1); + + /* OR servers only fetch the consensus from the authorities when they don't + * know their own address, but never use multiple directories for bootstrap + */ + memset(options, 0, sizeof(or_options_t)); + options->ORPort_set = 1; + + mock_router_pick_published_address_result = -1; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 1); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + mock_router_pick_published_address_result = 0; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 0); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + /* Exit OR servers only fetch the consensus from the authorities when they + * refuse unknown exits, but never use multiple directories for bootstrap + */ + memset(options, 0, sizeof(or_options_t)); + options->ORPort_set = 1; + options->ExitRelay = 1; + mock_router_pick_published_address_result = 0; + mock_router_my_exit_policy_is_reject_star_result = 0; + mock_advertised_server_mode_result = 1; + mock_router_get_my_routerinfo_result = &routerinfo; + + routerinfo.supports_tunnelled_dir_requests = 1; + + options->RefuseUnknownExits = 1; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 1); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + options->RefuseUnknownExits = 0; + mock_router_pick_published_address_result = 0; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 0); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + /* Dir servers fetch the consensus from the authorities, unless they are not + * advertising themselves (hibernating) or have no routerinfo or are not + * advertising their dirport, and never use multiple directories for + * bootstrap. This only applies if they are also OR servers. + * (We don't care much about the behaviour of non-OR directory servers.) */ + memset(options, 0, sizeof(or_options_t)); + options->DirPort_set = 1; + options->ORPort_set = 1; + options->DirCache = 1; + mock_router_pick_published_address_result = 0; + mock_router_my_exit_policy_is_reject_star_result = 1; + + mock_advertised_server_mode_result = 1; + routerinfo.dir_port = 1; + mock_router_get_my_routerinfo_result = &routerinfo; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 1); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + mock_advertised_server_mode_result = 0; + routerinfo.dir_port = 1; + mock_router_get_my_routerinfo_result = &routerinfo; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 0); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + mock_advertised_server_mode_result = 1; + mock_router_get_my_routerinfo_result = NULL; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 0); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + mock_advertised_server_mode_result = 1; + routerinfo.dir_port = 0; + routerinfo.supports_tunnelled_dir_requests = 0; + mock_router_get_my_routerinfo_result = &routerinfo; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 0); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + mock_advertised_server_mode_result = 1; + routerinfo.dir_port = 1; + routerinfo.supports_tunnelled_dir_requests = 1; + mock_router_get_my_routerinfo_result = &routerinfo; + tt_assert(server_mode(options) == 1); + tt_assert(public_server_mode(options) == 1); + tt_assert(directory_fetches_from_authorities(options) == 1); + tt_assert(networkstatus_consensus_can_use_multiple_directories(options) + == 0); + + done: + tor_free(options); + UNMOCK(router_pick_published_address); + UNMOCK(router_get_my_routerinfo); + UNMOCK(advertised_server_mode); + UNMOCK(router_my_exit_policy_is_reject_star); +} + +static void +test_config_default_fallback_dirs(void *arg) +{ + const char *fallback[] = { +#include "../or/fallback_dirs.inc" + NULL + }; + + int n_included_fallback_dirs = 0; + int n_added_fallback_dirs = 0; + + (void)arg; + clear_dir_servers(); + + while (fallback[n_included_fallback_dirs]) + n_included_fallback_dirs++; + + add_default_fallback_dir_servers(); + + n_added_fallback_dirs = smartlist_len(router_get_fallback_dir_servers()); + + tt_assert(n_included_fallback_dirs == n_added_fallback_dirs); + + done: + clear_dir_servers(); +} + #define CONFIG_TEST(name, flags) \ { #name, test_config_ ## name, flags, NULL, NULL } struct testcase_t config_tests[] = { + CONFIG_TEST(adding_trusted_dir_server, TT_FORK), + CONFIG_TEST(adding_fallback_dir_server, TT_FORK), + CONFIG_TEST(parsing_trusted_dir_server, 0), + CONFIG_TEST(parsing_fallback_dir_server, 0), + CONFIG_TEST(adding_default_trusted_dir_servers, TT_FORK), CONFIG_TEST(adding_dir_servers, TT_FORK), + CONFIG_TEST(default_dir_servers, TT_FORK), + CONFIG_TEST(default_fallback_dirs, 0), CONFIG_TEST(resolve_my_address, TT_FORK), CONFIG_TEST(addressmap, 0), CONFIG_TEST(parse_bridge_line, 0), @@ -3222,6 +3707,7 @@ struct testcase_t config_tests[] = { CONFIG_TEST(check_or_create_data_subdir, TT_FORK), CONFIG_TEST(write_to_data_subdir, TT_FORK), CONFIG_TEST(fix_my_family, 0), + CONFIG_TEST(directory_fetch, 0), END_OF_TESTCASES }; diff --git a/src/test/test_connection.c b/src/test/test_connection.c new file mode 100644 index 0000000000..c5ef92931d --- /dev/null +++ b/src/test/test_connection.c @@ -0,0 +1,761 @@ +/* Copyright (c) 2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#define CONNECTION_PRIVATE +#define MAIN_PRIVATE + +#include "or.h" +#include "test.h" + +#include "connection.h" +#include "main.h" +#include "networkstatus.h" +#include "rendcache.h" +#include "directory.h" + +static void test_conn_lookup_addr_helper(const char *address, + int family, + tor_addr_t *addr); + +static void * test_conn_get_basic_setup(const struct testcase_t *tc); +static int test_conn_get_basic_teardown(const struct testcase_t *tc, + void *arg); + +static void * test_conn_get_rend_setup(const struct testcase_t *tc); +static int test_conn_get_rend_teardown(const struct testcase_t *tc, + void *arg); + +static void * test_conn_get_rsrc_setup(const struct testcase_t *tc); +static int test_conn_get_rsrc_teardown(const struct testcase_t *tc, + void *arg); + +/* Arbitrary choice - IPv4 Directory Connection to localhost */ +#define TEST_CONN_TYPE (CONN_TYPE_DIR) +/* We assume every machine has IPv4 localhost, is that ok? */ +#define TEST_CONN_ADDRESS "127.0.0.1" +#define TEST_CONN_PORT (12345) +#define TEST_CONN_ADDRESS_PORT "127.0.0.1:12345" +#define TEST_CONN_FAMILY (AF_INET) +#define TEST_CONN_STATE (DIR_CONN_STATE_MIN_) +#define TEST_CONN_ADDRESS_2 "127.0.0.2" + +#define TEST_CONN_BASIC_PURPOSE (DIR_PURPOSE_MIN_) + +#define TEST_CONN_REND_ADDR "cfs3rltphxxvabci" +#define TEST_CONN_REND_PURPOSE (DIR_PURPOSE_FETCH_RENDDESC_V2) +#define TEST_CONN_REND_PURPOSE_SUCCESSFUL (DIR_PURPOSE_HAS_FETCHED_RENDDESC_V2) +#define TEST_CONN_REND_TYPE_2 (CONN_TYPE_AP) +#define TEST_CONN_REND_ADDR_2 "icbavxxhptlr3sfc" + +#define TEST_CONN_RSRC (networkstatus_get_flavor_name(FLAV_MICRODESC)) +#define TEST_CONN_RSRC_PURPOSE (DIR_PURPOSE_FETCH_CONSENSUS) +#define TEST_CONN_RSRC_STATE_SUCCESSFUL (DIR_CONN_STATE_CLIENT_FINISHED) +#define TEST_CONN_RSRC_2 (networkstatus_get_flavor_name(FLAV_NS)) + +#define TEST_CONN_DL_STATE (DIR_CONN_STATE_CLIENT_SENDING) + +#define TEST_CONN_FD_INIT 50 +static int mock_connection_connect_sockaddr_called = 0; +static int fake_socket_number = TEST_CONN_FD_INIT; + +static int +mock_connection_connect_sockaddr(connection_t *conn, + const struct sockaddr *sa, + socklen_t sa_len, + const struct sockaddr *bindaddr, + socklen_t bindaddr_len, + int *socket_error) +{ + (void)sa_len; + (void)bindaddr; + (void)bindaddr_len; + + tor_assert(conn); + tor_assert(sa); + tor_assert(socket_error); + + mock_connection_connect_sockaddr_called++; + + conn->s = fake_socket_number++; + tt_assert(SOCKET_OK(conn->s)); + /* We really should call tor_libevent_initialize() here. Because we don't, + * we are relying on other parts of the code not checking if the_event_base + * (and therefore event->ev_base) is NULL. */ + tt_assert(connection_add_connecting(conn) == 0); + + done: + /* Fake "connected" status */ + return 1; +} + +static void +test_conn_lookup_addr_helper(const char *address, int family, tor_addr_t *addr) +{ + int rv = 0; + + tt_assert(addr); + + rv = tor_addr_lookup(address, family, addr); + /* XXXX - should we retry on transient failure? */ + tt_assert(rv == 0); + tt_assert(tor_addr_is_loopback(addr)); + tt_assert(tor_addr_is_v4(addr)); + + return; + + done: + tor_addr_make_null(addr, TEST_CONN_FAMILY); +} + +static void * +test_conn_get_basic_setup(const struct testcase_t *tc) +{ + connection_t *conn = NULL; + tor_addr_t addr; + int socket_err = 0; + int in_progress = 0; + (void)tc; + + MOCK(connection_connect_sockaddr, + mock_connection_connect_sockaddr); + + init_connection_lists(); + + conn = connection_new(TEST_CONN_TYPE, TEST_CONN_FAMILY); + tt_assert(conn); + + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr); + tt_assert(!tor_addr_is_null(&addr)); + + /* XXXX - connection_connect doesn't set these, should it? */ + tor_addr_copy_tight(&conn->addr, &addr); + conn->port = TEST_CONN_PORT; + mock_connection_connect_sockaddr_called = 0; + in_progress = connection_connect(conn, TEST_CONN_ADDRESS_PORT, &addr, + TEST_CONN_PORT, &socket_err); + tt_assert(mock_connection_connect_sockaddr_called == 1); + tt_assert(!socket_err); + tt_assert(in_progress == 0 || in_progress == 1); + + /* fake some of the attributes so the connection looks OK */ + conn->state = TEST_CONN_STATE; + conn->purpose = TEST_CONN_BASIC_PURPOSE; + assert_connection_ok(conn, time(NULL)); + + UNMOCK(connection_connect_sockaddr); + + return conn; + + /* On failure */ + done: + UNMOCK(connection_connect_sockaddr); + test_conn_get_basic_teardown(tc, conn); + + /* Returning NULL causes the unit test to fail */ + return NULL; +} + +static int +test_conn_get_basic_teardown(const struct testcase_t *tc, void *arg) +{ + (void)tc; + connection_t *conn = arg; + + tt_assert(conn); + assert_connection_ok(conn, time(NULL)); + + /* teardown the connection as fast as possible */ + if (conn->linked_conn) { + assert_connection_ok(conn->linked_conn, time(NULL)); + + /* We didn't call tor_libevent_initialize(), so event_base was NULL, + * so we can't rely on connection_unregister_events() use of event_del(). + */ + if (conn->linked_conn->read_event) { + tor_free(conn->linked_conn->read_event); + conn->linked_conn->read_event = NULL; + } + if (conn->linked_conn->write_event) { + tor_free(conn->linked_conn->write_event); + conn->linked_conn->write_event = NULL; + } + + if (!conn->linked_conn->marked_for_close) { + connection_close_immediate(conn->linked_conn); + connection_mark_for_close(conn->linked_conn); + } + conn->linked_conn->linked_conn = NULL; + connection_free(conn->linked_conn); + conn->linked_conn = NULL; + } + + /* We didn't set the events up properly, so we can't use event_del() in + * close_closeable_connections() > connection_free() + * > connection_unregister_events() */ + if (conn->read_event) { + tor_free(conn->read_event); + conn->read_event = NULL; + } + if (conn->write_event) { + tor_free(conn->write_event); + conn->write_event = NULL; + } + + if (!conn->marked_for_close) { + connection_close_immediate(conn); + connection_mark_for_close(conn); + } + + close_closeable_connections(); + + /* The unit test will fail if we return 0 */ + return 1; + + /* When conn == NULL, we can't cleanup anything */ + done: + return 0; +} + +static void * +test_conn_get_rend_setup(const struct testcase_t *tc) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, + test_conn_get_basic_setup(tc)); + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + rend_cache_init(); + + /* TODO: use directory_initiate_command_rend() to do this - maybe? */ + conn->rend_data = tor_malloc_zero(sizeof(rend_data_t)); + tor_assert(strlen(TEST_CONN_REND_ADDR) == REND_SERVICE_ID_LEN_BASE32); + memcpy(conn->rend_data->onion_address, + TEST_CONN_REND_ADDR, + REND_SERVICE_ID_LEN_BASE32+1); + conn->rend_data->hsdirs_fp = smartlist_new(); + conn->base_.purpose = TEST_CONN_REND_PURPOSE; + + assert_connection_ok(&conn->base_, time(NULL)); + return conn; + + /* On failure */ + done: + test_conn_get_rend_teardown(tc, conn); + /* Returning NULL causes the unit test to fail */ + return NULL; +} + +static int +test_conn_get_rend_teardown(const struct testcase_t *tc, void *arg) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, arg); + int rv = 0; + + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + /* avoid a last-ditch attempt to refetch the descriptor */ + conn->base_.purpose = TEST_CONN_REND_PURPOSE_SUCCESSFUL; + + /* connection_free_() cleans up rend_data */ + rv = test_conn_get_basic_teardown(tc, arg); + done: + rend_cache_free_all(); + return rv; +} + +static void * +test_conn_get_rsrc_setup(const struct testcase_t *tc) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, + test_conn_get_basic_setup(tc)); + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + /* TODO: use the canonical function to do this - maybe? */ + conn->requested_resource = tor_strdup(TEST_CONN_RSRC); + conn->base_.purpose = TEST_CONN_RSRC_PURPOSE; + + assert_connection_ok(&conn->base_, time(NULL)); + return conn; + + /* On failure */ + done: + test_conn_get_rend_teardown(tc, conn); + /* Returning NULL causes the unit test to fail */ + return NULL; +} + +static int +test_conn_get_rsrc_teardown(const struct testcase_t *tc, void *arg) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, arg); + int rv = 0; + + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + /* avoid a last-ditch attempt to refetch the consensus */ + conn->base_.state = TEST_CONN_RSRC_STATE_SUCCESSFUL; + + /* connection_free_() cleans up requested_resource */ + rv = test_conn_get_basic_teardown(tc, arg); + done: + return rv; +} + +static void * +test_conn_download_status_setup(const struct testcase_t *tc) +{ + (void)tc; + + /* Don't return NULL, that causes the test to fail */ + return (void*)"ok"; +} + +static int +test_conn_download_status_teardown(const struct testcase_t *tc, void *arg) +{ + (void)arg; + int rv = 0; + + /* Ignore arg, and just loop through the connection array */ + SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { + if (conn) { + assert_connection_ok(conn, time(NULL)); + + /* connection_free_() cleans up requested_resource */ + rv = test_conn_get_rsrc_teardown(tc, conn); + tt_assert(rv == 1); + } + } SMARTLIST_FOREACH_END(conn); + + done: + return rv; +} + +static dir_connection_t * +test_conn_download_status_add_a_connection(void) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, + test_conn_get_rsrc_setup(NULL)); + + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + return conn; + + done: + test_conn_download_status_teardown(NULL, NULL); + return NULL; +} + +static struct testcase_setup_t test_conn_get_basic_st = { + test_conn_get_basic_setup, test_conn_get_basic_teardown +}; + +static struct testcase_setup_t test_conn_get_rend_st = { + test_conn_get_rend_setup, test_conn_get_rend_teardown +}; + +static struct testcase_setup_t test_conn_get_rsrc_st = { + test_conn_get_rsrc_setup, test_conn_get_rsrc_teardown +}; + +static struct testcase_setup_t test_conn_download_status_st = { + test_conn_download_status_setup, test_conn_download_status_teardown +}; + +static void +test_conn_get_basic(void *arg) +{ + connection_t *conn = (connection_t*)arg; + tor_addr_t addr, addr2; + + tt_assert(conn); + assert_connection_ok(conn, time(NULL)); + + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS, TEST_CONN_FAMILY, &addr); + tt_assert(!tor_addr_is_null(&addr)); + test_conn_lookup_addr_helper(TEST_CONN_ADDRESS_2, TEST_CONN_FAMILY, &addr2); + tt_assert(!tor_addr_is_null(&addr2)); + + /* Check that we get this connection back when we search for it by + * its attributes, but get NULL when we supply a different value. */ + + tt_assert(connection_get_by_global_id(conn->global_identifier) == conn); + tt_assert(connection_get_by_global_id(!conn->global_identifier) == NULL); + + tt_assert(connection_get_by_type(conn->type) == conn); + tt_assert(connection_get_by_type(TEST_CONN_TYPE) == conn); + tt_assert(connection_get_by_type(!conn->type) == NULL); + tt_assert(connection_get_by_type(!TEST_CONN_TYPE) == NULL); + + tt_assert(connection_get_by_type_state(conn->type, conn->state) + == conn); + tt_assert(connection_get_by_type_state(TEST_CONN_TYPE, TEST_CONN_STATE) + == conn); + tt_assert(connection_get_by_type_state(!conn->type, !conn->state) + == NULL); + tt_assert(connection_get_by_type_state(!TEST_CONN_TYPE, !TEST_CONN_STATE) + == NULL); + + /* Match on the connection fields themselves */ + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + conn->port, + conn->purpose) + == conn); + /* Match on the original inputs to the connection */ + tt_assert(connection_get_by_type_addr_port_purpose(TEST_CONN_TYPE, + &conn->addr, + conn->port, + conn->purpose) + == conn); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &addr, + conn->port, + conn->purpose) + == conn); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + TEST_CONN_PORT, + conn->purpose) + == conn); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + conn->port, + TEST_CONN_BASIC_PURPOSE) + == conn); + tt_assert(connection_get_by_type_addr_port_purpose(TEST_CONN_TYPE, + &addr, + TEST_CONN_PORT, + TEST_CONN_BASIC_PURPOSE) + == conn); + /* Then try each of the not-matching combinations */ + tt_assert(connection_get_by_type_addr_port_purpose(!conn->type, + &conn->addr, + conn->port, + conn->purpose) + == NULL); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &addr2, + conn->port, + conn->purpose) + == NULL); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + !conn->port, + conn->purpose) + == NULL); + tt_assert(connection_get_by_type_addr_port_purpose(conn->type, + &conn->addr, + conn->port, + !conn->purpose) + == NULL); + /* Then try everything not-matching */ + tt_assert(connection_get_by_type_addr_port_purpose(!conn->type, + &addr2, + !conn->port, + !conn->purpose) + == NULL); + tt_assert(connection_get_by_type_addr_port_purpose(!TEST_CONN_TYPE, + &addr2, + !TEST_CONN_PORT, + !TEST_CONN_BASIC_PURPOSE) + == NULL); + + done: + ; +} + +static void +test_conn_get_rend(void *arg) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, arg); + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + tt_assert(connection_get_by_type_state_rendquery( + conn->base_.type, + conn->base_.state, + conn->rend_data->onion_address) + == TO_CONN(conn)); + tt_assert(connection_get_by_type_state_rendquery( + TEST_CONN_TYPE, + TEST_CONN_STATE, + TEST_CONN_REND_ADDR) + == TO_CONN(conn)); + tt_assert(connection_get_by_type_state_rendquery(TEST_CONN_REND_TYPE_2, + !conn->base_.state, + "") + == NULL); + tt_assert(connection_get_by_type_state_rendquery(TEST_CONN_REND_TYPE_2, + !TEST_CONN_STATE, + TEST_CONN_REND_ADDR_2) + == NULL); + + done: + ; +} + +#define sl_is_conn_assert(sl_input, conn) \ + do { \ + the_sl = (sl_input); \ + tt_assert(smartlist_len((the_sl)) == 1); \ + tt_assert(smartlist_get((the_sl), 0) == (conn)); \ + smartlist_free(the_sl); the_sl = NULL; \ + } while (0) + +#define sl_no_conn_assert(sl_input) \ + do { \ + the_sl = (sl_input); \ + tt_assert(smartlist_len((the_sl)) == 0); \ + smartlist_free(the_sl); the_sl = NULL; \ + } while (0) + +static void +test_conn_get_rsrc(void *arg) +{ + dir_connection_t *conn = DOWNCAST(dir_connection_t, arg); + smartlist_t *the_sl = NULL; + tt_assert(conn); + assert_connection_ok(&conn->base_, time(NULL)); + + tt_assert(connection_dir_get_by_purpose_and_resource( + conn->base_.purpose, + conn->requested_resource) + == conn); + tt_assert(connection_dir_get_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) + == conn); + tt_assert(connection_dir_get_by_purpose_and_resource( + !conn->base_.purpose, + "") + == NULL); + tt_assert(connection_dir_get_by_purpose_and_resource( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2) + == NULL); + + tt_assert(connection_dir_get_by_purpose_resource_and_state( + conn->base_.purpose, + conn->requested_resource, + conn->base_.state) + == conn); + tt_assert(connection_dir_get_by_purpose_resource_and_state( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC, + TEST_CONN_STATE) + == conn); + tt_assert(connection_dir_get_by_purpose_resource_and_state( + !conn->base_.purpose, + "", + !conn->base_.state) + == NULL); + tt_assert(connection_dir_get_by_purpose_resource_and_state( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2, + !TEST_CONN_STATE) + == NULL); + + sl_is_conn_assert(connection_dir_list_by_purpose_and_resource( + conn->base_.purpose, + conn->requested_resource), + conn); + sl_is_conn_assert(connection_dir_list_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC), + conn); + sl_no_conn_assert(connection_dir_list_by_purpose_and_resource( + !conn->base_.purpose, + "")); + sl_no_conn_assert(connection_dir_list_by_purpose_and_resource( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2)); + + sl_is_conn_assert(connection_dir_list_by_purpose_resource_and_state( + conn->base_.purpose, + conn->requested_resource, + conn->base_.state), + conn); + sl_is_conn_assert(connection_dir_list_by_purpose_resource_and_state( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC, + TEST_CONN_STATE), + conn); + sl_no_conn_assert(connection_dir_list_by_purpose_resource_and_state( + !conn->base_.purpose, + "", + !conn->base_.state)); + sl_no_conn_assert(connection_dir_list_by_purpose_resource_and_state( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2, + !TEST_CONN_STATE)); + + tt_assert(connection_dir_count_by_purpose_and_resource( + conn->base_.purpose, + conn->requested_resource) + == 1); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) + == 1); + tt_assert(connection_dir_count_by_purpose_and_resource( + !conn->base_.purpose, + "") + == 0); + tt_assert(connection_dir_count_by_purpose_and_resource( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2) + == 0); + + tt_assert(connection_dir_count_by_purpose_resource_and_state( + conn->base_.purpose, + conn->requested_resource, + conn->base_.state) + == 1); + tt_assert(connection_dir_count_by_purpose_resource_and_state( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC, + TEST_CONN_STATE) + == 1); + tt_assert(connection_dir_count_by_purpose_resource_and_state( + !conn->base_.purpose, + "", + !conn->base_.state) + == 0); + tt_assert(connection_dir_count_by_purpose_resource_and_state( + !TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC_2, + !TEST_CONN_STATE) + == 0); + + done: + smartlist_free(the_sl); +} + +static void +test_conn_download_status(void *arg) +{ + (void)arg; + dir_connection_t *conn = NULL; + dir_connection_t *conn2 = NULL; + dir_connection_t *conn3 = NULL; + + /* no connections, no excess, not downloading */ + tt_assert(networkstatus_consensus_has_excess_connections() == 0); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 0); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 0); + + /* one connection, no excess, not downloading */ + conn = test_conn_download_status_add_a_connection(); + tt_assert(networkstatus_consensus_has_excess_connections() == 0); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 0); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 0); + + /* one connection, no excess, but downloading */ + conn->base_.state = TEST_CONN_DL_STATE; + tt_assert(networkstatus_consensus_has_excess_connections() == 0); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + conn->base_.state = TEST_CONN_STATE; + + /* two connections, excess, but not downloading */ + conn2 = test_conn_download_status_add_a_connection(); + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 0); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 0); + + /* two connections, excess, downloading */ + conn2->base_.state = TEST_CONN_DL_STATE; + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + conn2->base_.state = TEST_CONN_STATE; + + /* more connections, excess, but not downloading */ + conn3 = test_conn_download_status_add_a_connection(); + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 0); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 0); + + /* more connections, excess, downloading */ + conn3->base_.state = TEST_CONN_DL_STATE; + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* more connections, more downloading */ + conn2->base_.state = TEST_CONN_DL_STATE; + tt_assert(networkstatus_consensus_has_excess_connections() == 1); + tt_assert(networkstatus_consensus_is_downloading_usable_flavor() == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* now try closing the one that isn't downloading: + * these tests won't work unless tor thinks it is bootstrapping */ + tt_assert(networkstatus_consensus_is_boostrapping(time(NULL))); + + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 3); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + tt_assert(connection_dir_close_consensus_conn_if_extra(conn) == -1); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 2); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* now try closing one that is already closed - nothing happens */ + tt_assert(connection_dir_close_consensus_conn_if_extra(conn) == 0); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 2); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* now try closing one that is downloading - it stays open */ + tt_assert(connection_dir_close_consensus_conn_if_extra(conn2) == 0); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 2); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + /* now try closing all excess connections */ + connection_dir_close_extra_consensus_conns(); + tt_assert(connection_dir_count_by_purpose_and_resource( + TEST_CONN_RSRC_PURPOSE, + TEST_CONN_RSRC) == 1); + tt_assert(connection_dir_avoid_extra_connection_for_purpose( + TEST_CONN_RSRC_PURPOSE) == 1); + + done: + /* the teardown function removes all the connections */; +} + +#define CONNECTION_TESTCASE(name, fork, setup) \ + { #name, test_conn_##name, fork, &setup, NULL } + +struct testcase_t connection_tests[] = { + CONNECTION_TESTCASE(get_basic, TT_FORK, test_conn_get_basic_st), + CONNECTION_TESTCASE(get_rend, TT_FORK, test_conn_get_rend_st), + CONNECTION_TESTCASE(get_rsrc, TT_FORK, test_conn_get_rsrc_st), + CONNECTION_TESTCASE(download_status, TT_FORK, test_conn_download_status_st), +//CONNECTION_TESTCASE(func_suffix, TT_FORK, setup_func_pair), + END_OF_TESTCASES +}; + diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index dbaec61ee9..fb21f3ad09 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -5,6 +5,7 @@ #include "orconfig.h" #define CRYPTO_CURVE25519_PRIVATE +#define CRYPTO_PRIVATE #include "or.h" #include "test.h" #include "aes.h" @@ -15,6 +16,7 @@ #include "ed25519_vectors.inc" #include <openssl/evp.h> +#include <openssl/rand.h> extern const char AUTHORITY_SIGNKEY_3[]; extern const char AUTHORITY_SIGNKEY_A_DIGEST[]; @@ -131,6 +133,32 @@ test_crypto_rng_range(void *arg) ; } +/* Test for rectifying openssl RAND engine. */ +static void +test_crypto_rng_engine(void *arg) +{ + (void)arg; + RAND_METHOD dummy_method; + memset(&dummy_method, 0, sizeof(dummy_method)); + + /* We should be a no-op if we're already on RAND_OpenSSL */ + tt_int_op(0, ==, crypto_force_rand_ssleay()); + tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); + + /* We should correct the method if it's a dummy. */ + RAND_set_rand_method(&dummy_method); + tt_assert(RAND_get_rand_method() == &dummy_method); + tt_int_op(1, ==, crypto_force_rand_ssleay()); + tt_assert(RAND_get_rand_method() == RAND_OpenSSL()); + + /* Make sure we aren't calling dummy_method */ + crypto_rand((void *) &dummy_method, sizeof(dummy_method)); + crypto_rand((void *) &dummy_method, sizeof(dummy_method)); + + done: + ; +} + /** Run unit tests for our AES functionality */ static void test_crypto_aes(void *arg) @@ -284,10 +312,11 @@ test_crypto_sha(void *arg) { crypto_digest_t *d1 = NULL, *d2 = NULL; int i; - char key[160]; - char digest[32]; - char data[50]; - char d_out1[DIGEST_LEN], d_out2[DIGEST256_LEN]; +#define RFC_4231_MAX_KEY_SIZE 131 + char key[RFC_4231_MAX_KEY_SIZE]; + char digest[DIGEST256_LEN]; + char data[DIGEST512_LEN]; + char d_out1[DIGEST512_LEN], d_out2[DIGEST512_LEN]; char *mem_op_hex_tmp=NULL; /* Test SHA-1 with a test vector from the specification. */ @@ -302,6 +331,13 @@ test_crypto_sha(void *arg) "96177A9CB410FF61F20015AD"); tt_int_op(i, OP_EQ, 0); + /* Test SHA-512 with a test vector from the specification. */ + i = crypto_digest512(data, "abc", 3, DIGEST_SHA512); + test_memeq_hex(data, "ddaf35a193617abacc417349ae20413112e6fa4e89a97" + "ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3" + "feebbd454d4423643ce80e2a9ac94fa54ca49f"); + tt_int_op(i, OP_EQ, 0); + /* Test HMAC-SHA256 with test cases from wikipedia and RFC 4231 */ /* Case empty (wikipedia) */ @@ -378,15 +414,15 @@ test_crypto_sha(void *arg) d2 = crypto_digest_dup(d1); tt_assert(d2); crypto_digest_add_bytes(d2, "ghijkl", 6); - crypto_digest_get_digest(d2, d_out1, sizeof(d_out1)); + crypto_digest_get_digest(d2, d_out1, DIGEST_LEN); crypto_digest(d_out2, "abcdefghijkl", 12); tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST_LEN); crypto_digest_assign(d2, d1); crypto_digest_add_bytes(d2, "mno", 3); - crypto_digest_get_digest(d2, d_out1, sizeof(d_out1)); + crypto_digest_get_digest(d2, d_out1, DIGEST_LEN); crypto_digest(d_out2, "abcdefmno", 9); tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST_LEN); - crypto_digest_get_digest(d1, d_out1, sizeof(d_out1)); + crypto_digest_get_digest(d1, d_out1, DIGEST_LEN); crypto_digest(d_out2, "abcdef", 6); tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST_LEN); crypto_digest_free(d1); @@ -399,17 +435,38 @@ test_crypto_sha(void *arg) d2 = crypto_digest_dup(d1); tt_assert(d2); crypto_digest_add_bytes(d2, "ghijkl", 6); - crypto_digest_get_digest(d2, d_out1, sizeof(d_out1)); + crypto_digest_get_digest(d2, d_out1, DIGEST256_LEN); crypto_digest256(d_out2, "abcdefghijkl", 12, DIGEST_SHA256); - tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST_LEN); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST256_LEN); crypto_digest_assign(d2, d1); crypto_digest_add_bytes(d2, "mno", 3); - crypto_digest_get_digest(d2, d_out1, sizeof(d_out1)); + crypto_digest_get_digest(d2, d_out1, DIGEST256_LEN); crypto_digest256(d_out2, "abcdefmno", 9, DIGEST_SHA256); - tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST_LEN); - crypto_digest_get_digest(d1, d_out1, sizeof(d_out1)); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST256_LEN); + crypto_digest_get_digest(d1, d_out1, DIGEST256_LEN); crypto_digest256(d_out2, "abcdef", 6, DIGEST_SHA256); - tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST_LEN); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST256_LEN); + crypto_digest_free(d1); + crypto_digest_free(d2); + + /* Incremental digest code with sha512 */ + d1 = crypto_digest512_new(DIGEST_SHA512); + tt_assert(d1); + crypto_digest_add_bytes(d1, "abcdef", 6); + d2 = crypto_digest_dup(d1); + tt_assert(d2); + crypto_digest_add_bytes(d2, "ghijkl", 6); + crypto_digest_get_digest(d2, d_out1, DIGEST512_LEN); + crypto_digest512(d_out2, "abcdefghijkl", 12, DIGEST_SHA512); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST512_LEN); + crypto_digest_assign(d2, d1); + crypto_digest_add_bytes(d2, "mno", 3); + crypto_digest_get_digest(d2, d_out1, DIGEST512_LEN); + crypto_digest512(d_out2, "abcdefmno", 9, DIGEST_SHA512); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST512_LEN); + crypto_digest_get_digest(d1, d_out1, DIGEST512_LEN); + crypto_digest512(d_out2, "abcdef", 6, DIGEST_SHA512); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST512_LEN); done: if (d1) @@ -419,6 +476,394 @@ test_crypto_sha(void *arg) tor_free(mem_op_hex_tmp); } +static void +test_crypto_sha3(void *arg) +{ + crypto_digest_t *d1 = NULL, *d2 = NULL; + int i; + char data[DIGEST512_LEN]; + char d_out1[DIGEST512_LEN], d_out2[DIGEST512_LEN]; + char *mem_op_hex_tmp=NULL; + char *large = NULL; + + (void)arg; + + /* Test SHA3-[256,512] with a test vectors from the Keccak Code Package. + * + * NB: The code package's test vectors have length expressed in bits. + */ + + /* Len = 8, Msg = CC */ + const uint8_t keccak_kat_msg8[] = { 0xcc }; + i = crypto_digest256(data, (const char*)keccak_kat_msg8, 1, DIGEST_SHA3_256); + test_memeq_hex(data, "677035391CD3701293D385F037BA3279" + "6252BB7CE180B00B582DD9B20AAAD7F0"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest512(data, (const char*)keccak_kat_msg8, 1, DIGEST_SHA3_512); + test_memeq_hex(data, "3939FCC8B57B63612542DA31A834E5DC" + "C36E2EE0F652AC72E02624FA2E5ADEEC" + "C7DD6BB3580224B4D6138706FC6E8059" + "7B528051230B00621CC2B22999EAA205"); + tt_int_op(i, OP_EQ, 0); + + /* Len = 24, Msg = 1F877C */ + const uint8_t keccak_kat_msg24[] = { 0x1f, 0x87, 0x7c }; + i = crypto_digest256(data, (const char*)keccak_kat_msg24, 3, + DIGEST_SHA3_256); + test_memeq_hex(data, "BC22345E4BD3F792A341CF18AC0789F1" + "C9C966712A501B19D1B6632CCD408EC5"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest512(data, (const char*)keccak_kat_msg24, 3, + DIGEST_SHA3_512); + test_memeq_hex(data, "CB20DCF54955F8091111688BECCEF48C" + "1A2F0D0608C3A575163751F002DB30F4" + "0F2F671834B22D208591CFAF1F5ECFE4" + "3C49863A53B3225BDFD7C6591BA7658B"); + tt_int_op(i, OP_EQ, 0); + + /* Len = 1080, Msg = B771D5CEF... ...C35AC81B5 (SHA3-256 rate - 1) */ + const uint8_t keccak_kat_msg1080[] = { + 0xB7, 0x71, 0xD5, 0xCE, 0xF5, 0xD1, 0xA4, 0x1A, 0x93, 0xD1, + 0x56, 0x43, 0xD7, 0x18, 0x1D, 0x2A, 0x2E, 0xF0, 0xA8, 0xE8, + 0x4D, 0x91, 0x81, 0x2F, 0x20, 0xED, 0x21, 0xF1, 0x47, 0xBE, + 0xF7, 0x32, 0xBF, 0x3A, 0x60, 0xEF, 0x40, 0x67, 0xC3, 0x73, + 0x4B, 0x85, 0xBC, 0x8C, 0xD4, 0x71, 0x78, 0x0F, 0x10, 0xDC, + 0x9E, 0x82, 0x91, 0xB5, 0x83, 0x39, 0xA6, 0x77, 0xB9, 0x60, + 0x21, 0x8F, 0x71, 0xE7, 0x93, 0xF2, 0x79, 0x7A, 0xEA, 0x34, + 0x94, 0x06, 0x51, 0x28, 0x29, 0x06, 0x5D, 0x37, 0xBB, 0x55, + 0xEA, 0x79, 0x6F, 0xA4, 0xF5, 0x6F, 0xD8, 0x89, 0x6B, 0x49, + 0xB2, 0xCD, 0x19, 0xB4, 0x32, 0x15, 0xAD, 0x96, 0x7C, 0x71, + 0x2B, 0x24, 0xE5, 0x03, 0x2D, 0x06, 0x52, 0x32, 0xE0, 0x2C, + 0x12, 0x74, 0x09, 0xD2, 0xED, 0x41, 0x46, 0xB9, 0xD7, 0x5D, + 0x76, 0x3D, 0x52, 0xDB, 0x98, 0xD9, 0x49, 0xD3, 0xB0, 0xFE, + 0xD6, 0xA8, 0x05, 0x2F, 0xBB, + }; + i = crypto_digest256(data, (const char*)keccak_kat_msg1080, 135, + DIGEST_SHA3_256); + test_memeq_hex(data, "A19EEE92BB2097B64E823D597798AA18" + "BE9B7C736B8059ABFD6779AC35AC81B5"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest512(data, (const char*)keccak_kat_msg1080, 135, + DIGEST_SHA3_512); + test_memeq_hex(data, "7575A1FB4FC9A8F9C0466BD5FCA496D1" + "CB78696773A212A5F62D02D14E3259D1" + "92A87EBA4407DD83893527331407B6DA" + "DAAD920DBC46489B677493CE5F20B595"); + tt_int_op(i, OP_EQ, 0); + + /* Len = 1088, Msg = B32D95B0... ...8E380C04 (SHA3-256 rate) */ + const uint8_t keccak_kat_msg1088[] = { + 0xB3, 0x2D, 0x95, 0xB0, 0xB9, 0xAA, 0xD2, 0xA8, 0x81, 0x6D, + 0xE6, 0xD0, 0x6D, 0x1F, 0x86, 0x00, 0x85, 0x05, 0xBD, 0x8C, + 0x14, 0x12, 0x4F, 0x6E, 0x9A, 0x16, 0x3B, 0x5A, 0x2A, 0xDE, + 0x55, 0xF8, 0x35, 0xD0, 0xEC, 0x38, 0x80, 0xEF, 0x50, 0x70, + 0x0D, 0x3B, 0x25, 0xE4, 0x2C, 0xC0, 0xAF, 0x05, 0x0C, 0xCD, + 0x1B, 0xE5, 0xE5, 0x55, 0xB2, 0x30, 0x87, 0xE0, 0x4D, 0x7B, + 0xF9, 0x81, 0x36, 0x22, 0x78, 0x0C, 0x73, 0x13, 0xA1, 0x95, + 0x4F, 0x87, 0x40, 0xB6, 0xEE, 0x2D, 0x3F, 0x71, 0xF7, 0x68, + 0xDD, 0x41, 0x7F, 0x52, 0x04, 0x82, 0xBD, 0x3A, 0x08, 0xD4, + 0xF2, 0x22, 0xB4, 0xEE, 0x9D, 0xBD, 0x01, 0x54, 0x47, 0xB3, + 0x35, 0x07, 0xDD, 0x50, 0xF3, 0xAB, 0x42, 0x47, 0xC5, 0xDE, + 0x9A, 0x8A, 0xBD, 0x62, 0xA8, 0xDE, 0xCE, 0xA0, 0x1E, 0x3B, + 0x87, 0xC8, 0xB9, 0x27, 0xF5, 0xB0, 0x8B, 0xEB, 0x37, 0x67, + 0x4C, 0x6F, 0x8E, 0x38, 0x0C, 0x04, + }; + i = crypto_digest256(data, (const char*)keccak_kat_msg1088, 136, + DIGEST_SHA3_256); + test_memeq_hex(data, "DF673F4105379FF6B755EEAB20CEB0DC" + "77B5286364FE16C59CC8A907AFF07732"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest512(data, (const char*)keccak_kat_msg1088, 136, + DIGEST_SHA3_512); + test_memeq_hex(data, "2E293765022D48996CE8EFF0BE54E87E" + "FB94A14C72DE5ACD10D0EB5ECE029CAD" + "FA3BA17A40B2FFA2163991B17786E51C" + "ABA79E5E0FFD34CF085E2A098BE8BACB"); + tt_int_op(i, OP_EQ, 0); + + /* Len = 1096, Msg = 04410E310... ...601016A0D (SHA3-256 rate + 1) */ + const uint8_t keccak_kat_msg1096[] = { + 0x04, 0x41, 0x0E, 0x31, 0x08, 0x2A, 0x47, 0x58, 0x4B, 0x40, + 0x6F, 0x05, 0x13, 0x98, 0xA6, 0xAB, 0xE7, 0x4E, 0x4D, 0xA5, + 0x9B, 0xB6, 0xF8, 0x5E, 0x6B, 0x49, 0xE8, 0xA1, 0xF7, 0xF2, + 0xCA, 0x00, 0xDF, 0xBA, 0x54, 0x62, 0xC2, 0xCD, 0x2B, 0xFD, + 0xE8, 0xB6, 0x4F, 0xB2, 0x1D, 0x70, 0xC0, 0x83, 0xF1, 0x13, + 0x18, 0xB5, 0x6A, 0x52, 0xD0, 0x3B, 0x81, 0xCA, 0xC5, 0xEE, + 0xC2, 0x9E, 0xB3, 0x1B, 0xD0, 0x07, 0x8B, 0x61, 0x56, 0x78, + 0x6D, 0xA3, 0xD6, 0xD8, 0xC3, 0x30, 0x98, 0xC5, 0xC4, 0x7B, + 0xB6, 0x7A, 0xC6, 0x4D, 0xB1, 0x41, 0x65, 0xAF, 0x65, 0xB4, + 0x45, 0x44, 0xD8, 0x06, 0xDD, 0xE5, 0xF4, 0x87, 0xD5, 0x37, + 0x3C, 0x7F, 0x97, 0x92, 0xC2, 0x99, 0xE9, 0x68, 0x6B, 0x7E, + 0x58, 0x21, 0xE7, 0xC8, 0xE2, 0x45, 0x83, 0x15, 0xB9, 0x96, + 0xB5, 0x67, 0x7D, 0x92, 0x6D, 0xAC, 0x57, 0xB3, 0xF2, 0x2D, + 0xA8, 0x73, 0xC6, 0x01, 0x01, 0x6A, 0x0D, + }; + i = crypto_digest256(data, (const char*)keccak_kat_msg1096, 137, + DIGEST_SHA3_256); + test_memeq_hex(data, "D52432CF3B6B4B949AA848E058DCD62D" + "735E0177279222E7AC0AF8504762FAA0"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest512(data, (const char*)keccak_kat_msg1096, 137, + DIGEST_SHA3_512); + test_memeq_hex(data, "BE8E14B6757FFE53C9B75F6DDE9A7B6C" + "40474041DE83D4A60645A826D7AF1ABE" + "1EEFCB7B74B62CA6A514E5F2697D585B" + "FECECE12931BBE1D4ED7EBF7B0BE660E"); + tt_int_op(i, OP_EQ, 0); + + /* Len = 1144, Msg = EA40E83C... ...66DFAFEC (SHA3-512 rate *2 - 1) */ + const uint8_t keccak_kat_msg1144[] = { + 0xEA, 0x40, 0xE8, 0x3C, 0xB1, 0x8B, 0x3A, 0x24, 0x2C, 0x1E, + 0xCC, 0x6C, 0xCD, 0x0B, 0x78, 0x53, 0xA4, 0x39, 0xDA, 0xB2, + 0xC5, 0x69, 0xCF, 0xC6, 0xDC, 0x38, 0xA1, 0x9F, 0x5C, 0x90, + 0xAC, 0xBF, 0x76, 0xAE, 0xF9, 0xEA, 0x37, 0x42, 0xFF, 0x3B, + 0x54, 0xEF, 0x7D, 0x36, 0xEB, 0x7C, 0xE4, 0xFF, 0x1C, 0x9A, + 0xB3, 0xBC, 0x11, 0x9C, 0xFF, 0x6B, 0xE9, 0x3C, 0x03, 0xE2, + 0x08, 0x78, 0x33, 0x35, 0xC0, 0xAB, 0x81, 0x37, 0xBE, 0x5B, + 0x10, 0xCD, 0xC6, 0x6F, 0xF3, 0xF8, 0x9A, 0x1B, 0xDD, 0xC6, + 0xA1, 0xEE, 0xD7, 0x4F, 0x50, 0x4C, 0xBE, 0x72, 0x90, 0x69, + 0x0B, 0xB2, 0x95, 0xA8, 0x72, 0xB9, 0xE3, 0xFE, 0x2C, 0xEE, + 0x9E, 0x6C, 0x67, 0xC4, 0x1D, 0xB8, 0xEF, 0xD7, 0xD8, 0x63, + 0xCF, 0x10, 0xF8, 0x40, 0xFE, 0x61, 0x8E, 0x79, 0x36, 0xDA, + 0x3D, 0xCA, 0x5C, 0xA6, 0xDF, 0x93, 0x3F, 0x24, 0xF6, 0x95, + 0x4B, 0xA0, 0x80, 0x1A, 0x12, 0x94, 0xCD, 0x8D, 0x7E, 0x66, + 0xDF, 0xAF, 0xEC, + }; + i = crypto_digest512(data, (const char*)keccak_kat_msg1144, 143, + DIGEST_SHA3_512); + test_memeq_hex(data, "3A8E938C45F3F177991296B24565D9A6" + "605516615D96A062C8BE53A0D6C5A648" + "7BE35D2A8F3CF6620D0C2DBA2C560D68" + "295F284BE7F82F3B92919033C9CE5D80"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest256(data, (const char*)keccak_kat_msg1144, 143, + DIGEST_SHA3_256); + test_memeq_hex(data, "E58A947E98D6DD7E932D2FE02D9992E6" + "118C0C2C606BDCDA06E7943D2C95E0E5"); + tt_int_op(i, OP_EQ, 0); + + /* Len = 1152, Msg = 157D5B7E... ...79EE00C63 (SHA3-512 rate * 2) */ + const uint8_t keccak_kat_msg1152[] = { + 0x15, 0x7D, 0x5B, 0x7E, 0x45, 0x07, 0xF6, 0x6D, 0x9A, 0x26, + 0x74, 0x76, 0xD3, 0x38, 0x31, 0xE7, 0xBB, 0x76, 0x8D, 0x4D, + 0x04, 0xCC, 0x34, 0x38, 0xDA, 0x12, 0xF9, 0x01, 0x02, 0x63, + 0xEA, 0x5F, 0xCA, 0xFB, 0xDE, 0x25, 0x79, 0xDB, 0x2F, 0x6B, + 0x58, 0xF9, 0x11, 0xD5, 0x93, 0xD5, 0xF7, 0x9F, 0xB0, 0x5F, + 0xE3, 0x59, 0x6E, 0x3F, 0xA8, 0x0F, 0xF2, 0xF7, 0x61, 0xD1, + 0xB0, 0xE5, 0x70, 0x80, 0x05, 0x5C, 0x11, 0x8C, 0x53, 0xE5, + 0x3C, 0xDB, 0x63, 0x05, 0x52, 0x61, 0xD7, 0xC9, 0xB2, 0xB3, + 0x9B, 0xD9, 0x0A, 0xCC, 0x32, 0x52, 0x0C, 0xBB, 0xDB, 0xDA, + 0x2C, 0x4F, 0xD8, 0x85, 0x6D, 0xBC, 0xEE, 0x17, 0x31, 0x32, + 0xA2, 0x67, 0x91, 0x98, 0xDA, 0xF8, 0x30, 0x07, 0xA9, 0xB5, + 0xC5, 0x15, 0x11, 0xAE, 0x49, 0x76, 0x6C, 0x79, 0x2A, 0x29, + 0x52, 0x03, 0x88, 0x44, 0x4E, 0xBE, 0xFE, 0x28, 0x25, 0x6F, + 0xB3, 0x3D, 0x42, 0x60, 0x43, 0x9C, 0xBA, 0x73, 0xA9, 0x47, + 0x9E, 0xE0, 0x0C, 0x63, + }; + i = crypto_digest512(data, (const char*)keccak_kat_msg1152, 144, + DIGEST_SHA3_512); + test_memeq_hex(data, "FE45289874879720CE2A844AE34BB735" + "22775DCB6019DCD22B8885994672A088" + "9C69E8115C641DC8B83E39F7311815A1" + "64DC46E0BA2FCA344D86D4BC2EF2532C"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest256(data, (const char*)keccak_kat_msg1152, 144, + DIGEST_SHA3_256); + test_memeq_hex(data, "A936FB9AF87FB67857B3EAD5C76226AD" + "84DA47678F3C2FFE5A39FDB5F7E63FFB"); + tt_int_op(i, OP_EQ, 0); + + /* Len = 1160, Msg = 836B34B5... ...11044C53 (SHA3-512 rate * 2 + 1) */ + const uint8_t keccak_kat_msg1160[] = { + 0x83, 0x6B, 0x34, 0xB5, 0x15, 0x47, 0x6F, 0x61, 0x3F, 0xE4, + 0x47, 0xA4, 0xE0, 0xC3, 0xF3, 0xB8, 0xF2, 0x09, 0x10, 0xAC, + 0x89, 0xA3, 0x97, 0x70, 0x55, 0xC9, 0x60, 0xD2, 0xD5, 0xD2, + 0xB7, 0x2B, 0xD8, 0xAC, 0xC7, 0x15, 0xA9, 0x03, 0x53, 0x21, + 0xB8, 0x67, 0x03, 0xA4, 0x11, 0xDD, 0xE0, 0x46, 0x6D, 0x58, + 0xA5, 0x97, 0x69, 0x67, 0x2A, 0xA6, 0x0A, 0xD5, 0x87, 0xB8, + 0x48, 0x1D, 0xE4, 0xBB, 0xA5, 0x52, 0xA1, 0x64, 0x57, 0x79, + 0x78, 0x95, 0x01, 0xEC, 0x53, 0xD5, 0x40, 0xB9, 0x04, 0x82, + 0x1F, 0x32, 0xB0, 0xBD, 0x18, 0x55, 0xB0, 0x4E, 0x48, 0x48, + 0xF9, 0xF8, 0xCF, 0xE9, 0xEB, 0xD8, 0x91, 0x1B, 0xE9, 0x57, + 0x81, 0xA7, 0x59, 0xD7, 0xAD, 0x97, 0x24, 0xA7, 0x10, 0x2D, + 0xBE, 0x57, 0x67, 0x76, 0xB7, 0xC6, 0x32, 0xBC, 0x39, 0xB9, + 0xB5, 0xE1, 0x90, 0x57, 0xE2, 0x26, 0x55, 0x2A, 0x59, 0x94, + 0xC1, 0xDB, 0xB3, 0xB5, 0xC7, 0x87, 0x1A, 0x11, 0xF5, 0x53, + 0x70, 0x11, 0x04, 0x4C, 0x53, + }; + i = crypto_digest512(data, (const char*)keccak_kat_msg1160, 145, + DIGEST_SHA3_512); + test_memeq_hex(data, "AFF61C6E11B98E55AC213B1A0BC7DE04" + "05221AC5EFB1229842E4614F4A029C9B" + "D14A0ED7FD99AF3681429F3F309FDB53" + "166AA9A3CD9F1F1223D04B4A9015E94A"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest256(data, (const char*)keccak_kat_msg1160, 145, + DIGEST_SHA3_256); + test_memeq_hex(data, "3A654B88F88086C2751EDAE6D3924814" + "3CF6235C6B0B7969342C45A35194B67E"); + tt_int_op(i, OP_EQ, 0); + + /* SHA3-[256,512] Empty case (wikipedia) */ + i = crypto_digest256(data, "", 0, DIGEST_SHA3_256); + test_memeq_hex(data, "a7ffc6f8bf1ed76651c14756a061d662" + "f580ff4de43b49fa82d80a4b80f8434a"); + tt_int_op(i, OP_EQ, 0); + i = crypto_digest512(data, "", 0, DIGEST_SHA3_512); + test_memeq_hex(data, "a69f73cca23a9ac5c8b567dc185a756e" + "97c982164fe25859e0d1dcc1475c80a6" + "15b2123af1f5f94c11e3e9402c3ac558" + "f500199d95b6d3e301758586281dcd26"); + tt_int_op(i, OP_EQ, 0); + + /* Incremental digest code with SHA3-256 */ + d1 = crypto_digest256_new(DIGEST_SHA3_256); + tt_assert(d1); + crypto_digest_add_bytes(d1, "abcdef", 6); + d2 = crypto_digest_dup(d1); + tt_assert(d2); + crypto_digest_add_bytes(d2, "ghijkl", 6); + crypto_digest_get_digest(d2, d_out1, DIGEST256_LEN); + crypto_digest256(d_out2, "abcdefghijkl", 12, DIGEST_SHA3_256); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST256_LEN); + crypto_digest_assign(d2, d1); + crypto_digest_add_bytes(d2, "mno", 3); + crypto_digest_get_digest(d2, d_out1, DIGEST256_LEN); + crypto_digest256(d_out2, "abcdefmno", 9, DIGEST_SHA3_256); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST256_LEN); + crypto_digest_get_digest(d1, d_out1, DIGEST256_LEN); + crypto_digest256(d_out2, "abcdef", 6, DIGEST_SHA3_256); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST256_LEN); + crypto_digest_free(d1); + crypto_digest_free(d2); + + /* Incremental digest code with SHA3-512 */ + d1 = crypto_digest512_new(DIGEST_SHA3_512); + tt_assert(d1); + crypto_digest_add_bytes(d1, "abcdef", 6); + d2 = crypto_digest_dup(d1); + tt_assert(d2); + crypto_digest_add_bytes(d2, "ghijkl", 6); + crypto_digest_get_digest(d2, d_out1, DIGEST512_LEN); + crypto_digest512(d_out2, "abcdefghijkl", 12, DIGEST_SHA3_512); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST512_LEN); + crypto_digest_assign(d2, d1); + crypto_digest_add_bytes(d2, "mno", 3); + crypto_digest_get_digest(d2, d_out1, DIGEST512_LEN); + crypto_digest512(d_out2, "abcdefmno", 9, DIGEST_SHA3_512); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST512_LEN); + crypto_digest_get_digest(d1, d_out1, DIGEST512_LEN); + crypto_digest512(d_out2, "abcdef", 6, DIGEST_SHA3_512); + tt_mem_op(d_out1,OP_EQ, d_out2, DIGEST512_LEN); + crypto_digest_free(d1); + + /* Attempt to exercise the incremental hashing code by creating a randomized + * 100 KiB buffer, and hashing rand[1, 5 * Rate] bytes at a time. SHA3-512 + * is used because it has a lowest rate of the family (the code is common, + * but the slower rate exercises more of it). + */ + const size_t bufsz = 100 * 1024; + size_t j = 0; + large = tor_malloc(bufsz); + crypto_rand(large, bufsz); + d1 = crypto_digest512_new(DIGEST_SHA3_512); /* Running digest. */ + while (j < bufsz) { + /* Pick how much data to add to the running digest. */ + size_t incr = (size_t)crypto_rand_int_range(1, 72 * 5); + incr = MIN(bufsz - j, incr); + + /* Add the data, and calculate the hash. */ + crypto_digest_add_bytes(d1, large + j, incr); + crypto_digest_get_digest(d1, d_out1, DIGEST512_LEN); + + /* One-shot hash the buffer up to the data that was just added, + * and ensure that the values match up. + * + * XXX/yawning: If this actually fails, it'll be rather difficult to + * reproduce. Improvements welcome. + */ + i = crypto_digest512(d_out2, large, j + incr, DIGEST_SHA3_512); + tt_int_op(i, OP_EQ, 0); + tt_mem_op(d_out1, OP_EQ, d_out2, DIGEST512_LEN); + + j += incr; + } + + done: + if (d1) + crypto_digest_free(d1); + if (d2) + crypto_digest_free(d2); + tor_free(large); + tor_free(mem_op_hex_tmp); +} + +/** Run unit tests for our XOF. */ +static void +test_crypto_sha3_xof(void *arg) +{ + uint8_t msg[255]; + uint8_t out[512]; + crypto_xof_t *xof; + char *mem_op_hex_tmp=NULL; + + (void)arg; + + /* SHAKE256 test vector (Len = 2040) from the Keccak Code Package. */ + base16_decode((char *)msg, 255, + "3A3A819C48EFDE2AD914FBF00E18AB6BC4F14513AB27D0C178A188B61431" + "E7F5623CB66B23346775D386B50E982C493ADBBFC54B9A3CD383382336A1" + "A0B2150A15358F336D03AE18F666C7573D55C4FD181C29E6CCFDE63EA35F" + "0ADF5885CFC0A3D84A2B2E4DD24496DB789E663170CEF74798AA1BBCD457" + "4EA0BBA40489D764B2F83AADC66B148B4A0CD95246C127D5871C4F114186" + "90A5DDF01246A0C80A43C70088B6183639DCFDA4125BD113A8F49EE23ED3" + "06FAAC576C3FB0C1E256671D817FC2534A52F5B439F72E424DE376F4C565" + "CCA82307DD9EF76DA5B7C4EB7E085172E328807C02D011FFBF33785378D7" + "9DC266F6A5BE6BB0E4A92ECEEBAEB1", 510); + const char *squeezed_hex = + "8A5199B4A7E133E264A86202720655894D48CFF344A928CF8347F48379CE" + "F347DFC5BCFFAB99B27B1F89AA2735E23D30088FFA03B9EDB02B9635470A" + "B9F1038985D55F9CA774572DD006470EA65145469609F9FA0831BF1FFD84" + "2DC24ACADE27BD9816E3B5BF2876CB112232A0EB4475F1DFF9F5C713D9FF" + "D4CCB89AE5607FE35731DF06317949EEF646E9591CF3BE53ADD6B7DD2B60" + "96E2B3FB06E662EC8B2D77422DAAD9463CD155204ACDBD38E319613F39F9" + "9B6DFB35CA9365160066DB19835888C2241FF9A731A4ACBB5663727AAC34" + "A401247FBAA7499E7D5EE5B69D31025E63D04C35C798BCA1262D5673A9CF" + "0930B5AD89BD485599DC184528DA4790F088EBD170B635D9581632D2FF90" + "DB79665CED430089AF13C9F21F6D443A818064F17AEC9E9C5457001FA8DC" + "6AFBADBE3138F388D89D0E6F22F66671255B210754ED63D81DCE75CE8F18" + "9B534E6D6B3539AA51E837C42DF9DF59C71E6171CD4902FE1BDC73FB1775" + "B5C754A1ED4EA7F3105FC543EE0418DAD256F3F6118EA77114A16C15355B" + "42877A1DB2A7DF0E155AE1D8670ABCEC3450F4E2EEC9838F895423EF63D2" + "61138BAAF5D9F104CB5A957AEA06C0B9B8C78B0D441796DC0350DDEABB78" + "A33B6F1F9E68EDE3D1805C7B7E2CFD54E0FAD62F0D8CA67A775DC4546AF9" + "096F2EDB221DB42843D65327861282DC946A0BA01A11863AB2D1DFD16E39" + "73D4"; + + /* Test oneshot absorb/squeeze. */ + xof = crypto_xof_new(); + tt_assert(xof); + crypto_xof_add_bytes(xof, msg, sizeof(msg)); + crypto_xof_squeeze_bytes(xof, out, sizeof(out)); + test_memeq_hex(out, squeezed_hex); + crypto_xof_free(xof); + memset(out, 0, sizeof(out)); + + /* Test incremental absorb/squeeze. */ + xof = crypto_xof_new(); + tt_assert(xof); + for (size_t i = 0; i < sizeof(msg); i++) + crypto_xof_add_bytes(xof, msg + i, 1); + for (size_t i = 0; i < sizeof(out); i++) + crypto_xof_squeeze_bytes(xof, out + i, 1); + test_memeq_hex(out, squeezed_hex); + + done: + if (xof) + crypto_xof_free(xof); + tor_free(mem_op_hex_tmp); +} + /** Run unit tests for our public key crypto functions */ static void test_crypto_pk(void *arg) @@ -1271,6 +1716,24 @@ test_crypto_curve25519_persist(void *arg) tor_free(tag); } +static void * +ed25519_testcase_setup(const struct testcase_t *testcase) +{ + crypto_ed25519_testing_force_impl(testcase->setup_data); + return testcase->setup_data; +} +static int +ed25519_testcase_cleanup(const struct testcase_t *testcase, void *ptr) +{ + (void)testcase; + (void)ptr; + crypto_ed25519_testing_restore_impl(); + return 1; +} +static const struct testcase_setup_t ed25519_test_setup = { + ed25519_testcase_setup, ed25519_testcase_cleanup +}; + static void test_crypto_ed25519_simple(void *arg) { @@ -1803,13 +2266,126 @@ test_crypto_siphash(void *arg) ; } +/* We want the likelihood that the random buffer exhibits any regular pattern + * to be far less than the memory bit error rate in the int return value. + * Using 2048 bits provides a failure rate of 1/(3 * 10^616), and we call + * 3 functions, leading to an overall error rate of 1/10^616. + * This is comparable with the 1/10^603 failure rate of test_crypto_rng_range. + */ +#define FAILURE_MODE_BUFFER_SIZE (2048/8) + +/** Check crypto_rand for a failure mode where it does nothing to the buffer, + * or it sets the buffer to all zeroes. Return 0 when the check passes, + * or -1 when it fails. */ +static int +crypto_rand_check_failure_mode_zero(void) +{ + char buf[FAILURE_MODE_BUFFER_SIZE]; + + memset(buf, 0, FAILURE_MODE_BUFFER_SIZE); + crypto_rand(buf, FAILURE_MODE_BUFFER_SIZE); + + for (size_t i = 0; i < FAILURE_MODE_BUFFER_SIZE; i++) { + if (buf[i] != 0) { + return 0; + } + } + + return -1; +} + +/** Check crypto_rand for a failure mode where every int64_t in the buffer is + * the same. Return 0 when the check passes, or -1 when it fails. */ +static int +crypto_rand_check_failure_mode_identical(void) +{ + /* just in case the buffer size isn't a multiple of sizeof(int64_t) */ +#define FAILURE_MODE_BUFFER_SIZE_I64 \ + (FAILURE_MODE_BUFFER_SIZE/SIZEOF_INT64_T) +#define FAILURE_MODE_BUFFER_SIZE_I64_BYTES \ + (FAILURE_MODE_BUFFER_SIZE_I64*SIZEOF_INT64_T) + +#if FAILURE_MODE_BUFFER_SIZE_I64 < 2 +#error FAILURE_MODE_BUFFER_SIZE needs to be at least 2*SIZEOF_INT64_T +#endif + + int64_t buf[FAILURE_MODE_BUFFER_SIZE_I64]; + + memset(buf, 0, FAILURE_MODE_BUFFER_SIZE_I64_BYTES); + crypto_rand((char *)buf, FAILURE_MODE_BUFFER_SIZE_I64_BYTES); + + for (size_t i = 1; i < FAILURE_MODE_BUFFER_SIZE_I64; i++) { + if (buf[i] != buf[i-1]) { + return 0; + } + } + + return -1; +} + +/** Check crypto_rand for a failure mode where it increments the "random" + * value by 1 for every byte in the buffer. (This is OpenSSL's PREDICT mode.) + * Return 0 when the check passes, or -1 when it fails. */ +static int +crypto_rand_check_failure_mode_predict(void) +{ + unsigned char buf[FAILURE_MODE_BUFFER_SIZE]; + + memset(buf, 0, FAILURE_MODE_BUFFER_SIZE); + crypto_rand((char *)buf, FAILURE_MODE_BUFFER_SIZE); + + for (size_t i = 1; i < FAILURE_MODE_BUFFER_SIZE; i++) { + /* check if the last byte was incremented by 1, including integer + * wrapping */ + if (buf[i] - buf[i-1] != 1 && buf[i-1] - buf[i] != 255) { + return 0; + } + } + + return -1; +} + +#undef FAILURE_MODE_BUFFER_SIZE + +static void +test_crypto_failure_modes(void *arg) +{ + int rv = 0; + (void)arg; + + rv = crypto_early_init(); + tt_assert(rv == 0); + + /* Check random works */ + rv = crypto_rand_check_failure_mode_zero(); + tt_assert(rv == 0); + + rv = crypto_rand_check_failure_mode_identical(); + tt_assert(rv == 0); + + rv = crypto_rand_check_failure_mode_predict(); + tt_assert(rv == 0); + + done: + ; +} + #define CRYPTO_LEGACY(name) \ { #name, test_crypto_ ## name , 0, NULL, NULL } +#define ED25519_TEST_ONE(name, fl, which) \ + { #name "/ed25519_" which, test_crypto_ed25519_ ## name, (fl), \ + &ed25519_test_setup, (void*)which } + +#define ED25519_TEST(name, fl) \ + ED25519_TEST_ONE(name, (fl), "donna"), \ + ED25519_TEST_ONE(name, (fl), "ref10") + struct testcase_t crypto_tests[] = { CRYPTO_LEGACY(formats), CRYPTO_LEGACY(rng), { "rng_range", test_crypto_rng_range, 0, NULL, NULL }, + { "rng_engine", test_crypto_rng_engine, TT_FORK, NULL, NULL }, { "aes_AES", test_crypto_aes, TT_FORK, &passthrough_setup, (void*)"aes" }, { "aes_EVP", test_crypto_aes, TT_FORK, &passthrough_setup, (void*)"evp" }, CRYPTO_LEGACY(sha), @@ -1817,6 +2393,8 @@ struct testcase_t crypto_tests[] = { { "pk_fingerprints", test_crypto_pk_fingerprints, TT_FORK, NULL, NULL }, { "pk_base64", test_crypto_pk_base64, TT_FORK, NULL, NULL }, CRYPTO_LEGACY(digests), + { "sha3", test_crypto_sha3, TT_FORK, NULL, NULL}, + { "sha3_xof", test_crypto_sha3_xof, TT_FORK, NULL, NULL}, CRYPTO_LEGACY(dh), { "aes_iv_AES", test_crypto_aes_iv, TT_FORK, &passthrough_setup, (void*)"aes" }, @@ -1832,15 +2410,15 @@ struct testcase_t crypto_tests[] = { { "curve25519_wrappers", test_crypto_curve25519_wrappers, 0, NULL, NULL }, { "curve25519_encode", test_crypto_curve25519_encode, 0, NULL, NULL }, { "curve25519_persist", test_crypto_curve25519_persist, 0, NULL, NULL }, - { "ed25519_simple", test_crypto_ed25519_simple, 0, NULL, NULL }, - { "ed25519_test_vectors", test_crypto_ed25519_test_vectors, 0, NULL, NULL }, - { "ed25519_encode", test_crypto_ed25519_encode, 0, NULL, NULL }, - { "ed25519_convert", test_crypto_ed25519_convert, 0, NULL, NULL }, - { "ed25519_blinding", test_crypto_ed25519_blinding, 0, NULL, NULL }, - { "ed25519_testvectors", test_crypto_ed25519_testvectors, 0, NULL, NULL }, - { "ed25519_fuzz_donna", test_crypto_ed25519_fuzz_donna, TT_FORK, NULL, - NULL }, + ED25519_TEST(simple, 0), + ED25519_TEST(test_vectors, 0), + ED25519_TEST(encode, 0), + ED25519_TEST(convert, 0), + ED25519_TEST(blinding, 0), + ED25519_TEST(testvectors, 0), + ED25519_TEST(fuzz_donna, TT_FORK), { "siphash", test_crypto_siphash, 0, NULL, NULL }, + { "failure_modes", test_crypto_failure_modes, TT_FORK, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 855746e749..05fbe0a4f6 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -6,19 +6,24 @@ #include "orconfig.h" #include <math.h> +#define CONFIG_PRIVATE #define DIRSERV_PRIVATE #define DIRVOTE_PRIVATE #define ROUTER_PRIVATE #define ROUTERLIST_PRIVATE #define HIBERNATE_PRIVATE #define NETWORKSTATUS_PRIVATE +#define RELAY_PRIVATE + #include "or.h" +#include "confparse.h" #include "config.h" #include "crypto_ed25519.h" #include "directory.h" #include "dirserv.h" #include "dirvote.h" #include "hibernate.h" +#include "memarea.h" #include "networkstatus.h" #include "router.h" #include "routerkeys.h" @@ -26,7 +31,11 @@ #include "routerparse.h" #include "routerset.h" #include "test.h" +#include "test_dir_common.h" #include "torcert.h" +#include "relay.h" + +#define NS_MODULE dir static void test_dir_nicknames(void *arg) @@ -110,6 +119,7 @@ test_dir_formats(void *arg) r1->cache_info.published_on = 0; r1->or_port = 9000; r1->dir_port = 9003; + r1->supports_tunnelled_dir_requests = 1; tor_addr_parse(&r1->ipv6_addr, "1:2:3:4::"); r1->ipv6_orport = 9999; r1->onion_pkey = crypto_pk_dup_key(pk1); @@ -154,6 +164,7 @@ test_dir_formats(void *arg) r2->cache_info.published_on = 5; r2->or_port = 9005; r2->dir_port = 0; + r2->supports_tunnelled_dir_requests = 1; r2->onion_pkey = crypto_pk_dup_key(pk2); curve25519_keypair_t r2_onion_keypair; curve25519_keypair_generate(&r2_onion_keypair, 0); @@ -174,7 +185,9 @@ test_dir_formats(void *arg) /* XXXX025 router_dump_to_string should really take this from ri.*/ options->ContactInfo = tor_strdup("Magri White " "<magri@elsewhere.example.com>"); + buf = router_dump_router_to_string(r1, pk2, NULL, NULL, NULL); + tor_free(options->ContactInfo); tt_assert(buf); @@ -200,7 +213,8 @@ test_dir_formats(void *arg) strlcat(buf2, "hidden-service-dir\n", sizeof(buf2)); strlcat(buf2, "contact Magri White <magri@elsewhere.example.com>\n", sizeof(buf2)); - strlcat(buf2, "reject *:*\nrouter-signature\n", sizeof(buf2)); + strlcat(buf2, "reject *:*\n", sizeof(buf2)); + strlcat(buf2, "tunnelled-dir-server\nrouter-signature\n", sizeof(buf2)); buf[strlen(buf2)] = '\0'; /* Don't compare the sig; it's never the same * twice */ @@ -214,12 +228,13 @@ test_dir_formats(void *arg) tt_assert(rp1); tt_int_op(rp1->addr,OP_EQ, r1->addr); tt_int_op(rp1->or_port,OP_EQ, r1->or_port); - //test_eq(rp1->dir_port, r1->dir_port); + tt_int_op(rp1->dir_port,OP_EQ, r1->dir_port); tt_int_op(rp1->bandwidthrate,OP_EQ, r1->bandwidthrate); tt_int_op(rp1->bandwidthburst,OP_EQ, r1->bandwidthburst); tt_int_op(rp1->bandwidthcapacity,OP_EQ, r1->bandwidthcapacity); tt_assert(crypto_pk_cmp_keys(rp1->onion_pkey, pk1) == 0); tt_assert(crypto_pk_cmp_keys(rp1->identity_pkey, pk2) == 0); + tt_assert(rp1->supports_tunnelled_dir_requests); //tt_assert(rp1->exit_policy == NULL); tor_free(buf); @@ -290,6 +305,7 @@ test_dir_formats(void *arg) BASE64_ENCODE_MULTILINE); strlcat(buf2, cert_buf, sizeof(buf2)); strlcat(buf2, "accept *:80\nreject 18.0.0.0/8:24\n", sizeof(buf2)); + strlcat(buf2, "tunnelled-dir-server\n", sizeof(buf2)); strlcat(buf2, "router-sig-ed25519 ", sizeof(buf2)); buf = router_dump_router_to_string(r2, pk1, pk2, &r2_onion_keypair, &kp2); @@ -301,6 +317,8 @@ test_dir_formats(void *arg) tor_free(buf); buf = router_dump_router_to_string(r2, pk1, NULL, NULL, NULL); + + /* Reset for later */ cp = buf; rp2 = router_parse_entry_from_string((const char*)cp,NULL,1,0,NULL,NULL); tt_assert(rp2); @@ -315,6 +333,7 @@ test_dir_formats(void *arg) CURVE25519_PUBKEY_LEN); tt_assert(crypto_pk_cmp_keys(rp2->onion_pkey, pk2) == 0); tt_assert(crypto_pk_cmp_keys(rp2->identity_pkey, pk1) == 0); + tt_assert(rp2->supports_tunnelled_dir_requests); tt_int_op(smartlist_len(rp2->exit_policy),OP_EQ, 2); @@ -478,6 +497,7 @@ test_dir_routerinfo_parsing(void *arg) #undef CHECK_FAIL #undef CHECK_OK done: + memarea_clear_freelist(); routerinfo_free(ri); } @@ -580,6 +600,8 @@ test_dir_extrainfo_parsing(void *arg) #undef CHECK_FAIL done: + escaped(NULL); + memarea_clear_freelist(); extrainfo_free(ei); routerinfo_free(ri); digestmap_free((digestmap_t*)map, routerinfo_free_wrapper_); @@ -1477,13 +1499,6 @@ test_dir_param_voting(void *arg) return; } -extern const char AUTHORITY_CERT_1[]; -extern const char AUTHORITY_SIGNKEY_1[]; -extern const char AUTHORITY_CERT_2[]; -extern const char AUTHORITY_SIGNKEY_2[]; -extern const char AUTHORITY_CERT_3[]; -extern const char AUTHORITY_SIGNKEY_3[]; - /** Helper: Test that two networkstatus_voter_info_t do in fact represent the * same voting authority, and that they do in fact have all the same * information. */ @@ -1503,42 +1518,6 @@ test_same_voter(networkstatus_voter_info_t *v1, ; } -/** Helper: Make a new routerinfo containing the right information for a - * given vote_routerstatus_t. */ -static routerinfo_t * -generate_ri_from_rs(const vote_routerstatus_t *vrs) -{ - routerinfo_t *r; - const routerstatus_t *rs = &vrs->status; - static time_t published = 0; - - r = tor_malloc_zero(sizeof(routerinfo_t)); - r->cert_expiration_time = TIME_MAX; - memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN); - memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest, - DIGEST_LEN); - r->cache_info.do_not_cache = 1; - r->cache_info.routerlist_index = -1; - r->cache_info.signed_descriptor_body = - tor_strdup("123456789012345678901234567890123"); - r->cache_info.signed_descriptor_len = - strlen(r->cache_info.signed_descriptor_body); - r->exit_policy = smartlist_new(); - r->cache_info.published_on = ++published + time(NULL); - if (rs->has_bandwidth) { - /* - * Multiply by 1000 because the routerinfo_t and the routerstatus_t - * seem to use different units (*sigh*) and because we seem stuck on - * icky and perverse decimal kilobytes (*double sigh*) - see - * router_get_advertised_bandwidth_capped() of routerlist.c and - * routerstatus_format_entry() of dirserv.c. - */ - r->bandwidthrate = rs->bandwidth_kb * 1000; - r->bandwidthcapacity = rs->bandwidth_kb * 1000; - } - return r; -} - /** Helper: get a detached signatures document for one or two * consensuses. */ static char * @@ -1556,100 +1535,6 @@ get_detached_sigs(networkstatus_t *ns, networkstatus_t *ns2) return r; } -/** - * Generate a routerstatus for v3_networkstatus test - */ -static vote_routerstatus_t * -gen_routerstatus_for_v3ns(int idx, time_t now) -{ - vote_routerstatus_t *vrs=NULL; - routerstatus_t *rs; - tor_addr_t addr_ipv6; - - switch (idx) { - case 0: - /* Generate the first routerstatus. */ - vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - rs = &vrs->status; - vrs->version = tor_strdup("0.1.2.14"); - rs->published_on = now-1500; - strlcpy(rs->nickname, "router2", sizeof(rs->nickname)); - memset(rs->identity_digest, 3, DIGEST_LEN); - memset(rs->descriptor_digest, 78, DIGEST_LEN); - rs->addr = 0x99008801; - rs->or_port = 443; - rs->dir_port = 8000; - /* all flags but running cleared */ - rs->is_flagged_running = 1; - break; - case 1: - /* Generate the second routerstatus. */ - vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - rs = &vrs->status; - vrs->version = tor_strdup("0.2.0.5"); - rs->published_on = now-1000; - strlcpy(rs->nickname, "router1", sizeof(rs->nickname)); - memset(rs->identity_digest, 5, DIGEST_LEN); - memset(rs->descriptor_digest, 77, DIGEST_LEN); - rs->addr = 0x99009901; - rs->or_port = 443; - rs->dir_port = 0; - tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); - tor_addr_copy(&rs->ipv6_addr, &addr_ipv6); - rs->ipv6_orport = 4711; - rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running = - rs->is_valid = rs->is_possible_guard = 1; - break; - case 2: - /* Generate the third routerstatus. */ - vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - rs = &vrs->status; - vrs->version = tor_strdup("0.1.0.3"); - rs->published_on = now-1000; - strlcpy(rs->nickname, "router3", sizeof(rs->nickname)); - memset(rs->identity_digest, 33, DIGEST_LEN); - memset(rs->descriptor_digest, 79, DIGEST_LEN); - rs->addr = 0xAA009901; - rs->or_port = 400; - rs->dir_port = 9999; - rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = - rs->is_flagged_running = rs->is_valid = - rs->is_possible_guard = 1; - break; - case 3: - /* Generate a fourth routerstatus that is not running. */ - vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); - rs = &vrs->status; - vrs->version = tor_strdup("0.1.6.3"); - rs->published_on = now-1000; - strlcpy(rs->nickname, "router4", sizeof(rs->nickname)); - memset(rs->identity_digest, 34, DIGEST_LEN); - memset(rs->descriptor_digest, 47, DIGEST_LEN); - rs->addr = 0xC0000203; - rs->or_port = 500; - rs->dir_port = 1999; - /* Running flag (and others) cleared */ - break; - case 4: - /* No more for this test; return NULL */ - vrs = NULL; - break; - default: - /* Shouldn't happen */ - tt_assert(0); - } - if (vrs) { - vrs->microdesc = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); - tor_asprintf(&vrs->microdesc->microdesc_hash_line, - "m 9,10,11,12,13,14,15,16,17 " - "sha256=xyzajkldsdsajdadlsdjaslsdksdjlsdjsdaskdaaa%d\n", - idx); - } - - done: - return vrs; -} - /** Apply tweaks to the vote list for each voter */ static int vote_tweaks_for_v3ns(networkstatus_t *v, int voter, time_t now) @@ -1681,7 +1566,7 @@ vote_tweaks_for_v3ns(networkstatus_t *v, int voter, time_t now) vrs = smartlist_get(v->routerstatus_list, 0); memset(vrs->status.descriptor_digest, (int)'Z', DIGEST_LEN); tt_assert(router_add_to_routerlist( - generate_ri_from_rs(vrs), &msg,0,0) >= 0); + dir_common_generate_ri_from_rs(vrs), &msg,0,0) >= 0); } } @@ -1746,11 +1631,11 @@ test_vrs_for_v3ns(vote_routerstatus_t *vrs, int voter, time_t now) tt_assert(tor_addr_eq(&rs->ipv6_addr, &addr_ipv6)); tt_int_op(rs->ipv6_orport,OP_EQ, 4711); if (voter == 1) { - /* all except "authority" (1) and "v2dir" (64) */ - tt_u64_op(vrs->flags, OP_EQ, U64_LITERAL(190)); + /* all except "authority" (1) */ + tt_u64_op(vrs->flags, OP_EQ, U64_LITERAL(254)); } else { - /* 1023 - authority(1) - madeofcheese(16) - madeoftin(32) - v2dir(256) */ - tt_u64_op(vrs->flags, OP_EQ, U64_LITERAL(718)); + /* 1023 - authority(1) - madeofcheese(16) - madeoftin(32) */ + tt_u64_op(vrs->flags, OP_EQ, U64_LITERAL(974)); } } else if (tor_memeq(rs->identity_digest, "\x33\x33\x33\x33\x33\x33\x33\x33\x33\x33" @@ -1820,6 +1705,7 @@ test_routerstatus_for_v3ns(routerstatus_t *rs, time_t now) tt_assert(rs->is_flagged_running); tt_assert(!rs->is_valid); tt_assert(!rs->is_named); + tt_assert(rs->is_v2_dir); /* XXXX check version */ } else if (tor_memeq(rs->identity_digest, "\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5\x5" @@ -1845,6 +1731,7 @@ test_routerstatus_for_v3ns(routerstatus_t *rs, time_t now) tt_assert(rs->is_stable); tt_assert(rs->is_flagged_running); tt_assert(rs->is_valid); + tt_assert(rs->is_v2_dir); tt_assert(!rs->is_named); /* XXXX check version */ } else { @@ -1869,7 +1756,6 @@ test_a_networkstatus( authority_cert_t *cert1=NULL, *cert2=NULL, *cert3=NULL; crypto_pk_t *sign_skey_1=NULL, *sign_skey_2=NULL, *sign_skey_3=NULL; crypto_pk_t *sign_skey_leg1=NULL; - const char *msg=NULL; /* * Sum the non-zero returns from vote_tweaks() we've seen; if vote_tweaks() * returns non-zero, it changed net_params and we should skip the tests for @@ -1885,8 +1771,7 @@ test_a_networkstatus( vote_routerstatus_t *vrs; routerstatus_t *rs; int idx, n_rs, n_vrs; - char *v1_text=NULL, *v2_text=NULL, *v3_text=NULL, *consensus_text=NULL, - *cp=NULL; + char *consensus_text=NULL, *cp=NULL; smartlist_t *votes = smartlist_new(); /* For generating the two other consensuses. */ @@ -1901,79 +1786,13 @@ test_a_networkstatus( tt_assert(rs_test); tt_assert(vrs_test); - /* Parse certificates and keys. */ - cert1 = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); - tt_assert(cert1); - cert2 = authority_cert_parse_from_string(AUTHORITY_CERT_2, NULL); - tt_assert(cert2); - cert3 = authority_cert_parse_from_string(AUTHORITY_CERT_3, NULL); - tt_assert(cert3); - sign_skey_1 = crypto_pk_new(); - sign_skey_2 = crypto_pk_new(); - sign_skey_3 = crypto_pk_new(); + tt_assert(!dir_common_authority_pk_init(&cert1, &cert2, &cert3, + &sign_skey_1, &sign_skey_2, + &sign_skey_3)); sign_skey_leg1 = pk_generate(4); - tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_1, - AUTHORITY_SIGNKEY_1, -1)); - tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_2, - AUTHORITY_SIGNKEY_2, -1)); - tt_assert(!crypto_pk_read_private_key_from_string(sign_skey_3, - AUTHORITY_SIGNKEY_3, -1)); - - tt_assert(!crypto_pk_cmp_keys(sign_skey_1, cert1->signing_key)); - tt_assert(!crypto_pk_cmp_keys(sign_skey_2, cert2->signing_key)); - - /* - * Set up a vote; generate it; try to parse it. - */ - vote = tor_malloc_zero(sizeof(networkstatus_t)); - vote->type = NS_TYPE_VOTE; - vote->published = now; - vote->valid_after = now+1000; - vote->fresh_until = now+2000; - vote->valid_until = now+3000; - vote->vote_seconds = 100; - vote->dist_seconds = 200; - vote->supported_methods = smartlist_new(); - smartlist_split_string(vote->supported_methods, "1 2 3", NULL, 0, -1); - vote->client_versions = tor_strdup("0.1.2.14,0.1.2.15"); - vote->server_versions = tor_strdup("0.1.2.14,0.1.2.15,0.1.2.16"); - vote->known_flags = smartlist_new(); - smartlist_split_string(vote->known_flags, - "Authority Exit Fast Guard Running Stable V2Dir Valid", - 0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); - vote->voters = smartlist_new(); - voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t)); - voter->nickname = tor_strdup("Voter1"); - voter->address = tor_strdup("1.2.3.4"); - voter->addr = 0x01020304; - voter->dir_port = 80; - voter->or_port = 9000; - voter->contact = tor_strdup("voter@example.com"); - crypto_pk_get_digest(cert1->identity_key, voter->identity_digest); - smartlist_add(vote->voters, voter); - vote->cert = authority_cert_dup(cert1); - vote->net_params = smartlist_new(); - smartlist_split_string(vote->net_params, "circuitwindow=101 foo=990", - NULL, 0, 0); - vote->routerstatus_list = smartlist_new(); - /* add routerstatuses */ - idx = 0; - do { - vrs = vrs_gen(idx, now); - if (vrs) { - smartlist_add(vote->routerstatus_list, vrs); - tt_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), - &msg,0,0)>=0); - ++idx; - } - } while (vrs); - n_vrs = idx; - - /* dump the vote and try to parse it. */ - v1_text = format_networkstatus_vote(sign_skey_1, vote); - tt_assert(v1_text); - v1 = networkstatus_parse_vote_from_string(v1_text, NULL, NS_TYPE_VOTE); + tt_assert(!dir_common_construct_vote_1(&vote, cert1, sign_skey_1, vrs_gen, + &v1, &n_vrs, now, 1)); tt_assert(v1); /* Make sure the parsed thing was right. */ @@ -2000,6 +1819,8 @@ test_a_networkstatus( tt_str_op(cp,OP_EQ, "Authority:Exit:Fast:Guard:Running:Stable:V2Dir:Valid"); tor_free(cp); tt_int_op(smartlist_len(v1->routerstatus_list),OP_EQ, n_vrs); + networkstatus_vote_free(vote); + vote = NULL; if (vote_tweaks) params_tweaked += vote_tweaks(v1, 1, now); @@ -2011,33 +1832,10 @@ test_a_networkstatus( } /* Generate second vote. It disagrees on some of the times, - * and doesn't list versions, and knows some crazy flags */ - vote->published = now+1; - vote->fresh_until = now+3005; - vote->dist_seconds = 300; - authority_cert_free(vote->cert); - vote->cert = authority_cert_dup(cert2); - SMARTLIST_FOREACH(vote->net_params, char *, c, tor_free(c)); - smartlist_clear(vote->net_params); - smartlist_split_string(vote->net_params, "bar=2000000000 circuitwindow=20", - NULL, 0, 0); - tor_free(vote->client_versions); - tor_free(vote->server_versions); - voter = smartlist_get(vote->voters, 0); - tor_free(voter->nickname); - tor_free(voter->address); - voter->nickname = tor_strdup("Voter2"); - voter->address = tor_strdup("2.3.4.5"); - voter->addr = 0x02030405; - crypto_pk_get_digest(cert2->identity_key, voter->identity_digest); - smartlist_add(vote->known_flags, tor_strdup("MadeOfCheese")); - smartlist_add(vote->known_flags, tor_strdup("MadeOfTin")); - smartlist_sort_strings(vote->known_flags); - - /* generate and parse v2. */ - v2_text = format_networkstatus_vote(sign_skey_2, vote); - tt_assert(v2_text); - v2 = networkstatus_parse_vote_from_string(v2_text, NULL, NS_TYPE_VOTE); + * and doesn't list versions, and knows some crazy flags. + * Generate and parse v2. */ + tt_assert(!dir_common_construct_vote_2(&vote, cert2, sign_skey_2, vrs_gen, + &v2, &n_vrs, now, 1)); tt_assert(v2); if (vote_tweaks) params_tweaked += vote_tweaks(v2, 2, now); @@ -2055,34 +1853,12 @@ test_a_networkstatus( tt_assert(vrs); vrs_test(vrs, 2, now); } + networkstatus_vote_free(vote); + vote = NULL; - /* Generate the third vote. */ - vote->published = now; - vote->fresh_until = now+2003; - vote->dist_seconds = 250; - authority_cert_free(vote->cert); - vote->cert = authority_cert_dup(cert3); - SMARTLIST_FOREACH(vote->net_params, char *, c, tor_free(c)); - smartlist_clear(vote->net_params); - smartlist_split_string(vote->net_params, "circuitwindow=80 foo=660", - NULL, 0, 0); - smartlist_add(vote->supported_methods, tor_strdup("4")); - vote->client_versions = tor_strdup("0.1.2.14,0.1.2.17"); - vote->server_versions = tor_strdup("0.1.2.10,0.1.2.15,0.1.2.16"); - voter = smartlist_get(vote->voters, 0); - tor_free(voter->nickname); - tor_free(voter->address); - voter->nickname = tor_strdup("Voter3"); - voter->address = tor_strdup("3.4.5.6"); - voter->addr = 0x03040506; - crypto_pk_get_digest(cert3->identity_key, voter->identity_digest); - /* This one has a legacy id. */ - memset(voter->legacy_id_digest, (int)'A', DIGEST_LEN); - - v3_text = format_networkstatus_vote(sign_skey_3, vote); - tt_assert(v3_text); - - v3 = networkstatus_parse_vote_from_string(v3_text, NULL, NS_TYPE_VOTE); + /* Generate the third vote with a legacy id. */ + tt_assert(!dir_common_construct_vote_3(&vote, cert3, sign_skey_3, vrs_gen, + &v3, &n_vrs, now, 1)); tt_assert(v3); if (vote_tweaks) params_tweaked += vote_tweaks(v3, 3, now); @@ -2153,12 +1929,20 @@ test_a_networkstatus( /* Check the routerstatuses. */ n_rs = smartlist_len(con->routerstatus_list); + tt_assert(n_rs); for (idx = 0; idx < n_rs; ++idx) { rs = smartlist_get(con->routerstatus_list, idx); tt_assert(rs); rs_test(rs, now); } + n_rs = smartlist_len(con_md->routerstatus_list); + tt_assert(n_rs); + for (idx = 0; idx < n_rs; ++idx) { + rs = smartlist_get(con_md->routerstatus_list, idx); + tt_assert(rs); + } + /* Check signatures. the first voter is a pseudo-entry with a legacy key. * The second one hasn't signed. The fourth one has signed: validate it. */ voter = smartlist_get(con->voters, 1); @@ -2309,38 +2093,22 @@ test_a_networkstatus( done: tor_free(cp); smartlist_free(votes); - tor_free(v1_text); - tor_free(v2_text); - tor_free(v3_text); tor_free(consensus_text); tor_free(consensus_text_md); - if (vote) - networkstatus_vote_free(vote); - if (v1) - networkstatus_vote_free(v1); - if (v2) - networkstatus_vote_free(v2); - if (v3) - networkstatus_vote_free(v3); - if (con) - networkstatus_vote_free(con); - if (con_md) - networkstatus_vote_free(con_md); - if (sign_skey_1) - crypto_pk_free(sign_skey_1); - if (sign_skey_2) - crypto_pk_free(sign_skey_2); - if (sign_skey_3) - crypto_pk_free(sign_skey_3); - if (sign_skey_leg1) - crypto_pk_free(sign_skey_leg1); - if (cert1) - authority_cert_free(cert1); - if (cert2) - authority_cert_free(cert2); - if (cert3) - authority_cert_free(cert3); + networkstatus_vote_free(vote); + networkstatus_vote_free(v1); + networkstatus_vote_free(v2); + networkstatus_vote_free(v3); + networkstatus_vote_free(con); + networkstatus_vote_free(con_md); + crypto_pk_free(sign_skey_1); + crypto_pk_free(sign_skey_2); + crypto_pk_free(sign_skey_3); + crypto_pk_free(sign_skey_leg1); + authority_cert_free(cert1); + authority_cert_free(cert2); + authority_cert_free(cert3); tor_free(consensus_text2); tor_free(consensus_text3); @@ -2348,18 +2116,13 @@ test_a_networkstatus( tor_free(consensus_text_md3); tor_free(detached_text1); tor_free(detached_text2); - if (con2) - networkstatus_vote_free(con2); - if (con3) - networkstatus_vote_free(con3); - if (con_md2) - networkstatus_vote_free(con_md2); - if (con_md3) - networkstatus_vote_free(con_md3); - if (dsig1) - ns_detached_signatures_free(dsig1); - if (dsig2) - ns_detached_signatures_free(dsig2); + + networkstatus_vote_free(con2); + networkstatus_vote_free(con3); + networkstatus_vote_free(con_md2); + networkstatus_vote_free(con_md3); + ns_detached_signatures_free(dsig1); + ns_detached_signatures_free(dsig2); } /** Run unit tests for generating and parsing V3 consensus networkstatus @@ -2368,7 +2131,7 @@ static void test_dir_v3_networkstatus(void *arg) { (void)arg; - test_a_networkstatus(gen_routerstatus_for_v3ns, + test_a_networkstatus(dir_common_gen_routerstatus_for_v3ns, vote_tweaks_for_v3ns, test_vrs_for_v3ns, test_consensus_for_v3ns, @@ -2965,6 +2728,7 @@ test_dir_fmt_control_ns(void *arg) rs.is_fast = 1; rs.is_flagged_running = 1; rs.has_bandwidth = 1; + rs.is_v2_dir = 1; rs.bandwidth_kb = 1000; s = networkstatus_getinfo_helper_single(&rs); @@ -3340,12 +3104,33 @@ static void test_dir_fetch_type(void *arg) { (void)arg; - tt_assert(dir_fetch_type(DIR_PURPOSE_FETCH_MICRODESC, ROUTER_PURPOSE_GENERAL, - NULL) == MICRODESC_DIRINFO); - tt_assert(dir_fetch_type(DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_BRIDGE, - NULL) == BRIDGE_DIRINFO); - tt_assert(dir_fetch_type(DIR_PURPOSE_FETCH_CONSENSUS, ROUTER_PURPOSE_GENERAL, - "microdesc") == (V3_DIRINFO | MICRODESC_DIRINFO)); + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_EXTRAINFO, ROUTER_PURPOSE_BRIDGE, + NULL), OP_EQ, EXTRAINFO_DIRINFO | BRIDGE_DIRINFO); + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_EXTRAINFO, ROUTER_PURPOSE_GENERAL, + NULL), OP_EQ, EXTRAINFO_DIRINFO | V3_DIRINFO); + + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_BRIDGE, + NULL), OP_EQ, BRIDGE_DIRINFO); + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_SERVERDESC, + ROUTER_PURPOSE_GENERAL, NULL), OP_EQ, V3_DIRINFO); + + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_STATUS_VOTE, + ROUTER_PURPOSE_GENERAL, NULL), OP_EQ, V3_DIRINFO); + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, + ROUTER_PURPOSE_GENERAL, NULL), OP_EQ, V3_DIRINFO); + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_CERTIFICATE, + ROUTER_PURPOSE_GENERAL, NULL), OP_EQ, V3_DIRINFO); + + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_CONSENSUS, ROUTER_PURPOSE_GENERAL, + "microdesc"), OP_EQ, V3_DIRINFO|MICRODESC_DIRINFO); + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_CONSENSUS, ROUTER_PURPOSE_GENERAL, + NULL), OP_EQ, V3_DIRINFO); + + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_MICRODESC, ROUTER_PURPOSE_GENERAL, + NULL), OP_EQ, MICRODESC_DIRINFO); + + tt_int_op(dir_fetch_type(DIR_PURPOSE_FETCH_RENDDESC_V2, + ROUTER_PURPOSE_GENERAL, NULL), OP_EQ, NO_DIRINFO); done: ; } @@ -3494,6 +3279,770 @@ test_dir_packages(void *arg) tor_free(res); } +static void +test_dir_download_status_schedule(void *arg) +{ + (void)arg; + download_status_t dls_failure = { 0, 0, 0, DL_SCHED_GENERIC, + DL_WANT_AUTHORITY, + DL_SCHED_INCREMENT_FAILURE }; + download_status_t dls_attempt = { 0, 0, 0, DL_SCHED_CONSENSUS, + DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_ATTEMPT}; + download_status_t dls_bridge = { 0, 0, 0, DL_SCHED_BRIDGE, + DL_WANT_AUTHORITY, + DL_SCHED_INCREMENT_FAILURE}; + int increment = -1; + int expected_increment = -1; + time_t current_time = time(NULL); + int delay1 = -1; + int delay2 = -1; + smartlist_t *schedule = smartlist_new(); + + /* Make a dummy schedule */ + smartlist_add(schedule, (void *)&delay1); + smartlist_add(schedule, (void *)&delay2); + + /* check a range of values */ + delay1 = 1000; + increment = download_status_schedule_get_delay(&dls_failure, + schedule, + TIME_MIN); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_failure.next_attempt_at == TIME_MIN + expected_increment); + +#if TIME_T_IS_SIGNED + delay1 = INT_MAX; + increment = download_status_schedule_get_delay(&dls_failure, + schedule, + -1); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_failure.next_attempt_at == TIME_MAX); +#endif + + delay1 = 0; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + 0); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == 0 + expected_increment); + + delay1 = 1000; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + 1); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == 1 + expected_increment); + + delay1 = INT_MAX; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + current_time); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == TIME_MAX); + + delay1 = 1; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + TIME_MAX); + expected_increment = delay1; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == TIME_MAX); + + /* see what happens when we reach the end */ + dls_attempt.n_download_attempts++; + dls_bridge.n_download_failures++; + + delay2 = 100; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + current_time); + expected_increment = delay2; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == current_time + delay2); + + delay2 = 1; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + current_time); + expected_increment = delay2; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == current_time + delay2); + + /* see what happens when we try to go off the end */ + dls_attempt.n_download_attempts++; + dls_bridge.n_download_failures++; + + delay2 = 5; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + current_time); + expected_increment = delay2; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == current_time + delay2); + + delay2 = 17; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + current_time); + expected_increment = delay2; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == current_time + delay2); + + /* see what happens when we reach IMPOSSIBLE_TO_DOWNLOAD */ + dls_attempt.n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD; + dls_bridge.n_download_failures = IMPOSSIBLE_TO_DOWNLOAD; + + delay2 = 35; + increment = download_status_schedule_get_delay(&dls_attempt, + schedule, + current_time); + expected_increment = INT_MAX; + tt_assert(increment == expected_increment); + tt_assert(dls_attempt.next_attempt_at == TIME_MAX); + + delay2 = 99; + increment = download_status_schedule_get_delay(&dls_bridge, + schedule, + current_time); + expected_increment = INT_MAX; + tt_assert(increment == expected_increment); + tt_assert(dls_bridge.next_attempt_at == TIME_MAX); + + done: + /* the pointers in schedule are allocated on the stack */ + smartlist_free(schedule); +} + +static void +test_dir_download_status_increment(void *arg) +{ + (void)arg; + download_status_t dls_failure = { 0, 0, 0, DL_SCHED_GENERIC, + DL_WANT_AUTHORITY, + DL_SCHED_INCREMENT_FAILURE }; + download_status_t dls_attempt = { 0, 0, 0, DL_SCHED_BRIDGE, + DL_WANT_ANY_DIRSERVER, + DL_SCHED_INCREMENT_ATTEMPT}; + int delay0 = -1; + int delay1 = -1; + int delay2 = -1; + smartlist_t *schedule = smartlist_new(); + or_options_t test_options; + time_t next_at = TIME_MAX; + time_t current_time = time(NULL); + + /* Provide some values for the schedule */ + delay0 = 10; + delay1 = 99; + delay2 = 20; + + /* Make the schedule */ + smartlist_add(schedule, (void *)&delay0); + smartlist_add(schedule, (void *)&delay1); + smartlist_add(schedule, (void *)&delay2); + + /* Put it in the options */ + mock_options = &test_options; + reset_options(mock_options, &mock_get_options_calls); + mock_options->TestingClientDownloadSchedule = schedule; + mock_options->TestingBridgeDownloadSchedule = schedule; + + MOCK(get_options, mock_get_options); + + /* Check that a failure reset works */ + mock_get_options_calls = 0; + download_status_reset(&dls_failure); + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_failure) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_failure) + != TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) == 0); + tt_assert(download_status_get_n_attempts(&dls_failure) == 0); + tt_assert(mock_get_options_calls >= 1); + + /* avoid timing inconsistencies */ + dls_failure.next_attempt_at = current_time + delay0; + + /* check that a reset schedule becomes ready at the right time */ + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay0 - 1, + 1) == 0); + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay0, + 1) == 1); + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay0 + 1, + 1) == 1); + + /* Check that a failure increment works */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, + current_time); + tt_assert(next_at == current_time + delay1); + tt_assert(download_status_get_n_failures(&dls_failure) == 1); + tt_assert(download_status_get_n_attempts(&dls_failure) == 1); + tt_assert(mock_get_options_calls >= 1); + + /* check that an incremented schedule becomes ready at the right time */ + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay1 - 1, + 1) == 0); + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay1, + 1) == 1); + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay1 + 1, + 1) == 1); + + /* check that a schedule isn't ready if it's had too many failures */ + tt_assert(download_status_is_ready(&dls_failure, + current_time + delay1 + 10, + 0) == 0); + + /* Check that failure increments don't happen on 503 for clients, but that + * attempt increments do. */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_failure, 503, "test", 0, + current_time); + tt_assert(next_at == current_time + delay1); + tt_assert(download_status_get_n_failures(&dls_failure) == 1); + tt_assert(download_status_get_n_attempts(&dls_failure) == 2); + tt_assert(mock_get_options_calls >= 1); + + /* Check that failure increments do happen on 503 for servers */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_failure, 503, "test", 1, + current_time); + tt_assert(next_at == current_time + delay2); + tt_assert(download_status_get_n_failures(&dls_failure) == 2); + tt_assert(download_status_get_n_attempts(&dls_failure) == 3); + tt_assert(mock_get_options_calls >= 1); + + /* Check what happens when we run off the end of the schedule */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, + current_time); + tt_assert(next_at == current_time + delay2); + tt_assert(download_status_get_n_failures(&dls_failure) == 3); + tt_assert(download_status_get_n_attempts(&dls_failure) == 4); + tt_assert(mock_get_options_calls >= 1); + + /* Check what happens when we hit the failure limit */ + mock_get_options_calls = 0; + download_status_mark_impossible(&dls_failure); + next_at = download_status_increment_failure(&dls_failure, 404, "test", 0, + current_time); + tt_assert(next_at == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(download_status_get_n_attempts(&dls_failure) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(mock_get_options_calls >= 1); + + /* Check that a failure reset doesn't reset at the limit */ + mock_get_options_calls = 0; + download_status_reset(&dls_failure); + tt_assert(download_status_get_next_attempt_at(&dls_failure) + == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(download_status_get_n_attempts(&dls_failure) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(mock_get_options_calls == 0); + + /* Check that a failure reset resets just before the limit */ + mock_get_options_calls = 0; + dls_failure.n_download_failures = IMPOSSIBLE_TO_DOWNLOAD - 1; + dls_failure.n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD - 1; + download_status_reset(&dls_failure); + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_failure) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_failure) + != TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) == 0); + tt_assert(download_status_get_n_attempts(&dls_failure) == 0); + tt_assert(mock_get_options_calls >= 1); + + /* Check that failure increments do happen on attempt-based schedules, + * but that the retry is set at the end of time */ + mock_get_options_calls = 0; + next_at = download_status_increment_failure(&dls_attempt, 404, "test", 0, + current_time); + tt_assert(next_at == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) == 1); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); + tt_assert(mock_get_options_calls == 0); + + /* Check that an attempt reset works */ + mock_get_options_calls = 0; + download_status_reset(&dls_attempt); + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + != TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); + tt_assert(mock_get_options_calls >= 1); + + /* avoid timing inconsistencies */ + dls_attempt.next_attempt_at = current_time + delay0; + + /* check that a reset schedule becomes ready at the right time */ + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay0 - 1, + 1) == 0); + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay0, + 1) == 1); + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay0 + 1, + 1) == 1); + + /* Check that an attempt increment works */ + mock_get_options_calls = 0; + next_at = download_status_increment_attempt(&dls_attempt, "test", + current_time); + tt_assert(next_at == current_time + delay1); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 1); + tt_assert(mock_get_options_calls >= 1); + + /* check that an incremented schedule becomes ready at the right time */ + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay1 - 1, + 1) == 0); + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay1, + 1) == 1); + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay1 + 1, + 1) == 1); + + /* check that a schedule isn't ready if it's had too many attempts */ + tt_assert(download_status_is_ready(&dls_attempt, + current_time + delay1 + 10, + 0) == 0); + + /* Check what happens when we reach then run off the end of the schedule */ + mock_get_options_calls = 0; + next_at = download_status_increment_attempt(&dls_attempt, "test", + current_time); + tt_assert(next_at == current_time + delay2); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 2); + tt_assert(mock_get_options_calls >= 1); + + mock_get_options_calls = 0; + next_at = download_status_increment_attempt(&dls_attempt, "test", + current_time); + tt_assert(next_at == current_time + delay2); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 3); + tt_assert(mock_get_options_calls >= 1); + + /* Check what happens when we hit the attempt limit */ + mock_get_options_calls = 0; + download_status_mark_impossible(&dls_attempt); + next_at = download_status_increment_attempt(&dls_attempt, "test", + current_time); + tt_assert(next_at == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(download_status_get_n_attempts(&dls_attempt) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(mock_get_options_calls >= 1); + + /* Check that an attempt reset doesn't reset at the limit */ + mock_get_options_calls = 0; + download_status_reset(&dls_attempt); + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(download_status_get_n_attempts(&dls_attempt) + == IMPOSSIBLE_TO_DOWNLOAD); + tt_assert(mock_get_options_calls == 0); + + /* Check that an attempt reset resets just before the limit */ + mock_get_options_calls = 0; + dls_attempt.n_download_failures = IMPOSSIBLE_TO_DOWNLOAD - 1; + dls_attempt.n_download_attempts = IMPOSSIBLE_TO_DOWNLOAD - 1; + download_status_reset(&dls_attempt); + /* we really want to test that it's equal to time(NULL) + delay0, but that's + * an unrealiable test, because time(NULL) might change. */ + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + >= current_time + delay0); + tt_assert(download_status_get_next_attempt_at(&dls_attempt) + != TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_attempt) == 0); + tt_assert(download_status_get_n_attempts(&dls_attempt) == 0); + tt_assert(mock_get_options_calls >= 1); + + /* Check that attempt increments don't happen on failure-based schedules, + * and that the attempt is set at the end of time */ + mock_get_options_calls = 0; + next_at = download_status_increment_attempt(&dls_failure, "test", + current_time); + tt_assert(next_at == TIME_MAX); + tt_assert(download_status_get_n_failures(&dls_failure) == 0); + tt_assert(download_status_get_n_attempts(&dls_failure) == 0); + tt_assert(mock_get_options_calls == 0); + + done: + /* the pointers in schedule are allocated on the stack */ + smartlist_free(schedule); + UNMOCK(get_options); + mock_options = NULL; + mock_get_options_calls = 0; +} + +static void +test_dir_authdir_type_to_string(void *data) +{ + (void)data; + char *res; + + tt_str_op(res = authdir_type_to_string(NO_DIRINFO), OP_EQ, + "[Not an authority]"); + tor_free(res); + + tt_str_op(res = authdir_type_to_string(EXTRAINFO_DIRINFO), OP_EQ, + "[Not an authority]"); + tor_free(res); + + tt_str_op(res = authdir_type_to_string(MICRODESC_DIRINFO), OP_EQ, + "[Not an authority]"); + tor_free(res); + + tt_str_op(res = authdir_type_to_string(V3_DIRINFO), OP_EQ, "V3"); + tor_free(res); + + tt_str_op(res = authdir_type_to_string(BRIDGE_DIRINFO), OP_EQ, "Bridge"); + tor_free(res); + + tt_str_op(res = authdir_type_to_string( + V3_DIRINFO | BRIDGE_DIRINFO | EXTRAINFO_DIRINFO), OP_EQ, + "V3, Bridge"); + done: + tor_free(res); +} + +static void +test_dir_conn_purpose_to_string(void *data) +{ + (void)data; + +#define EXPECT_CONN_PURPOSE(purpose, expected) \ + tt_str_op(dir_conn_purpose_to_string(purpose), OP_EQ, expected); + + EXPECT_CONN_PURPOSE(DIR_PURPOSE_UPLOAD_DIR, "server descriptor upload"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_UPLOAD_VOTE, "server vote upload"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_UPLOAD_SIGNATURES, + "consensus signature upload"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_FETCH_SERVERDESC, "server descriptor fetch"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_FETCH_EXTRAINFO, "extra-info fetch"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_FETCH_CONSENSUS, + "consensus network-status fetch"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_FETCH_CERTIFICATE, "authority cert fetch"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_FETCH_STATUS_VOTE, "status vote fetch"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, + "consensus signature fetch"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_FETCH_RENDDESC_V2, + "hidden-service v2 descriptor fetch"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_UPLOAD_RENDDESC_V2, + "hidden-service v2 descriptor upload"); + EXPECT_CONN_PURPOSE(DIR_PURPOSE_FETCH_MICRODESC, "microdescriptor fetch"); + EXPECT_CONN_PURPOSE(1024, "(unknown)"); + + done: ; +} + +NS_DECL(int, +public_server_mode, (const or_options_t *options)); + +static int +NS(public_server_mode)(const or_options_t *options) +{ + (void)options; + + if (CALLED(public_server_mode)++ == 0) { + return 1; + } + + return 0; +} + +static void +test_dir_should_use_directory_guards(void *data) +{ + or_options_t *options; + char *errmsg = NULL; + (void)data; + + NS_MOCK(public_server_mode); + + options = options_new(); + options_init(options); + + tt_int_op(should_use_directory_guards(options), OP_EQ, 0); + tt_int_op(CALLED(public_server_mode), OP_EQ, 1); + + options->UseEntryGuardsAsDirGuards = 1; + options->UseEntryGuards = 1; + options->DownloadExtraInfo = 0; + options->FetchDirInfoEarly = 0; + options->FetchDirInfoExtraEarly = 0; + options->FetchUselessDescriptors = 0; + tt_int_op(should_use_directory_guards(options), OP_EQ, 1); + tt_int_op(CALLED(public_server_mode), OP_EQ, 2); + + options->UseEntryGuards = 0; + tt_int_op(should_use_directory_guards(options), OP_EQ, 0); + tt_int_op(CALLED(public_server_mode), OP_EQ, 3); + options->UseEntryGuards = 1; + + options->UseEntryGuardsAsDirGuards = 0; + tt_int_op(should_use_directory_guards(options), OP_EQ, 0); + tt_int_op(CALLED(public_server_mode), OP_EQ, 4); + options->UseEntryGuardsAsDirGuards = 1; + + options->DownloadExtraInfo = 1; + tt_int_op(should_use_directory_guards(options), OP_EQ, 0); + tt_int_op(CALLED(public_server_mode), OP_EQ, 5); + options->DownloadExtraInfo = 0; + + options->FetchDirInfoEarly = 1; + tt_int_op(should_use_directory_guards(options), OP_EQ, 0); + tt_int_op(CALLED(public_server_mode), OP_EQ, 6); + options->FetchDirInfoEarly = 0; + + options->FetchDirInfoExtraEarly = 1; + tt_int_op(should_use_directory_guards(options), OP_EQ, 0); + tt_int_op(CALLED(public_server_mode), OP_EQ, 7); + options->FetchDirInfoExtraEarly = 0; + + options->FetchUselessDescriptors = 1; + tt_int_op(should_use_directory_guards(options), OP_EQ, 0); + tt_int_op(CALLED(public_server_mode), OP_EQ, 8); + options->FetchUselessDescriptors = 0; + + done: + NS_UNMOCK(public_server_mode); + or_options_free(options); + tor_free(errmsg); +} + +NS_DECL(void, +directory_initiate_command_routerstatus, (const routerstatus_t *status, + uint8_t dir_purpose, + uint8_t router_purpose, + dir_indirection_t indirection, + const char *resource, + const char *payload, + size_t payload_len, + time_t if_modified_since)); + +static void +test_dir_should_not_init_request_to_ourselves(void *data) +{ + char digest[DIGEST_LEN]; + dir_server_t *ourself = NULL; + crypto_pk_t *key = pk_generate(2); + (void) data; + + NS_MOCK(directory_initiate_command_routerstatus); + + clear_dir_servers(); + routerlist_free_all(); + + set_server_identity_key(key); + crypto_pk_get_digest(key, (char*) &digest); + ourself = trusted_dir_server_new("ourself", "127.0.0.1", 9059, 9060, + NULL, digest, + NULL, V3_DIRINFO, 1.0); + + tt_assert(ourself); + dir_server_add(ourself); + + directory_get_from_all_authorities(DIR_PURPOSE_FETCH_STATUS_VOTE, 0, NULL); + tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 0); + + directory_get_from_all_authorities(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, 0, + NULL); + + tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 0); + + done: + NS_UNMOCK(directory_initiate_command_routerstatus); + clear_dir_servers(); + routerlist_free_all(); + crypto_pk_free(key); +} + +static void +test_dir_should_not_init_request_to_dir_auths_without_v3_info(void *data) +{ + dir_server_t *ds = NULL; + dirinfo_type_t dirinfo_type = BRIDGE_DIRINFO | EXTRAINFO_DIRINFO \ + | MICRODESC_DIRINFO; + (void) data; + + NS_MOCK(directory_initiate_command_routerstatus); + + clear_dir_servers(); + routerlist_free_all(); + + ds = trusted_dir_server_new("ds", "10.0.0.1", 9059, 9060, NULL, + "12345678901234567890", NULL, dirinfo_type, 1.0); + tt_assert(ds); + dir_server_add(ds); + + directory_get_from_all_authorities(DIR_PURPOSE_FETCH_STATUS_VOTE, 0, NULL); + tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 0); + + directory_get_from_all_authorities(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, 0, + NULL); + tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 0); + + done: + NS_UNMOCK(directory_initiate_command_routerstatus); + clear_dir_servers(); + routerlist_free_all(); +} + +static void +test_dir_should_init_request_to_dir_auths(void *data) +{ + dir_server_t *ds = NULL; + (void) data; + + NS_MOCK(directory_initiate_command_routerstatus); + + clear_dir_servers(); + routerlist_free_all(); + + ds = trusted_dir_server_new("ds", "10.0.0.1", 9059, 9060, NULL, + "12345678901234567890", NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + dir_server_add(ds); + + directory_get_from_all_authorities(DIR_PURPOSE_FETCH_STATUS_VOTE, 0, NULL); + tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 1); + + directory_get_from_all_authorities(DIR_PURPOSE_FETCH_DETACHED_SIGNATURES, 0, + NULL); + tt_int_op(CALLED(directory_initiate_command_routerstatus), OP_EQ, 2); + + done: + NS_UNMOCK(directory_initiate_command_routerstatus); + clear_dir_servers(); + routerlist_free_all(); +} + +void +NS(directory_initiate_command_routerstatus)(const routerstatus_t *status, + uint8_t dir_purpose, + uint8_t router_purpose, + dir_indirection_t indirection, + const char *resource, + const char *payload, + size_t payload_len, + time_t if_modified_since) +{ + (void)status; + (void)dir_purpose; + (void)router_purpose; + (void)indirection; + (void)resource; + (void)payload; + (void)payload_len; + (void)if_modified_since; + CALLED(directory_initiate_command_routerstatus)++; +} + +static void +test_dir_choose_compression_level(void* data) +{ + (void)data; + + /* It starts under_memory_pressure */ + tt_int_op(have_been_under_memory_pressure(), OP_EQ, 1); + + tt_assert(HIGH_COMPRESSION == choose_compression_level(-1)); + tt_assert(LOW_COMPRESSION == choose_compression_level(1024-1)); + tt_assert(MEDIUM_COMPRESSION == choose_compression_level(2048-1)); + tt_assert(HIGH_COMPRESSION == choose_compression_level(2048)); + + /* Reset under_memory_pressure timer */ + cell_queues_check_size(); + tt_int_op(have_been_under_memory_pressure(), OP_EQ, 0); + + tt_assert(HIGH_COMPRESSION == choose_compression_level(-1)); + tt_assert(HIGH_COMPRESSION == choose_compression_level(1024-1)); + tt_assert(HIGH_COMPRESSION == choose_compression_level(2048-1)); + tt_assert(HIGH_COMPRESSION == choose_compression_level(2048)); + + done: ; +} + +static void +test_dir_find_dl_schedule(void* data) +{ + download_status_t dls; + smartlist_t server, client, server_cons, client_cons, bridge; + (void)data; + + mock_options = malloc(sizeof(or_options_t)); + reset_options(mock_options, &mock_get_options_calls); + MOCK(get_options, mock_get_options); + + mock_options->TestingServerDownloadSchedule = &server; + mock_options->TestingClientDownloadSchedule = &client; + mock_options->TestingServerConsensusDownloadSchedule = &server_cons; + mock_options->TestingClientConsensusDownloadSchedule = &client_cons; + mock_options->TestingBridgeDownloadSchedule = &bridge; + + dls.schedule = DL_SCHED_GENERIC; + mock_options->ClientOnly = 1; + tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &client); + mock_options->ClientOnly = 0; + mock_options->DirPort_set = 1; + mock_options->ORPort_set = 1; + mock_options->DirCache = 1; + tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &server); + +#if 0 + dls.schedule = DL_SCHED_CONSENSUS; + mock_options->ClientOnly = 1; + mock_options->DirCache = 0; + tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &client_cons); + mock_options->ClientOnly = 0; + mock_options->DirCache = 1; + tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &server_cons); +#endif + + dls.schedule = DL_SCHED_BRIDGE; + mock_options->ClientOnly = 1; + tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &bridge); + mock_options->ClientOnly = 0; + tt_ptr_op(find_dl_schedule(&dls, mock_options), OP_EQ, &bridge); + + done: + UNMOCK(get_options); +} + #define DIR_LEGACY(name) \ { #name, test_dir_ ## name , TT_FORK, NULL, NULL } @@ -3525,6 +4074,16 @@ struct testcase_t dir_tests[] = { DIR(purpose_needs_anonymity, 0), DIR(fetch_type, 0), DIR(packages, 0), + DIR(download_status_schedule, 0), + DIR(download_status_increment, 0), + DIR(authdir_type_to_string, 0), + DIR(conn_purpose_to_string, 0), + DIR(should_use_directory_guards, 0), + DIR(should_not_init_request_to_ourselves, TT_FORK), + DIR(should_not_init_request_to_dir_auths_without_v3_info, 0), + DIR(should_init_request_to_dir_auths, 0), + DIR(choose_compression_level, 0), + DIR(find_dl_schedule, 0), END_OF_TESTCASES }; diff --git a/src/test/test_dir_common.c b/src/test/test_dir_common.c new file mode 100644 index 0000000000..f9e97c973c --- /dev/null +++ b/src/test/test_dir_common.c @@ -0,0 +1,425 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#define DIRVOTE_PRIVATE +#include "crypto.h" +#include "test.h" +#include "container.h" +#include "or.h" +#include "dirvote.h" +#include "nodelist.h" +#include "routerlist.h" +#include "test_dir_common.h" + +void dir_common_setup_vote(networkstatus_t **vote, time_t now); +networkstatus_t * dir_common_add_rs_and_parse(networkstatus_t *vote, + networkstatus_t **vote_out, + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + crypto_pk_t *sign_skey, int *n_vrs, + time_t now, int clear_rl); + +extern const char AUTHORITY_CERT_1[]; +extern const char AUTHORITY_SIGNKEY_1[]; +extern const char AUTHORITY_CERT_2[]; +extern const char AUTHORITY_SIGNKEY_2[]; +extern const char AUTHORITY_CERT_3[]; +extern const char AUTHORITY_SIGNKEY_3[]; + +/** Initialize and set auth certs and keys + * Returns 0 on success, -1 on failure. Clean up handled by caller. + */ +int +dir_common_authority_pk_init(authority_cert_t **cert1, + authority_cert_t **cert2, + authority_cert_t **cert3, + crypto_pk_t **sign_skey_1, + crypto_pk_t **sign_skey_2, + crypto_pk_t **sign_skey_3) +{ + /* Parse certificates and keys. */ + authority_cert_t *cert; + cert = authority_cert_parse_from_string(AUTHORITY_CERT_1, NULL); + tt_assert(cert); + tt_assert(cert->identity_key); + *cert1 = cert; + tt_assert(*cert1); + *cert2 = authority_cert_parse_from_string(AUTHORITY_CERT_2, NULL); + tt_assert(*cert2); + *cert3 = authority_cert_parse_from_string(AUTHORITY_CERT_3, NULL); + tt_assert(*cert3); + *sign_skey_1 = crypto_pk_new(); + *sign_skey_2 = crypto_pk_new(); + *sign_skey_3 = crypto_pk_new(); + + tt_assert(!crypto_pk_read_private_key_from_string(*sign_skey_1, + AUTHORITY_SIGNKEY_1, -1)); + tt_assert(!crypto_pk_read_private_key_from_string(*sign_skey_2, + AUTHORITY_SIGNKEY_2, -1)); + tt_assert(!crypto_pk_read_private_key_from_string(*sign_skey_3, + AUTHORITY_SIGNKEY_3, -1)); + + tt_assert(!crypto_pk_cmp_keys(*sign_skey_1, (*cert1)->signing_key)); + tt_assert(!crypto_pk_cmp_keys(*sign_skey_2, (*cert2)->signing_key)); + + return 0; + done: + return -1; +} + +/** + * Generate a routerstatus for v3_networkstatus test. + */ +vote_routerstatus_t * +dir_common_gen_routerstatus_for_v3ns(int idx, time_t now) +{ + vote_routerstatus_t *vrs=NULL; + routerstatus_t *rs = NULL; + tor_addr_t addr_ipv6; + char *method_list = NULL; + + switch (idx) { + case 0: + /* Generate the first routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.2.14"); + rs->published_on = now-1500; + strlcpy(rs->nickname, "router2", sizeof(rs->nickname)); + memset(rs->identity_digest, TEST_DIR_ROUTER_ID_1, DIGEST_LEN); + memset(rs->descriptor_digest, TEST_DIR_ROUTER_DD_1, DIGEST_LEN); + rs->addr = 0x99008801; + rs->or_port = 443; + rs->dir_port = 8000; + /* all flags but running and v2dir cleared */ + rs->is_flagged_running = 1; + rs->is_v2_dir = 1; + break; + case 1: + /* Generate the second routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.2.0.5"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router1", sizeof(rs->nickname)); + memset(rs->identity_digest, TEST_DIR_ROUTER_ID_2, DIGEST_LEN); + memset(rs->descriptor_digest, TEST_DIR_ROUTER_DD_2, DIGEST_LEN); + rs->addr = 0x99009901; + rs->or_port = 443; + rs->dir_port = 0; + tor_addr_parse(&addr_ipv6, "[1:2:3::4]"); + tor_addr_copy(&rs->ipv6_addr, &addr_ipv6); + rs->ipv6_orport = 4711; + rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running = + rs->is_valid = rs->is_possible_guard = rs->is_v2_dir = 1; + break; + case 2: + /* Generate the third routerstatus. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.0.3"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router3", sizeof(rs->nickname)); + memset(rs->identity_digest, TEST_DIR_ROUTER_ID_3, DIGEST_LEN); + memset(rs->descriptor_digest, TEST_DIR_ROUTER_DD_3, DIGEST_LEN); + rs->addr = 0xAA009901; + rs->or_port = 400; + rs->dir_port = 9999; + rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = + rs->is_flagged_running = rs->is_valid = rs->is_v2_dir = + rs->is_possible_guard = 1; + break; + case 3: + /* Generate a fourth routerstatus that is not running. */ + vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); + rs = &vrs->status; + vrs->version = tor_strdup("0.1.6.3"); + rs->published_on = now-1000; + strlcpy(rs->nickname, "router4", sizeof(rs->nickname)); + memset(rs->identity_digest, TEST_DIR_ROUTER_ID_4, DIGEST_LEN); + memset(rs->descriptor_digest, TEST_DIR_ROUTER_DD_4, DIGEST_LEN); + rs->addr = 0xC0000203; + rs->or_port = 500; + rs->dir_port = 1999; + rs->is_v2_dir = 1; + /* Running flag (and others) cleared */ + break; + case 4: + /* No more for this test; return NULL */ + vrs = NULL; + break; + default: + /* Shouldn't happen */ + tt_assert(0); + } + if (vrs) { + vrs->microdesc = tor_malloc_zero(sizeof(vote_microdesc_hash_t)); + method_list = make_consensus_method_list(MIN_SUPPORTED_CONSENSUS_METHOD, + MAX_SUPPORTED_CONSENSUS_METHOD, + ","); + tor_asprintf(&vrs->microdesc->microdesc_hash_line, + "m %s " + "sha256=xyzajkldsdsajdadlsdjaslsdksdjlsdjsdaskdaaa%d\n", + method_list, idx); + } + + done: + tor_free(method_list); + return vrs; +} + +/** Initialize networkstatus vote object attributes. */ +void +dir_common_setup_vote(networkstatus_t **vote, time_t now) +{ + *vote = tor_malloc_zero(sizeof(networkstatus_t)); + (*vote)->type = NS_TYPE_VOTE; + (*vote)->published = now; + (*vote)->supported_methods = smartlist_new(); + (*vote)->known_flags = smartlist_new(); + (*vote)->net_params = smartlist_new(); + (*vote)->routerstatus_list = smartlist_new(); + (*vote)->voters = smartlist_new(); +} + +/** Helper: Make a new routerinfo containing the right information for a + * given vote_routerstatus_t. */ +routerinfo_t * +dir_common_generate_ri_from_rs(const vote_routerstatus_t *vrs) +{ + routerinfo_t *r; + const routerstatus_t *rs = &vrs->status; + static time_t published = 0; + + r = tor_malloc_zero(sizeof(routerinfo_t)); + r->cert_expiration_time = TIME_MAX; + memcpy(r->cache_info.identity_digest, rs->identity_digest, DIGEST_LEN); + memcpy(r->cache_info.signed_descriptor_digest, rs->descriptor_digest, + DIGEST_LEN); + r->cache_info.do_not_cache = 1; + r->cache_info.routerlist_index = -1; + r->cache_info.signed_descriptor_body = + tor_strdup("123456789012345678901234567890123"); + r->cache_info.signed_descriptor_len = + strlen(r->cache_info.signed_descriptor_body); + r->exit_policy = smartlist_new(); + r->cache_info.published_on = ++published + time(NULL); + if (rs->has_bandwidth) { + /* + * Multiply by 1000 because the routerinfo_t and the routerstatus_t + * seem to use different units (*sigh*) and because we seem stuck on + * icky and perverse decimal kilobytes (*double sigh*) - see + * router_get_advertised_bandwidth_capped() of routerlist.c and + * routerstatus_format_entry() of dirserv.c. + */ + r->bandwidthrate = rs->bandwidth_kb * 1000; + r->bandwidthcapacity = rs->bandwidth_kb * 1000; + } + return r; +} + +/** Create routerstatuses and signed vote. + * Create routerstatuses using *vrs_gen* and add them to global routerlist. + * Next, create signed vote using *sign_skey* and *vote*, which should have + * predefined header fields. + * Setting *clear_rl* clears the global routerlist before adding the new + * routers. + * Return the signed vote, same as *vote_out*. Save the number of routers added + * in *n_vrs*. + */ +networkstatus_t * +dir_common_add_rs_and_parse(networkstatus_t *vote, networkstatus_t **vote_out, + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + crypto_pk_t *sign_skey, int *n_vrs, time_t now, + int clear_rl) +{ + vote_routerstatus_t *vrs; + char *v_text=NULL; + const char *msg=NULL; + int idx; + was_router_added_t router_added = -1; + *vote_out = NULL; + + if (clear_rl) { + nodelist_free_all(); + routerlist_free_all(); + } + + idx = 0; + do { + vrs = vrs_gen(idx, now); + if (vrs) { + smartlist_add(vote->routerstatus_list, vrs); + router_added = + router_add_to_routerlist(dir_common_generate_ri_from_rs(vrs), + &msg,0,0); + tt_assert(router_added >= 0); + ++idx; + } + } while (vrs); + *n_vrs = idx; + + /* dump the vote and try to parse it. */ + v_text = format_networkstatus_vote(sign_skey, vote); + tt_assert(v_text); + *vote_out = networkstatus_parse_vote_from_string(v_text, NULL, NS_TYPE_VOTE); + + done: + if (v_text) + tor_free(v_text); + + return *vote_out; +} + +/** Create a fake *vote* where *cert* describes the signer, *sign_skey* + * is the signing key, and *vrs_gen* is the function we'll use to create the + * routers on which we're voting. + * We pass *vote_out*, *n_vrs*, and *clear_rl* directly to vrs_gen(). + * Return 0 on success, return -1 on failure. + */ +int +dir_common_construct_vote_1(networkstatus_t **vote, authority_cert_t *cert, + crypto_pk_t *sign_skey, + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + networkstatus_t **vote_out, int *n_vrs, + time_t now, int clear_rl) +{ + networkstatus_voter_info_t *voter; + + dir_common_setup_vote(vote, now); + (*vote)->valid_after = now+1000; + (*vote)->fresh_until = now+2000; + (*vote)->valid_until = now+3000; + (*vote)->vote_seconds = 100; + (*vote)->dist_seconds = 200; + smartlist_split_string((*vote)->supported_methods, "1 2 3", NULL, 0, -1); + (*vote)->client_versions = tor_strdup("0.1.2.14,0.1.2.15"); + (*vote)->server_versions = tor_strdup("0.1.2.14,0.1.2.15,0.1.2.16"); + smartlist_split_string((*vote)->known_flags, + "Authority Exit Fast Guard Running Stable V2Dir Valid", + 0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t)); + voter->nickname = tor_strdup("Voter1"); + voter->address = tor_strdup("1.2.3.4"); + voter->addr = 0x01020304; + voter->dir_port = 80; + voter->or_port = 9000; + voter->contact = tor_strdup("voter@example.com"); + crypto_pk_get_digest(cert->identity_key, voter->identity_digest); + /* + * Set up a vote; generate it; try to parse it. + */ + smartlist_add((*vote)->voters, voter); + (*vote)->cert = authority_cert_dup(cert); + smartlist_split_string((*vote)->net_params, "circuitwindow=101 foo=990", + NULL, 0, 0); + *n_vrs = 0; + /* add routerstatuses */ + if (!dir_common_add_rs_and_parse(*vote, vote_out, vrs_gen, sign_skey, + n_vrs, now, clear_rl)) + return -1; + + return 0; +} + +/** See dir_common_construct_vote_1. + * Produces a vote with slightly different values. + */ +int +dir_common_construct_vote_2(networkstatus_t **vote, authority_cert_t *cert, + crypto_pk_t *sign_skey, + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + networkstatus_t **vote_out, int *n_vrs, + time_t now, int clear_rl) +{ + networkstatus_voter_info_t *voter; + + dir_common_setup_vote(vote, now); + (*vote)->type = NS_TYPE_VOTE; + (*vote)->published += 1; + (*vote)->valid_after = now+1000; + (*vote)->fresh_until = now+3005; + (*vote)->valid_until = now+3000; + (*vote)->vote_seconds = 100; + (*vote)->dist_seconds = 300; + smartlist_split_string((*vote)->supported_methods, "1 2 3", NULL, 0, -1); + smartlist_split_string((*vote)->known_flags, + "Authority Exit Fast Guard MadeOfCheese MadeOfTin " + "Running Stable V2Dir Valid", 0, + SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t)); + voter->nickname = tor_strdup("Voter2"); + voter->address = tor_strdup("2.3.4.5"); + voter->addr = 0x02030405; + voter->dir_port = 80; + voter->or_port = 9000; + voter->contact = tor_strdup("voter@example.com"); + crypto_pk_get_digest(cert->identity_key, voter->identity_digest); + /* + * Set up a vote; generate it; try to parse it. + */ + smartlist_add((*vote)->voters, voter); + (*vote)->cert = authority_cert_dup(cert); + if (! (*vote)->net_params) + (*vote)->net_params = smartlist_new(); + smartlist_split_string((*vote)->net_params, + "bar=2000000000 circuitwindow=20", + NULL, 0, 0); + /* add routerstatuses */ + /* dump the vote and try to parse it. */ + dir_common_add_rs_and_parse(*vote, vote_out, vrs_gen, sign_skey, + n_vrs, now, clear_rl); + + return 0; +} + +/** See dir_common_construct_vote_1. + * Produces a vote with slightly different values. Adds a legacy key. + */ +int +dir_common_construct_vote_3(networkstatus_t **vote, authority_cert_t *cert, + crypto_pk_t *sign_skey, + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + networkstatus_t **vote_out, int *n_vrs, + time_t now, int clear_rl) +{ + networkstatus_voter_info_t *voter; + + dir_common_setup_vote(vote, now); + (*vote)->valid_after = now+1000; + (*vote)->fresh_until = now+2003; + (*vote)->valid_until = now+3000; + (*vote)->vote_seconds = 100; + (*vote)->dist_seconds = 250; + smartlist_split_string((*vote)->supported_methods, "1 2 3 4", NULL, 0, -1); + (*vote)->client_versions = tor_strdup("0.1.2.14,0.1.2.17"); + (*vote)->server_versions = tor_strdup("0.1.2.10,0.1.2.15,0.1.2.16"); + smartlist_split_string((*vote)->known_flags, + "Authority Exit Fast Guard Running Stable V2Dir Valid", + 0, SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); + voter = tor_malloc_zero(sizeof(networkstatus_voter_info_t)); + voter->nickname = tor_strdup("Voter2"); + voter->address = tor_strdup("3.4.5.6"); + voter->addr = 0x03040506; + voter->dir_port = 80; + voter->or_port = 9000; + voter->contact = tor_strdup("voter@example.com"); + crypto_pk_get_digest(cert->identity_key, voter->identity_digest); + memset(voter->legacy_id_digest, (int)'A', DIGEST_LEN); + /* + * Set up a vote; generate it; try to parse it. + */ + smartlist_add((*vote)->voters, voter); + (*vote)->cert = authority_cert_dup(cert); + smartlist_split_string((*vote)->net_params, "circuitwindow=80 foo=660", + NULL, 0, 0); + /* add routerstatuses */ + /* dump the vote and try to parse it. */ + dir_common_add_rs_and_parse(*vote, vote_out, vrs_gen, sign_skey, + n_vrs, now, clear_rl); + + return 0; +} + diff --git a/src/test/test_dir_common.h b/src/test/test_dir_common.h new file mode 100644 index 0000000000..9557cb7157 --- /dev/null +++ b/src/test/test_dir_common.h @@ -0,0 +1,52 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" +#include "networkstatus.h" +#include "routerparse.h" + +#define TEST_DIR_ROUTER_ID_1 3 +#define TEST_DIR_ROUTER_ID_2 5 +#define TEST_DIR_ROUTER_ID_3 33 +#define TEST_DIR_ROUTER_ID_4 34 + +#define TEST_DIR_ROUTER_DD_1 78 +#define TEST_DIR_ROUTER_DD_2 77 +#define TEST_DIR_ROUTER_DD_3 79 +#define TEST_DIR_ROUTER_DD_4 44 + +int dir_common_authority_pk_init(authority_cert_t **cert1, + authority_cert_t **cert2, + authority_cert_t **cert3, + crypto_pk_t **sign_skey_1, + crypto_pk_t **sign_skey_2, + crypto_pk_t **sign_skey_3); + +routerinfo_t * dir_common_generate_ri_from_rs(const vote_routerstatus_t *vrs); + +vote_routerstatus_t * dir_common_gen_routerstatus_for_v3ns(int idx, + time_t now); + +int dir_common_construct_vote_1(networkstatus_t **vote, + authority_cert_t *cert1, + crypto_pk_t *sign_skey, + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + networkstatus_t **vote_out, int *n_vrs, time_t now, + int clear_rl); + +int dir_common_construct_vote_2(networkstatus_t **vote, + authority_cert_t *cert2, + crypto_pk_t *sign_skey, + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + networkstatus_t **vote_out, int *n_vrs, time_t now, + int clear_rl); + +int dir_common_construct_vote_3(networkstatus_t **vote, + authority_cert_t *cert3, + crypto_pk_t *sign_skey, + vote_routerstatus_t * (*vrs_gen)(int idx, time_t now), + networkstatus_t **vote_out, int *n_vrs, time_t now, + int clear_rl); + diff --git a/src/test/test_dir_handle_get.c b/src/test/test_dir_handle_get.c new file mode 100644 index 0000000000..e1ac50e66e --- /dev/null +++ b/src/test/test_dir_handle_get.c @@ -0,0 +1,2556 @@ +/* Copyright (c) 2001-2004, Roger Dingledine. + * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. + * Copyright (c) 2007-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define RENDCOMMON_PRIVATE +#define GEOIP_PRIVATE +#define CONNECTION_PRIVATE +#define CONFIG_PRIVATE +#define RENDCACHE_PRIVATE + +#include "or.h" +#include "config.h" +#include "connection.h" +#include "directory.h" +#include "test.h" +#include "connection.h" +#include "rendcommon.h" +#include "rendcache.h" +#include "router.h" +#include "routerlist.h" +#include "rend_test_helpers.h" +#include "microdesc.h" +#include "test_helpers.h" +#include "nodelist.h" +#include "entrynodes.h" +#include "routerparse.h" +#include "networkstatus.h" +#include "geoip.h" +#include "dirserv.h" +#include "torgzip.h" +#include "dirvote.h" + +#ifdef _WIN32 +/* For mkdir() */ +#include <direct.h> +#else +#include <dirent.h> +#endif + +#include "vote_descriptors.inc" + +#define NS_MODULE dir_handle_get + +static void +connection_write_to_buf_mock(const char *string, size_t len, + connection_t *conn, int zlib) +{ + (void) zlib; + + tor_assert(string); + tor_assert(conn); + + write_to_buf(string, len, conn->outbuf); +} + +#define GET(path) "GET " path " HTTP/1.0\r\n\r\n" +#define NOT_FOUND "HTTP/1.0 404 Not found\r\n\r\n" +#define BAD_REQUEST "HTTP/1.0 400 Bad request\r\n\r\n" +#define SERVER_BUSY "HTTP/1.0 503 Directory busy, try again later\r\n\r\n" +#define NOT_ENOUGH_CONSENSUS_SIGNATURES "HTTP/1.0 404 " \ + "Consensus not signed by sufficient number of requested authorities\r\n\r\n" + +static tor_addr_t MOCK_TOR_ADDR; + +static void +test_dir_handle_get_bad_request(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(directory_handle_command_get(conn, "", NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(header, OP_EQ, BAD_REQUEST); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_v1_command_not_found(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + // no frontpage configured + tt_ptr_op(get_dirportfrontpage(), OP_EQ, NULL); + + /* V1 path */ + tt_int_op(directory_handle_command_get(conn, GET("/tor/"), NULL, 0), + OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static const char* +mock_get_dirportfrontpage(void) +{ + return "HELLO FROM FRONTPAGE"; +} + +static void +test_dir_handle_get_v1_command(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0, body_len = 0; + const char *exp_body = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + MOCK(get_dirportfrontpage, mock_get_dirportfrontpage); + + exp_body = get_dirportfrontpage(); + body_len = strlen(exp_body); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(directory_handle_command_get(conn, GET("/tor/"), NULL, 0), + OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, body_len+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/html\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 20\r\n")); + + tt_int_op(body_used, OP_EQ, strlen(body)); + tt_str_op(body, OP_EQ, exp_body); + + done: + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(get_dirportfrontpage); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); +} + +static void +test_dir_handle_get_not_found(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + /* Unrecognized path */ + tt_int_op(directory_handle_command_get(conn, GET("/anything"), NULL, 0), + OP_EQ, 0); + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_robots_txt(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + tt_int_op(directory_handle_command_get(conn, GET("/tor/robots.txt"), + NULL, 0), OP_EQ, 0); + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, 29, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 28\r\n")); + + tt_int_op(body_used, OP_EQ, strlen(body)); + tt_str_op(body, OP_EQ, "User-agent: *\r\nDisallow: /\r\n"); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); +} + +static void +test_dir_handle_get_bytes_txt(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0, body_len = 0; + char buff[30]; + char *exp_body = NULL; + (void) data; + + exp_body = directory_dump_request_log(); + body_len = strlen(exp_body); + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + tt_int_op(directory_handle_command_get(conn, GET("/tor/bytes.txt"), NULL, 0), + OP_EQ, 0); + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, body_len+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Pragma: no-cache\r\n")); + + sprintf(buff, "Content-Length: %ld\r\n", (long) body_len); + tt_assert(strstr(header, buff)); + + tt_int_op(body_used, OP_EQ, strlen(body)); + tt_str_op(body, OP_EQ, exp_body); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + tor_free(exp_body); +} + +#define RENDEZVOUS2_GET(descid) GET("/tor/rendezvous2/" descid) +static void +test_dir_handle_get_rendezvous2_not_found_if_not_encrypted(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + // connection is not encrypted + tt_assert(!connection_dir_is_encrypted(conn)) + + tt_int_op(directory_handle_command_get(conn, RENDEZVOUS2_GET(), NULL, 0), + OP_EQ, 0); + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_rendezvous2_on_encrypted_conn_with_invalid_desc_id( + void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + // connection is encrypted + TO_CONN(conn)->linked = 1; + tt_assert(connection_dir_is_encrypted(conn)); + + tt_int_op(directory_handle_command_get(conn, + RENDEZVOUS2_GET("invalid-desc-id"), NULL, 0), OP_EQ, 0); + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(header, OP_EQ, BAD_REQUEST); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_rendezvous2_on_encrypted_conn_not_well_formed(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + // connection is encrypted + TO_CONN(conn)->linked = 1; + tt_assert(connection_dir_is_encrypted(conn)); + + //TODO: this cant be reached because rend_valid_descriptor_id() prevents this + //case to happen. This test is the same as + //test_dir_handle_get_rendezvous2_on_encrypted_conn_with_invalid_desc_id + //We should refactor to remove the case from the switch. + + const char *req = RENDEZVOUS2_GET("1bababababababababababababababab"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(header, OP_EQ, BAD_REQUEST); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_rendezvous2_not_found(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + rend_cache_init(); + + // connection is encrypted + TO_CONN(conn)->linked = 1; + tt_assert(connection_dir_is_encrypted(conn)); + + const char *req = RENDEZVOUS2_GET("3xqunszqnaolrrfmtzgaki7mxelgvkje"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + rend_cache_free_all(); +} + +NS_DECL(const routerinfo_t *, router_get_my_routerinfo, (void)); +NS_DECL(int, hid_serv_responsible_for_desc_id, (const char *id)); + +static routerinfo_t *mock_routerinfo; +static int hid_serv_responsible_for_desc_id_response; + +static const routerinfo_t * +NS(router_get_my_routerinfo)(void) +{ + if (!mock_routerinfo) { + mock_routerinfo = tor_malloc_zero(sizeof(routerinfo_t)); + } + + return mock_routerinfo; +} + +static int +NS(hid_serv_responsible_for_desc_id)(const char *id) +{ + (void)id; + return hid_serv_responsible_for_desc_id_response; +} + +static void +test_dir_handle_get_rendezvous2_on_encrypted_conn_success(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + char buff[30]; + char req[70]; + rend_encoded_v2_service_descriptor_t *desc_holder = NULL; + char *service_id = NULL; + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + size_t body_len = 0; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + NS_MOCK(router_get_my_routerinfo); + NS_MOCK(hid_serv_responsible_for_desc_id); + + rend_cache_init(); + hid_serv_responsible_for_desc_id_response = 1; + + /* create a valid rend service descriptor */ + #define RECENT_TIME -10 + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + + tt_int_op(rend_cache_store_v2_desc_as_dir(desc_holder->desc_str), + OP_EQ, RCS_OKAY); + + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + // connection is encrypted + TO_CONN(conn)->linked = 1; + tt_assert(connection_dir_is_encrypted(conn)); + + sprintf(req, RENDEZVOUS2_GET("%s"), desc_id_base32); + + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + body_len = strlen(desc_holder->desc_str); + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, body_len+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Pragma: no-cache\r\n")); + sprintf(buff, "Content-Length: %ld\r\n", (long) body_len); + tt_assert(strstr(header, buff)); + + tt_int_op(body_used, OP_EQ, strlen(body)); + tt_str_op(body, OP_EQ, desc_holder->desc_str); + + done: + UNMOCK(connection_write_to_buf_impl_); + NS_UNMOCK(router_get_my_routerinfo); + NS_UNMOCK(hid_serv_responsible_for_desc_id); + tor_free(mock_routerinfo->cache_info.signed_descriptor_body); + tor_free(mock_routerinfo); + + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_cache_free_all(); +} + +#define MICRODESC_GET(digest) GET("/tor/micro/d/" digest) +static void +test_dir_handle_get_micro_d_not_found(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + #define B64_256_1 "8/Pz8/u7vz8/Pz+7vz8/Pz+7u/Pz8/P7u/Pz8/P7u78" + #define B64_256_2 "zMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMzMw" + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = MICRODESC_GET(B64_256_1 "-" B64_256_2); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static or_options_t *mock_options = NULL; +static void +init_mock_options(void) +{ + mock_options = malloc(sizeof(or_options_t)); + memset(mock_options, 0, sizeof(or_options_t)); + mock_options->TestingTorNetwork = 1; +} + +static const or_options_t * +mock_get_options(void) +{ + tor_assert(mock_options); + return mock_options; +} + +static const char microdesc[] = + "onion-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAMjlHH/daN43cSVRaHBwgUfnszzAhg98EvivJ9Qxfv51mvQUxPjQ07es\n" + "gV/3n8fyh3Kqr/ehi9jxkdgSRfSnmF7giaHL1SLZ29kA7KtST+pBvmTpDtHa3ykX\n" + "Xorc7hJvIyTZoc1HU+5XSynj3gsBE5IGK1ZRzrNS688LnuZMVp1tAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n"; + +static void +test_dir_handle_get_micro_d(void *data) +{ + dir_connection_t *conn = NULL; + microdesc_cache_t *mc = NULL ; + smartlist_t *list = NULL; + char digest[DIGEST256_LEN]; + char digest_base64[128]; + char path[80]; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + (void) data; + + MOCK(get_options, mock_get_options); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* SETUP */ + init_mock_options(); + const char *fn = get_fname("dir_handle_datadir_test1"); + mock_options->DataDirectory = tor_strdup(fn); + +#ifdef _WIN32 + tt_int_op(0, OP_EQ, mkdir(mock_options->DataDirectory)); +#else + tt_int_op(0, OP_EQ, mkdir(mock_options->DataDirectory, 0700)); +#endif + + /* Add microdesc to cache */ + crypto_digest256(digest, microdesc, strlen(microdesc), DIGEST_SHA256); + base64_encode_nopad(digest_base64, sizeof(digest_base64), + (uint8_t *) digest, DIGEST256_LEN); + + mc = get_microdesc_cache(); + list = microdescs_add_to_cache(mc, microdesc, NULL, SAVED_NOWHERE, 0, + time(NULL), NULL); + tt_int_op(1, OP_EQ, smartlist_len(list)); + + /* Make the request */ + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + sprintf(path, MICRODESC_GET("%s"), digest_base64); + tt_int_op(directory_handle_command_get(conn, path, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(microdesc)+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + + tt_int_op(body_used, OP_EQ, strlen(body)); + tt_str_op(body, OP_EQ, microdesc); + + done: + UNMOCK(get_options); + UNMOCK(connection_write_to_buf_impl_); + + or_options_free(mock_options); mock_options = NULL; + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + smartlist_free(list); + microdesc_free_all(); +} + +static void +test_dir_handle_get_micro_d_server_busy(void *data) +{ + dir_connection_t *conn = NULL; + microdesc_cache_t *mc = NULL ; + smartlist_t *list = NULL; + char digest[DIGEST256_LEN]; + char digest_base64[128]; + char path[80]; + char *header = NULL; + (void) data; + + MOCK(get_options, mock_get_options); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* SETUP */ + init_mock_options(); + const char *fn = get_fname("dir_handle_datadir_test2"); + mock_options->DataDirectory = tor_strdup(fn); + +#ifdef _WIN32 + tt_int_op(0, OP_EQ, mkdir(mock_options->DataDirectory)); +#else + tt_int_op(0, OP_EQ, mkdir(mock_options->DataDirectory, 0700)); +#endif + + /* Add microdesc to cache */ + crypto_digest256(digest, microdesc, strlen(microdesc), DIGEST_SHA256); + base64_encode_nopad(digest_base64, sizeof(digest_base64), + (uint8_t *) digest, DIGEST256_LEN); + + mc = get_microdesc_cache(); + list = microdescs_add_to_cache(mc, microdesc, NULL, SAVED_NOWHERE, 0, + time(NULL), NULL); + tt_int_op(1, OP_EQ, smartlist_len(list)); + + //Make it busy + mock_options->CountPrivateBandwidth = 1; + + /* Make the request */ + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + sprintf(path, MICRODESC_GET("%s"), digest_base64); + tt_int_op(directory_handle_command_get(conn, path, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(SERVER_BUSY, OP_EQ, header); + + done: + UNMOCK(get_options); + UNMOCK(connection_write_to_buf_impl_); + + or_options_free(mock_options); mock_options = NULL; + connection_free_(TO_CONN(conn)); + tor_free(header); + smartlist_free(list); + microdesc_free_all(); +} + +#define BRIDGES_PATH "/tor/networkstatus-bridges" +static void +test_dir_handle_get_networkstatus_bridges_not_found_without_auth(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(get_options, mock_get_options); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* SETUP */ + init_mock_options(); + mock_options->BridgeAuthoritativeDir = 1; + mock_options->BridgePassword_AuthDigest_ = tor_strdup("digest"); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + TO_CONN(conn)->linked = 1; + + const char *req = GET(BRIDGES_PATH); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(get_options); + UNMOCK(connection_write_to_buf_impl_); + or_options_free(mock_options); mock_options = NULL; + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_networkstatus_bridges(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(get_options, mock_get_options); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* SETUP */ + init_mock_options(); + mock_options->BridgeAuthoritativeDir = 1; + mock_options->BridgePassword_AuthDigest_ = tor_malloc(DIGEST256_LEN); + crypto_digest256(mock_options->BridgePassword_AuthDigest_, + "abcdefghijklm12345", 18, DIGEST_SHA256); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + TO_CONN(conn)->linked = 1; + + const char *req = "GET " BRIDGES_PATH " HTTP/1.0\r\n" + "Authorization: Basic abcdefghijklm12345\r\n\r\n"; + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 0\r\n")); + + done: + UNMOCK(get_options); + UNMOCK(connection_write_to_buf_impl_); + or_options_free(mock_options); mock_options = NULL; + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_networkstatus_bridges_not_found_wrong_auth(void *data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(get_options, mock_get_options); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* SETUP */ + init_mock_options(); + mock_options->BridgeAuthoritativeDir = 1; + mock_options->BridgePassword_AuthDigest_ = tor_malloc(DIGEST256_LEN); + crypto_digest256(mock_options->BridgePassword_AuthDigest_, + "abcdefghijklm12345", 18, DIGEST_SHA256); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + TO_CONN(conn)->linked = 1; + + const char *req = "GET " BRIDGES_PATH " HTTP/1.0\r\n" + "Authorization: Basic NOTSAMEDIGEST\r\n\r\n"; + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(get_options); + UNMOCK(connection_write_to_buf_impl_); + or_options_free(mock_options); mock_options = NULL; + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +#define SERVER_DESC_GET(id) GET("/tor/server/" id) +static void +test_dir_handle_get_server_descriptors_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = SERVER_DESC_GET("invalid"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_str_op(NOT_FOUND, OP_EQ, header); + tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_SERVER_BY_FP); + + done: + UNMOCK(connection_write_to_buf_impl_); + or_options_free(mock_options); mock_options = NULL; + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_server_descriptors_all(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + (void) data; + + /* Setup fake routerlist. */ + helper_setup_fake_routerlist(); + + //TODO: change to router_get_my_extrainfo when testing "extra" path + NS_MOCK(router_get_my_routerinfo); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + // We are one of the routers + routerlist_t *our_routerlist = router_get_routerlist(); + tt_int_op(smartlist_len(our_routerlist->routers), OP_GE, 1); + mock_routerinfo = smartlist_get(our_routerlist->routers, 0); + set_server_identity_key(mock_routerinfo->identity_pkey); + + /* Treat "all" requests as if they were unencrypted */ + mock_routerinfo->cache_info.send_unencrypted = 1; + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = SERVER_DESC_GET("all"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + //TODO: Is this a BUG? + //It requires strlen(signed_descriptor_len)+1 as body_len but returns a body + //which is smaller than that by annotation_len bytes + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, + mock_routerinfo->cache_info.signed_descriptor_len+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + + //TODO: Is this a BUG? + //This is what should be expected: tt_int_op(body_used, OP_EQ, strlen(body)); + tt_int_op(body_used, OP_EQ, + mock_routerinfo->cache_info.signed_descriptor_len); + + tt_str_op(body, OP_EQ, mock_routerinfo->cache_info.signed_descriptor_body + + mock_routerinfo->cache_info.annotations_len); + tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + + done: + NS_UNMOCK(router_get_my_routerinfo); + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + + routerlist_free_all(); + nodelist_free_all(); + entry_guards_free_all(); +} + +static char +TEST_DESCRIPTOR[] = +"@uploaded-at 2014-06-08 19:20:11\n" +"@source \"127.0.0.1\"\n" +"router test000a 127.0.0.1 5000 0 7000\n" +"platform Tor 0.2.5.3-alpha-dev on Linux\n" +"protocols Link 1 2 Circuit 1\n" +"published 2014-06-08 19:20:11\n" +"fingerprint C7E7 CCB8 179F 8CC3 7F5C 8A04 2B3A 180B 934B 14BA\n" +"uptime 0\n" +"bandwidth 1073741824 1073741824 0\n" +"extra-info-digest 67A152A4C7686FB07664F872620635F194D76D95\n" +"caches-extra-info\n" +"onion-key\n" +"-----BEGIN RSA PUBLIC KEY-----\n" +"MIGJAoGBAOuBUIEBARMkkka/TGyaQNgUEDLP0KG7sy6KNQTNOlZHUresPr/vlVjo\n" +"HPpLMfu9M2z18c51YX/muWwY9x4MyQooD56wI4+AqXQcJRwQfQlPn3Ay82uZViA9\n" +"DpBajRieLlKKkl145KjArpD7F5BVsqccvjErgFYXvhhjSrx7BVLnAgMBAAE=\n" +"-----END RSA PUBLIC KEY-----\n" +"signing-key\n" +"-----BEGIN RSA PUBLIC KEY-----\n" +"MIGJAoGBAN6NLnSxWQnFXxqZi5D3b0BMgV6y9NJLGjYQVP+eWtPZWgqyv4zeYsqv\n" +"O9y6c5lvxyUxmNHfoAbe/s8f2Vf3/YaC17asAVSln4ktrr3e9iY74a9RMWHv1Gzk\n" +"3042nMcqj3PEhRN0PoLkcOZNjjmNbaqki6qy9bWWZDNTdo+uI44dAgMBAAE=\n" +"-----END RSA PUBLIC KEY-----\n" +"hidden-service-dir\n" +"contact auth0@test.test\n" +"ntor-onion-key pK4bs08ERYN591jj7ca17Rn9Q02TIEfhnjR6hSq+fhU=\n" +"reject *:*\n" +"router-signature\n" +"-----BEGIN SIGNATURE-----\n" +"rx88DuM3Y7tODlHNDDEVzKpwh3csaG1or+T4l2Xs1oq3iHHyPEtB6QTLYrC60trG\n" +"aAPsj3DEowGfjga1b248g2dtic8Ab+0exfjMm1RHXfDam5TXXZU3A0wMyoHjqHuf\n" +"eChGPgFNUvEc+5YtD27qEDcUjcinYztTs7/dzxBT4PE=\n" +"-----END SIGNATURE-----\n"; + +static void +test_dir_handle_get_server_descriptors_authority(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + crypto_pk_t *identity_pkey = pk_generate(0); + (void) data; + + NS_MOCK(router_get_my_routerinfo); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* init mock */ + router_get_my_routerinfo(); + crypto_pk_get_digest(identity_pkey, + mock_routerinfo->cache_info.identity_digest); + + // the digest is mine (the channel is unnecrypted, so we must allow sending) + set_server_identity_key(identity_pkey); + mock_routerinfo->cache_info.send_unencrypted = 1; + + /* Setup descriptor */ + long annotation_len = strstr(TEST_DESCRIPTOR, "router ") - TEST_DESCRIPTOR; + mock_routerinfo->cache_info.signed_descriptor_body = + tor_strdup(TEST_DESCRIPTOR); + mock_routerinfo->cache_info.signed_descriptor_len = + strlen(TEST_DESCRIPTOR) - annotation_len;; + mock_routerinfo->cache_info.annotations_len = annotation_len; + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = SERVER_DESC_GET("authority"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + //TODO: Is this a BUG? + //It requires strlen(TEST_DESCRIPTOR)+1 as body_len but returns a body which + //is smaller than that by annotation_len bytes + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(TEST_DESCRIPTOR)+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + + tt_int_op(body_used, OP_EQ, strlen(body)); + + tt_str_op(body, OP_EQ, TEST_DESCRIPTOR + annotation_len); + tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + + done: + NS_UNMOCK(router_get_my_routerinfo); + UNMOCK(connection_write_to_buf_impl_); + tor_free(mock_routerinfo->cache_info.signed_descriptor_body); + tor_free(mock_routerinfo); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + crypto_pk_free(identity_pkey); +} + +static void +test_dir_handle_get_server_descriptors_fp(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + crypto_pk_t *identity_pkey = pk_generate(0); + (void) data; + + NS_MOCK(router_get_my_routerinfo); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* init mock */ + router_get_my_routerinfo(); + crypto_pk_get_digest(identity_pkey, + mock_routerinfo->cache_info.identity_digest); + + // the digest is mine (the channel is unnecrypted, so we must allow sending) + set_server_identity_key(identity_pkey); + mock_routerinfo->cache_info.send_unencrypted = 1; + + /* Setup descriptor */ + long annotation_len = strstr(TEST_DESCRIPTOR, "router ") - TEST_DESCRIPTOR; + mock_routerinfo->cache_info.signed_descriptor_body = + tor_strdup(TEST_DESCRIPTOR); + mock_routerinfo->cache_info.signed_descriptor_len = + strlen(TEST_DESCRIPTOR) - annotation_len; + mock_routerinfo->cache_info.annotations_len = annotation_len; + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + #define HEX1 "Fe0daff89127389bc67558691231234551193EEE" + #define HEX2 "Deadbeef99999991111119999911111111f00ba4" + const char *hex_digest = hex_str(mock_routerinfo->cache_info.identity_digest, + DIGEST_LEN); + + char req[155]; + sprintf(req, SERVER_DESC_GET("fp/%s+" HEX1 "+" HEX2), hex_digest); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + //TODO: Is this a BUG? + //It requires strlen(TEST_DESCRIPTOR)+1 as body_len but returns a body which + //is smaller than that by annotation_len bytes + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(TEST_DESCRIPTOR)+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + + tt_int_op(body_used, OP_EQ, strlen(body)); + + tt_str_op(body, OP_EQ, TEST_DESCRIPTOR + annotation_len); + tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + + done: + NS_UNMOCK(router_get_my_routerinfo); + UNMOCK(connection_write_to_buf_impl_); + tor_free(mock_routerinfo->cache_info.signed_descriptor_body); + tor_free(mock_routerinfo); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + crypto_pk_free(identity_pkey); +} + +#define HEX1 "Fe0daff89127389bc67558691231234551193EEE" +#define HEX2 "Deadbeef99999991111119999911111111f00ba4" + +static void +test_dir_handle_get_server_descriptors_d(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + crypto_pk_t *identity_pkey = pk_generate(0); + (void) data; + + /* Setup fake routerlist. */ + helper_setup_fake_routerlist(); + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* Get one router's signed_descriptor_digest */ + routerlist_t *our_routerlist = router_get_routerlist(); + tt_int_op(smartlist_len(our_routerlist->routers), OP_GE, 1); + routerinfo_t *router = smartlist_get(our_routerlist->routers, 0); + const char *hex_digest = hex_str(router->cache_info.signed_descriptor_digest, + DIGEST_LEN); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + char req_header[155]; + sprintf(req_header, SERVER_DESC_GET("d/%s+" HEX1 "+" HEX2), hex_digest); + tt_int_op(directory_handle_command_get(conn, req_header, NULL, 0), OP_EQ, 0); + + //TODO: Is this a BUG? + //It requires strlen(signed_descriptor_len)+1 as body_len but returns a body + //which is smaller than that by annotation_len bytes + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, + router->cache_info.signed_descriptor_len+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + + //TODO: Is this a BUG? + //This is what should be expected: + //tt_int_op(body_used, OP_EQ, strlen(body)); + tt_int_op(body_used, OP_EQ, router->cache_info.signed_descriptor_len); + + tt_str_op(body, OP_EQ, router->cache_info.signed_descriptor_body + + router->cache_info.annotations_len); + tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + + done: + UNMOCK(connection_write_to_buf_impl_); + tor_free(mock_routerinfo); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + crypto_pk_free(identity_pkey); + + routerlist_free_all(); + nodelist_free_all(); + entry_guards_free_all(); +} + +static void +test_dir_handle_get_server_descriptors_busy(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + crypto_pk_t *identity_pkey = pk_generate(0); + (void) data; + + /* Setup fake routerlist. */ + helper_setup_fake_routerlist(); + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + //Make it busy + MOCK(get_options, mock_get_options); + init_mock_options(); + mock_options->CountPrivateBandwidth = 1; + + /* Get one router's signed_descriptor_digest */ + routerlist_t *our_routerlist = router_get_routerlist(); + tt_int_op(smartlist_len(our_routerlist->routers), OP_GE, 1); + routerinfo_t *router = smartlist_get(our_routerlist->routers, 0); + const char *hex_digest = hex_str(router->cache_info.signed_descriptor_digest, + DIGEST_LEN); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + #define HEX1 "Fe0daff89127389bc67558691231234551193EEE" + #define HEX2 "Deadbeef99999991111119999911111111f00ba4" + char req_header[155]; + sprintf(req_header, SERVER_DESC_GET("d/%s+" HEX1 "+" HEX2), hex_digest); + tt_int_op(directory_handle_command_get(conn, req_header, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(SERVER_BUSY, OP_EQ, header); + + tt_int_op(conn->dir_spool_src, OP_EQ, DIR_SPOOL_NONE); + + done: + UNMOCK(get_options); + UNMOCK(connection_write_to_buf_impl_); + tor_free(mock_routerinfo); + connection_free_(TO_CONN(conn)); + tor_free(header); + crypto_pk_free(identity_pkey); + + routerlist_free_all(); + nodelist_free_all(); + entry_guards_free_all(); +} + +static void +test_dir_handle_get_server_keys_bad_req(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = GET("/tor/keys/"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(BAD_REQUEST, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_server_keys_all_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = GET("/tor/keys/all"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +#define TEST_CERTIFICATE AUTHORITY_CERT_3 +#define TEST_SIGNING_KEY AUTHORITY_SIGNKEY_A_DIGEST +extern const char AUTHORITY_CERT_3[]; +extern const char AUTHORITY_SIGNKEY_A_DIGEST[]; + +static const char TEST_CERT_IDENT_KEY[] = + "D867ACF56A9D229B35C25F0090BC9867E906BE69"; + +static void +test_dir_handle_get_server_keys_all(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + const char digest[DIGEST_LEN] = ""; + + dir_server_t *ds = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + clear_dir_servers(); + routerlist_free_all(); + + /* create a trusted ds */ + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, NULL, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + dir_server_add(ds); + + /* ds v3_identity_digest is the certificate's identity_key */ + base16_decode(ds->v3_identity_digest, DIGEST_LEN, + TEST_CERT_IDENT_KEY, HEX_DIGEST_LEN); + tt_int_op(0, OP_EQ, trusted_dirs_load_certs_from_string(TEST_CERTIFICATE, + TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST, 1)); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = GET("/tor/keys/all"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(TEST_CERTIFICATE)+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 1883\r\n")); + + tt_str_op(TEST_CERTIFICATE, OP_EQ, body); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + + clear_dir_servers(); + routerlist_free_all(); +} + +static void +test_dir_handle_get_server_keys_authority_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = GET("/tor/keys/authority"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static authority_cert_t * mock_cert = NULL; + +static authority_cert_t * +get_my_v3_authority_cert_m(void) +{ + tor_assert(mock_cert); + return mock_cert; +} + +static void +test_dir_handle_get_server_keys_authority(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + (void) data; + + mock_cert = authority_cert_parse_from_string(TEST_CERTIFICATE, NULL); + + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = GET("/tor/keys/authority"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(TEST_CERTIFICATE)+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 1883\r\n")); + + tt_str_op(TEST_CERTIFICATE, OP_EQ, body); + + done: + UNMOCK(get_my_v3_authority_cert); + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + authority_cert_free(mock_cert); mock_cert = NULL; +} + +static void +test_dir_handle_get_server_keys_fp_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = GET("/tor/keys/fp/somehex"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_server_keys_fp(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + dir_server_t *ds = NULL; + const char digest[DIGEST_LEN] = ""; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + clear_dir_servers(); + routerlist_free_all(); + + /* create a trusted ds */ + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, NULL, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + dir_server_add(ds); + + /* ds v3_identity_digest is the certificate's identity_key */ + base16_decode(ds->v3_identity_digest, DIGEST_LEN, + TEST_CERT_IDENT_KEY, HEX_DIGEST_LEN); + + tt_int_op(0, OP_EQ, trusted_dirs_load_certs_from_string(TEST_CERTIFICATE, + TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST, 1)); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + char req[71]; + sprintf(req, GET("/tor/keys/fp/%s"), TEST_CERT_IDENT_KEY); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(TEST_CERTIFICATE)+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 1883\r\n")); + + tt_str_op(TEST_CERTIFICATE, OP_EQ, body); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + clear_dir_servers(); + routerlist_free_all(); +} + +static void +test_dir_handle_get_server_keys_sk_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = GET("/tor/keys/sk/somehex"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_server_keys_sk(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + (void) data; + + mock_cert = authority_cert_parse_from_string(TEST_CERTIFICATE, NULL); + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + clear_dir_servers(); + routerlist_free_all(); + + tt_int_op(0, OP_EQ, trusted_dirs_load_certs_from_string(TEST_CERTIFICATE, + TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST, 1)); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + char req[71]; + sprintf(req, GET("/tor/keys/sk/%s"), TEST_SIGNING_KEY); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(TEST_CERTIFICATE)+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 1883\r\n")); + + tt_str_op(TEST_CERTIFICATE, OP_EQ, body); + + done: + UNMOCK(get_my_v3_authority_cert); + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + authority_cert_free(mock_cert); mock_cert = NULL; + tor_free(header); + tor_free(body); +} + +static void +test_dir_handle_get_server_keys_fpsk_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + const char *req = GET("/tor/keys/fp-sk/somehex"); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_server_keys_fpsk(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + dir_server_t *ds = NULL; + const char digest[DIGEST_LEN] = ""; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + clear_dir_servers(); + routerlist_free_all(); + + /* create a trusted ds */ + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, NULL, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + + /* ds v3_identity_digest is the certificate's identity_key */ + base16_decode(ds->v3_identity_digest, DIGEST_LEN, + TEST_CERT_IDENT_KEY, HEX_DIGEST_LEN); + dir_server_add(ds); + + tt_int_op(0, OP_EQ, trusted_dirs_load_certs_from_string(TEST_CERTIFICATE, + TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST, 1)); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + char req[115]; + sprintf(req, GET("/tor/keys/fp-sk/%s-%s"), + TEST_CERT_IDENT_KEY, TEST_SIGNING_KEY); + + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(TEST_CERTIFICATE)+1, 0); + + tt_assert(header); + tt_assert(body); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 1883\r\n")); + + tt_str_op(TEST_CERTIFICATE, OP_EQ, body); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + + clear_dir_servers(); + routerlist_free_all(); +} + +static void +test_dir_handle_get_server_keys_busy(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + dir_server_t *ds = NULL; + const char digest[DIGEST_LEN] = ""; + (void) data; + + clear_dir_servers(); + routerlist_free_all(); + + /* create a trusted ds */ + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, NULL, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + + /* ds v3_identity_digest is the certificate's identity_key */ + base16_decode(ds->v3_identity_digest, DIGEST_LEN, + TEST_CERT_IDENT_KEY, HEX_DIGEST_LEN); + dir_server_add(ds); + + tt_int_op(0, OP_EQ, trusted_dirs_load_certs_from_string(TEST_CERTIFICATE, + TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST, 1)); + + MOCK(get_options, mock_get_options); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* setup busy server */ + init_mock_options(); + mock_options->CountPrivateBandwidth = 1; + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + char req[71]; + sprintf(req, GET("/tor/keys/fp/%s"), TEST_CERT_IDENT_KEY); + tt_int_op(directory_handle_command_get(conn, req, NULL, 0), OP_EQ, 0); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(SERVER_BUSY, OP_EQ, header); + + done: + UNMOCK(get_options); + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); + or_options_free(mock_options); mock_options = NULL; + + clear_dir_servers(); + routerlist_free_all(); +} + +static networkstatus_t *mock_ns_val = NULL; +static networkstatus_t * +mock_ns_get_by_flavor(consensus_flavor_t f) +{ + (void)f; + return mock_ns_val; +} + +static void +test_dir_handle_get_status_vote_current_consensus_ns_not_enough_sigs(void* d) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *stats = NULL; + (void) d; + + /* init mock */ + mock_ns_val = tor_malloc_zero(sizeof(networkstatus_t)); + mock_ns_val->flavor = FLAV_NS; + mock_ns_val->voters = smartlist_new(); + + /* init mock */ + init_mock_options(); + + MOCK(get_options, mock_get_options); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + MOCK(networkstatus_get_latest_consensus_by_flavor, mock_ns_get_by_flavor); + + /* start gathering stats */ + mock_options->DirReqStatistics = 1; + geoip_dirreq_stats_init(time(NULL)); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/current/consensus-ns/" HEX1 "+" HEX2), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + + tt_assert(header); + tt_str_op(NOT_ENOUGH_CONSENSUS_SIGNATURES, OP_EQ, header); + + stats = geoip_format_dirreq_stats(time(NULL)); + tt_assert(stats); + tt_assert(strstr(stats, "not-enough-sigs=8")); + + done: + UNMOCK(networkstatus_get_latest_consensus_by_flavor); + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(get_options); + + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(stats); + smartlist_free(mock_ns_val->voters); + tor_free(mock_ns_val); + or_options_free(mock_options); mock_options = NULL; +} + +static void +test_dir_handle_get_status_vote_current_consensus_ns_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + char *stats = NULL; + (void) data; + + init_mock_options(); + + MOCK(get_options, mock_get_options); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + /* start gathering stats */ + mock_options->DirReqStatistics = 1; + geoip_dirreq_stats_init(time(NULL)); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/current/consensus-ns"), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + stats = geoip_format_dirreq_stats(time(NULL)); + tt_assert(stats); + tt_assert(strstr(stats, "not-found=8")); + + done: + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(get_options); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(stats); + or_options_free(mock_options); mock_options = NULL; +} + +NS_DECL(int, geoip_get_country_by_addr, (const tor_addr_t *addr)); + +int +NS(geoip_get_country_by_addr)(const tor_addr_t *addr) +{ + (void)addr; + CALLED(geoip_get_country_by_addr)++; + return 1; +} + +static void +status_vote_current_consensus_ns_test(char **header, char **body, + size_t *body_len) +{ + digests_t digests; + dir_connection_t *conn = NULL; + + #define NETWORK_STATUS "some network status string" + dirserv_set_cached_consensus_networkstatus(NETWORK_STATUS, "ns", &digests, + time(NULL)); + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + tt_assert(mock_options); + mock_options->DirReqStatistics = 1; + geoip_dirreq_stats_init(time(NULL)); + + /* init geoip database */ + geoip_parse_entry("10,50,AB", AF_INET); + tt_str_op("ab", OP_EQ, geoip_get_country_name(1)); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + TO_CONN(conn)->address = tor_strdup("127.0.0.1"); + + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/current/consensus-ns"), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, header, MAX_HEADERS_SIZE, + body, body_len, strlen(NETWORK_STATUS)+7, 0); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); +} + +static void +test_dir_handle_get_status_vote_current_consensus_ns(void* data) +{ + char *header = NULL; + char *body = NULL, *comp_body = NULL; + size_t body_used = 0, comp_body_used = 0; + char *stats = NULL, *hist = NULL; + (void) data; + + dirserv_free_all(); + clear_geoip_db(); + + NS_MOCK(geoip_get_country_by_addr); + MOCK(get_options, mock_get_options); + + init_mock_options(); + + status_vote_current_consensus_ns_test(&header, &comp_body, &comp_body_used); + tt_assert(header); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Pragma: no-cache\r\n")); + + compress_method_t compression = detect_compression_method(comp_body, + comp_body_used); + tt_int_op(ZLIB_METHOD, OP_EQ, compression); + + tor_gzip_uncompress(&body, &body_used, comp_body, comp_body_used, + compression, 0, LOG_PROTOCOL_WARN); + + tt_str_op(NETWORK_STATUS, OP_EQ, body); + tt_int_op(strlen(NETWORK_STATUS), OP_EQ, body_used); + + stats = geoip_format_dirreq_stats(time(NULL)); + tt_assert(stats); + + tt_assert(strstr(stats, "ok=8")); + tt_assert(strstr(stats, "dirreq-v3-ips ab=8")); + tt_assert(strstr(stats, "dirreq-v3-reqs ab=8")); + tt_assert(strstr(stats, "dirreq-v3-direct-dl" + " complete=0,timeout=0,running=4")); + + hist = geoip_get_request_history(); + tt_assert(hist); + tt_str_op("ab=8", OP_EQ, hist); + + done: + NS_UNMOCK(geoip_get_country_by_addr); + UNMOCK(get_options); + tor_free(header); + tor_free(comp_body); + tor_free(body); + tor_free(stats); + tor_free(hist); + or_options_free(mock_options); mock_options = NULL; + + dirserv_free_all(); + clear_geoip_db(); +} + +static void +test_dir_handle_get_status_vote_current_consensus_ns_busy(void* data) +{ + char *header = NULL; + char *body = NULL; + size_t body_used = 0; + char *stats = NULL; + (void) data; + + dirserv_free_all(); + clear_geoip_db(); + + MOCK(get_options, mock_get_options); + + // Make it busy + init_mock_options(); + mock_options->CountPrivateBandwidth = 1; + + status_vote_current_consensus_ns_test(&header, &body, &body_used); + tt_assert(header); + + tt_str_op(SERVER_BUSY, OP_EQ, header); + + stats = geoip_format_dirreq_stats(time(NULL)); + tt_assert(stats); + tt_assert(strstr(stats, "busy=8")); + + done: + UNMOCK(get_options); + tor_free(header); + tor_free(body); + or_options_free(mock_options); mock_options = NULL; + + tor_free(stats); + dirserv_free_all(); + clear_geoip_db(); +} + +static void +test_dir_handle_get_status_vote_current_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/current/" HEX1), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +#define VOTE_DIGEST "312A4890D4D832597ABBD3089C782DBBFB81E48D" + +static void +status_vote_current_d_test(char **header, char **body, size_t *body_l) +{ + dir_connection_t *conn = NULL; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/current/d/" VOTE_DIGEST), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, header, MAX_HEADERS_SIZE, + body, body_l, strlen(VOTE_BODY_V3)+1, 0); + tt_assert(header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); +} + +static void +status_vote_next_d_test(char **header, char **body, size_t *body_l) +{ + dir_connection_t *conn = NULL; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/next/d/" VOTE_DIGEST), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, header, MAX_HEADERS_SIZE, + body, body_l, strlen(VOTE_BODY_V3)+1, 0); + tt_assert(header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); +} + +static void +test_dir_handle_get_status_vote_current_d_not_found(void* data) +{ + char *header = NULL; + (void) data; + + status_vote_current_d_test(&header, NULL, NULL); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + tor_free(header); +} + +static void +test_dir_handle_get_status_vote_next_d_not_found(void* data) +{ + char *header = NULL; + (void) data; + + status_vote_next_d_test(&header, NULL, NULL); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + tor_free(header); +} + +static void +test_dir_handle_get_status_vote_d(void* data) +{ + char *header = NULL, *body = NULL; + size_t body_used = 0; + dir_server_t *ds = NULL; + const char digest[DIGEST_LEN] = ""; + (void) data; + + clear_dir_servers(); + dirvote_free_all(); + + /* create a trusted ds */ + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, NULL, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + dir_server_add(ds); + + /* ds v3_identity_digest is the certificate's identity_key */ + base16_decode(ds->v3_identity_digest, DIGEST_LEN, + TEST_CERT_IDENT_KEY, HEX_DIGEST_LEN); + + init_mock_options(); + mock_options->AuthoritativeDir = 1; + mock_options->V3AuthoritativeDir = 1; + mock_options->TestingV3AuthVotingStartOffset = 0; + mock_options->TestingV3AuthInitialVotingInterval = 1; + mock_options->TestingV3AuthInitialVoteDelay = 1; + mock_options->TestingV3AuthInitialDistDelay = 1; + + time_t now = 1441223455 -1; + dirvote_recalculate_timing(mock_options, now); + + const char *msg_out = NULL; + int status_out = 0; + struct pending_vote_t *pv = dirvote_add_vote(VOTE_BODY_V3, &msg_out, + &status_out); + tt_assert(pv); + + status_vote_current_d_test(&header, &body, &body_used); + + tt_assert(header); + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 4135\r\n")); + + tt_str_op(VOTE_BODY_V3, OP_EQ, body); + + tor_free(header); + tor_free(body); + + status_vote_next_d_test(&header, &body, &body_used); + + tt_assert(header); + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 4135\r\n")); + + tt_str_op(VOTE_BODY_V3, OP_EQ, body); + + done: + tor_free(header); + tor_free(body); + or_options_free(mock_options); mock_options = NULL; + + clear_dir_servers(); + dirvote_free_all(); +} + +static void +test_dir_handle_get_status_vote_next_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/next/" HEX1), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +status_vote_next_consensus_test(char **header, char **body, size_t *body_used) +{ + dir_connection_t *conn = NULL; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/next/consensus"), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, header, MAX_HEADERS_SIZE, + body, body_used, 18, 0); + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); +} + +static void +test_dir_handle_get_status_vote_next_consensus_not_found(void* data) +{ + char *header = NULL, *body = NULL; + size_t body_used; + (void) data; + + status_vote_next_consensus_test(&header, &body, &body_used); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + tor_free(header); + tor_free(body); +} + +static void +test_dir_handle_get_status_vote_current_authority_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/current/authority"), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +static void +test_dir_handle_get_status_vote_next_authority_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL; + (void) data; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/next/authority"), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + NULL, NULL, 1, 0); + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + UNMOCK(connection_write_to_buf_impl_); + connection_free_(TO_CONN(conn)); + tor_free(header); +} + +NS_DECL(const char*, +dirvote_get_pending_consensus, (consensus_flavor_t flav)); + +const char* +NS(dirvote_get_pending_consensus)(consensus_flavor_t flav) +{ + (void)flav; + return "pending consensus"; +} + +static void +test_dir_handle_get_status_vote_next_consensus(void* data) +{ + char *header = NULL, *body = NULL; + size_t body_used = 0; + (void) data; + + NS_MOCK(dirvote_get_pending_consensus); + + status_vote_next_consensus_test(&header, &body, &body_used); + tt_assert(header); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 17\r\n")); + + tt_str_op("pending consensus", OP_EQ, body); + + done: + NS_UNMOCK(dirvote_get_pending_consensus); + tor_free(header); + tor_free(body); +} + +static void +test_dir_handle_get_status_vote_next_consensus_busy(void* data) +{ + char *header = NULL, *body = NULL; + size_t body_used = 0; + (void) data; + + MOCK(get_options, mock_get_options); + NS_MOCK(dirvote_get_pending_consensus); + + //Make it busy + init_mock_options(); + mock_options->CountPrivateBandwidth = 1; + + status_vote_next_consensus_test(&header, &body, &body_used); + + tt_assert(header); + tt_str_op(SERVER_BUSY, OP_EQ, header); + + done: + NS_UNMOCK(dirvote_get_pending_consensus); + UNMOCK(get_options); + tor_free(header); + tor_free(body); + or_options_free(mock_options); mock_options = NULL; +} + +static void +status_vote_next_consensus_signatures_test(char **header, char **body, + size_t *body_used) +{ + dir_connection_t *conn = NULL; + + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/next/consensus-signatures"), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, header, MAX_HEADERS_SIZE, + body, body_used, 22, 0); + + done: + connection_free_(TO_CONN(conn)); + UNMOCK(connection_write_to_buf_impl_); +} + +static void +test_dir_handle_get_status_vote_next_consensus_signatures_not_found(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL, *body = NULL; + size_t body_used; + (void) data; + + status_vote_next_consensus_signatures_test(&header, &body, &body_used); + + tt_assert(header); + tt_str_op(NOT_FOUND, OP_EQ, header); + + done: + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); +} + +NS_DECL(const char*, +dirvote_get_pending_detached_signatures, (void)); + +const char* +NS(dirvote_get_pending_detached_signatures)(void) +{ + return "pending detached sigs"; +} + +static void +test_dir_handle_get_status_vote_next_consensus_signatures(void* data) +{ + char *header = NULL, *body = NULL; + size_t body_used = 0; + (void) data; + + NS_MOCK(dirvote_get_pending_detached_signatures); + + status_vote_next_consensus_signatures_test(&header, &body, &body_used); + tt_assert(header); + + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 21\r\n")); + + tt_str_op("pending detached sigs", OP_EQ, body); + + done: + NS_UNMOCK(dirvote_get_pending_detached_signatures); + tor_free(header); + tor_free(body); +} + +static void +test_dir_handle_get_status_vote_next_consensus_signatures_busy(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL, *body = NULL; + size_t body_used; + (void) data; + + NS_MOCK(dirvote_get_pending_detached_signatures); + MOCK(get_options, mock_get_options); + + //Make it busy + init_mock_options(); + mock_options->CountPrivateBandwidth = 1; + + status_vote_next_consensus_signatures_test(&header, &body, &body_used); + + tt_assert(header); + tt_str_op(SERVER_BUSY, OP_EQ, header); + + done: + UNMOCK(get_options); + NS_UNMOCK(dirvote_get_pending_detached_signatures); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + or_options_free(mock_options); mock_options = NULL; +} + +static void +test_dir_handle_get_status_vote_next_authority(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL, *body = NULL; + const char *msg_out = NULL; + int status_out = 0; + size_t body_used = 0; + dir_server_t *ds = NULL; + const char digest[DIGEST_LEN] = ""; + (void) data; + + clear_dir_servers(); + routerlist_free_all(); + dirvote_free_all(); + + mock_cert = authority_cert_parse_from_string(TEST_CERTIFICATE, NULL); + + /* create a trusted ds */ + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, NULL, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + dir_server_add(ds); + + /* ds v3_identity_digest is the certificate's identity_key */ + base16_decode(ds->v3_identity_digest, DIGEST_LEN, + TEST_CERT_IDENT_KEY, HEX_DIGEST_LEN); + tt_int_op(0, OP_EQ, trusted_dirs_load_certs_from_string(TEST_CERTIFICATE, + TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST, 1)); + + init_mock_options(); + mock_options->AuthoritativeDir = 1; + mock_options->V3AuthoritativeDir = 1; + mock_options->TestingV3AuthVotingStartOffset = 0; + mock_options->TestingV3AuthInitialVotingInterval = 1; + mock_options->TestingV3AuthInitialVoteDelay = 1; + mock_options->TestingV3AuthInitialDistDelay = 1; + + time_t now = 1441223455 -1; + dirvote_recalculate_timing(mock_options, now); + + struct pending_vote_t *vote = dirvote_add_vote(VOTE_BODY_V3, &msg_out, + &status_out); + tt_assert(vote); + + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/next/authority"), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(VOTE_BODY_V3)+1, 0); + + tt_assert(header); + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 4135\r\n")); + + tt_str_op(VOTE_BODY_V3, OP_EQ, body); + + done: + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(get_my_v3_authority_cert); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + authority_cert_free(mock_cert); mock_cert = NULL; + or_options_free(mock_options); mock_options = NULL; + + clear_dir_servers(); + routerlist_free_all(); + dirvote_free_all(); +} + +static void +test_dir_handle_get_status_vote_current_authority(void* data) +{ + dir_connection_t *conn = NULL; + char *header = NULL, *body = NULL; + const char *msg_out = NULL; + int status_out = 0; + size_t body_used = 0; + const char digest[DIGEST_LEN] = ""; + + dir_server_t *ds = NULL; + (void) data; + + clear_dir_servers(); + routerlist_free_all(); + dirvote_free_all(); + + mock_cert = authority_cert_parse_from_string(TEST_CERTIFICATE, NULL); + + /* create a trusted ds */ + ds = trusted_dir_server_new("ds", "127.0.0.1", 9059, 9060, NULL, digest, + NULL, V3_DIRINFO, 1.0); + tt_assert(ds); + dir_server_add(ds); + + /* ds v3_identity_digest is the certificate's identity_key */ + base16_decode(ds->v3_identity_digest, DIGEST_LEN, + TEST_CERT_IDENT_KEY, HEX_DIGEST_LEN); + + tt_int_op(0, OP_EQ, trusted_dirs_load_certs_from_string(TEST_CERTIFICATE, + TRUSTED_DIRS_CERTS_SRC_DL_BY_ID_DIGEST, 1)); + + init_mock_options(); + mock_options->AuthoritativeDir = 1; + mock_options->V3AuthoritativeDir = 1; + mock_options->TestingV3AuthVotingStartOffset = 0; + mock_options->TestingV3AuthInitialVotingInterval = 1; + mock_options->TestingV3AuthInitialVoteDelay = 1; + mock_options->TestingV3AuthInitialDistDelay = 1; + + time_t now = 1441223455; + dirvote_recalculate_timing(mock_options, now-1); + + struct pending_vote_t *vote = dirvote_add_vote(VOTE_BODY_V3, &msg_out, + &status_out); + tt_assert(vote); + + // move the pending vote to previous vote + dirvote_act(mock_options, now+1); + + MOCK(get_my_v3_authority_cert, get_my_v3_authority_cert_m); + MOCK(connection_write_to_buf_impl_, connection_write_to_buf_mock); + + conn = dir_connection_new(tor_addr_family(&MOCK_TOR_ADDR)); + tt_int_op(0, OP_EQ, directory_handle_command_get(conn, + GET("/tor/status-vote/current/authority"), NULL, 0)); + + fetch_from_buf_http(TO_CONN(conn)->outbuf, &header, MAX_HEADERS_SIZE, + &body, &body_used, strlen(VOTE_BODY_V3)+1, 0); + + tt_assert(header); + tt_ptr_op(strstr(header, "HTTP/1.0 200 OK\r\n"), OP_EQ, header); + tt_assert(strstr(header, "Content-Type: text/plain\r\n")); + tt_assert(strstr(header, "Content-Encoding: identity\r\n")); + tt_assert(strstr(header, "Content-Length: 4135\r\n")); + + tt_str_op(VOTE_BODY_V3, OP_EQ, body); + + done: + UNMOCK(connection_write_to_buf_impl_); + UNMOCK(get_my_v3_authority_cert); + connection_free_(TO_CONN(conn)); + tor_free(header); + tor_free(body); + authority_cert_free(mock_cert); mock_cert = NULL; + or_options_free(mock_options); mock_options = NULL; + + clear_dir_servers(); + routerlist_free_all(); + dirvote_free_all(); +} + +#define DIR_HANDLE_CMD(name,flags) \ + { #name, test_dir_handle_get_##name, (flags), NULL, NULL } + +struct testcase_t dir_handle_get_tests[] = { + DIR_HANDLE_CMD(not_found, 0), + DIR_HANDLE_CMD(bad_request, 0), + DIR_HANDLE_CMD(v1_command_not_found, 0), + DIR_HANDLE_CMD(v1_command, 0), + DIR_HANDLE_CMD(robots_txt, 0), + DIR_HANDLE_CMD(bytes_txt, 0), + DIR_HANDLE_CMD(rendezvous2_not_found_if_not_encrypted, 0), + DIR_HANDLE_CMD(rendezvous2_not_found, 0), + DIR_HANDLE_CMD(rendezvous2_on_encrypted_conn_with_invalid_desc_id, 0), + DIR_HANDLE_CMD(rendezvous2_on_encrypted_conn_not_well_formed, 0), + DIR_HANDLE_CMD(rendezvous2_on_encrypted_conn_success, 0), + DIR_HANDLE_CMD(micro_d_not_found, 0), + DIR_HANDLE_CMD(micro_d_server_busy, 0), + DIR_HANDLE_CMD(micro_d, 0), + DIR_HANDLE_CMD(networkstatus_bridges_not_found_without_auth, 0), + DIR_HANDLE_CMD(networkstatus_bridges_not_found_wrong_auth, 0), + DIR_HANDLE_CMD(networkstatus_bridges, 0), + DIR_HANDLE_CMD(server_descriptors_not_found, 0), + DIR_HANDLE_CMD(server_descriptors_busy, TT_FORK), + DIR_HANDLE_CMD(server_descriptors_all, TT_FORK), + DIR_HANDLE_CMD(server_descriptors_authority, TT_FORK), + DIR_HANDLE_CMD(server_descriptors_fp, TT_FORK), + DIR_HANDLE_CMD(server_descriptors_d, TT_FORK), + DIR_HANDLE_CMD(server_keys_bad_req, 0), + DIR_HANDLE_CMD(server_keys_busy, 0), + DIR_HANDLE_CMD(server_keys_all_not_found, 0), + DIR_HANDLE_CMD(server_keys_all, 0), + DIR_HANDLE_CMD(server_keys_authority_not_found, 0), + DIR_HANDLE_CMD(server_keys_authority, 0), + DIR_HANDLE_CMD(server_keys_fp_not_found, 0), + DIR_HANDLE_CMD(server_keys_fp, 0), + DIR_HANDLE_CMD(server_keys_sk_not_found, 0), + DIR_HANDLE_CMD(server_keys_sk, 0), + DIR_HANDLE_CMD(server_keys_fpsk_not_found, 0), + DIR_HANDLE_CMD(server_keys_fpsk, 0), + DIR_HANDLE_CMD(status_vote_current_not_found, 0), + DIR_HANDLE_CMD(status_vote_next_not_found, 0), + DIR_HANDLE_CMD(status_vote_current_authority_not_found, 0), + DIR_HANDLE_CMD(status_vote_current_authority, 0), + DIR_HANDLE_CMD(status_vote_next_authority_not_found, 0), + DIR_HANDLE_CMD(status_vote_next_authority, 0), + DIR_HANDLE_CMD(status_vote_current_consensus_ns_not_enough_sigs, 0), + DIR_HANDLE_CMD(status_vote_current_consensus_ns_not_found, 0), + DIR_HANDLE_CMD(status_vote_current_consensus_ns_busy, 0), + DIR_HANDLE_CMD(status_vote_current_consensus_ns, 0), + DIR_HANDLE_CMD(status_vote_current_d_not_found, 0), + DIR_HANDLE_CMD(status_vote_next_d_not_found, 0), + DIR_HANDLE_CMD(status_vote_d, 0), + DIR_HANDLE_CMD(status_vote_next_consensus_not_found, 0), + DIR_HANDLE_CMD(status_vote_next_consensus_busy, 0), + DIR_HANDLE_CMD(status_vote_next_consensus, 0), + DIR_HANDLE_CMD(status_vote_next_consensus_signatures_not_found, 0), + DIR_HANDLE_CMD(status_vote_next_consensus_signatures_busy, 0), + DIR_HANDLE_CMD(status_vote_next_consensus_signatures, 0), + END_OF_TESTCASES +}; + diff --git a/src/test/test_dns.c b/src/test/test_dns.c index ad81914ccb..6fdbe905e0 100644 --- a/src/test/test_dns.c +++ b/src/test/test_dns.c @@ -5,9 +5,14 @@ #include "dns.h" #include "connection.h" +#include "router.h" + +#define NS_MODULE dns + +#define NS_SUBMODULE clip_ttl static void -test_dns_clip_ttl(void *arg) +NS(test_main)(void *arg) { (void)arg; @@ -21,8 +26,12 @@ test_dns_clip_ttl(void *arg) return; } +#undef NS_SUBMODULE + +#define NS_SUBMODULE expiry_ttl + static void -test_dns_expiry_ttl(void *arg) +NS(test_main)(void *arg) { (void)arg; @@ -36,6 +45,10 @@ test_dns_expiry_ttl(void *arg) return; } +#undef NS_SUBMODULE + +#define NS_SUBMODULE resolve + static int resolve_retval = 0; static int resolve_made_conn_pending = 0; static char *resolved_name = NULL; @@ -43,6 +56,11 @@ static cached_resolve_t *cache_entry = NULL; static int n_fake_impl = 0; +NS_DECL(int, dns_resolve_impl, (edge_connection_t *exitconn, int is_resolve, + or_circuit_t *oncirc, char **hostname_out, + int *made_connection_pending_out, + cached_resolve_t **resolve_out)); + /** This will be our configurable substitute for <b>dns_resolve_impl</b> in * dns.c. It will return <b>resolve_retval</b>, * and set <b>resolve_made_conn_pending</b> to @@ -52,10 +70,10 @@ static int n_fake_impl = 0; * 1. */ static int -dns_resolve_fake_impl(edge_connection_t *exitconn, int is_resolve, - or_circuit_t *oncirc, char **hostname_out, - int *made_connection_pending_out, - cached_resolve_t **resolve_out) +NS(dns_resolve_impl)(edge_connection_t *exitconn, int is_resolve, + or_circuit_t *oncirc, char **hostname_out, + int *made_connection_pending_out, + cached_resolve_t **resolve_out) { (void)oncirc; (void)exitconn; @@ -82,8 +100,8 @@ static uint8_t last_answer_type = 0; static cached_resolve_t *last_resolved; static void -send_resolved_cell_replacement(edge_connection_t *conn, uint8_t answer_type, - const cached_resolve_t *resolved) +NS(send_resolved_cell)(edge_connection_t *conn, uint8_t answer_type, + const cached_resolve_t *resolved) { conn_for_resolved_cell = conn; @@ -98,8 +116,8 @@ static int n_send_resolved_hostname_cell_replacement = 0; static char *last_resolved_hostname = NULL; static void -send_resolved_hostname_cell_replacement(edge_connection_t *conn, - const char *hostname) +NS(send_resolved_hostname_cell)(edge_connection_t *conn, + const char *hostname) { conn_for_resolved_cell = conn; @@ -112,7 +130,7 @@ send_resolved_hostname_cell_replacement(edge_connection_t *conn, static int n_dns_cancel_pending_resolve_replacement = 0; static void -dns_cancel_pending_resolve_replacement(const char *address) +NS(dns_cancel_pending_resolve)(const char *address) { (void) address; n_dns_cancel_pending_resolve_replacement++; @@ -122,7 +140,7 @@ static int n_connection_free = 0; static connection_t *last_freed_conn = NULL; static void -connection_free_replacement(connection_t *conn) +NS(connection_free)(connection_t *conn) { n_connection_free++; @@ -130,7 +148,7 @@ connection_free_replacement(connection_t *conn) } static void -test_dns_resolve_outer(void *arg) +NS(test_main)(void *arg) { (void) arg; int retval; @@ -149,9 +167,9 @@ test_dns_resolve_outer(void *arg) memset(exitconn,0,sizeof(edge_connection_t)); memset(nextconn,0,sizeof(edge_connection_t)); - MOCK(dns_resolve_impl,dns_resolve_fake_impl); - MOCK(send_resolved_cell,send_resolved_cell_replacement); - MOCK(send_resolved_hostname_cell,send_resolved_hostname_cell_replacement); + NS_MOCK(dns_resolve_impl); + NS_MOCK(send_resolved_cell); + NS_MOCK(send_resolved_hostname_cell); /* * CASE 1: dns_resolve_impl returns 1 and sets a hostname. purpose is @@ -264,8 +282,8 @@ test_dns_resolve_outer(void *arg) * on exitconn with type being RESOLVED_TYPE_ERROR. */ - MOCK(dns_cancel_pending_resolve,dns_cancel_pending_resolve_replacement); - MOCK(connection_free,connection_free_replacement); + NS_MOCK(dns_cancel_pending_resolve); + NS_MOCK(connection_free); exitconn->on_circuit = &(on_circuit->base_); exitconn->base_.purpose = EXIT_PURPOSE_RESOLVE; @@ -288,11 +306,11 @@ test_dns_resolve_outer(void *arg) tt_assert(last_freed_conn == TO_CONN(exitconn)); done: - UNMOCK(dns_resolve_impl); - UNMOCK(send_resolved_cell); - UNMOCK(send_resolved_hostname_cell); - UNMOCK(dns_cancel_pending_resolve); - UNMOCK(connection_free); + NS_UNMOCK(dns_resolve_impl); + NS_UNMOCK(send_resolved_cell); + NS_UNMOCK(send_resolved_hostname_cell); + NS_UNMOCK(dns_cancel_pending_resolve); + NS_UNMOCK(connection_free); tor_free(on_circuit); tor_free(exitconn); tor_free(nextconn); @@ -302,10 +320,446 @@ test_dns_resolve_outer(void *arg) return; } +#undef NS_SUBMODULE + +/** Create an <b>edge_connection_t</b> instance that is considered a + * valid exit connection by asserts in dns_resolve_impl. + */ +static edge_connection_t * +create_valid_exitconn(void) +{ + edge_connection_t *exitconn = tor_malloc_zero(sizeof(edge_connection_t)); + TO_CONN(exitconn)->type = CONN_TYPE_EXIT; + TO_CONN(exitconn)->magic = EDGE_CONNECTION_MAGIC; + TO_CONN(exitconn)->purpose = EXIT_PURPOSE_RESOLVE; + TO_CONN(exitconn)->state = EXIT_CONN_STATE_RESOLVING; + exitconn->base_.s = TOR_INVALID_SOCKET; + + return exitconn; +} + +#define NS_SUBMODULE ASPECT(resolve_impl, addr_is_ip_no_need_to_resolve) + +/* + * Given that <b>exitconn->base_.address</b> is IP address string, we + * want dns_resolve_impl() to parse it and store in + * <b>exitconn->base_.addr</b>. We expect dns_resolve_impl to return 1. + * Lastly, we want it to set the TTL value to default one for DNS queries. + */ + +static void +NS(test_main)(void *arg) +{ + int retval; + int made_pending; + const tor_addr_t *resolved_addr; + tor_addr_t addr_to_compare; + + (void)arg; + + tor_addr_parse(&addr_to_compare, "8.8.8.8"); + + or_circuit_t *on_circ = tor_malloc_zero(sizeof(or_circuit_t)); + + edge_connection_t *exitconn = create_valid_exitconn(); + + TO_CONN(exitconn)->address = tor_strdup("8.8.8.8"); + + retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, + NULL); + + resolved_addr = &(exitconn->base_.addr); + + tt_int_op(retval,==,1); + tt_assert(tor_addr_eq(resolved_addr, (const tor_addr_t *)&addr_to_compare)); + tt_int_op(exitconn->address_ttl,==,DEFAULT_DNS_TTL); + + done: + tor_free(on_circ); + tor_free(TO_CONN(exitconn)->address); + tor_free(exitconn); + return; +} + +#undef NS_SUBMODULE + +#define NS_SUBMODULE ASPECT(resolve_impl, non_exit) + +/** Given that Tor instance is not configured as an exit node, we want + * dns_resolve_impl() to fail with return value -1. + */ +static int +NS(router_my_exit_policy_is_reject_star)(void) +{ + return 1; +} + +static void +NS(test_main)(void *arg) +{ + int retval; + int made_pending; + + edge_connection_t *exitconn = create_valid_exitconn(); + or_circuit_t *on_circ = tor_malloc_zero(sizeof(or_circuit_t)); + + (void)arg; + + TO_CONN(exitconn)->address = tor_strdup("torproject.org"); + + NS_MOCK(router_my_exit_policy_is_reject_star); + + retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, + NULL); + + tt_int_op(retval,==,-1); + + done: + tor_free(TO_CONN(exitconn)->address); + tor_free(exitconn); + tor_free(on_circ); + NS_UNMOCK(router_my_exit_policy_is_reject_star); + return; +} + +#undef NS_SUBMODULE + +#define NS_SUBMODULE ASPECT(resolve_impl, addr_is_invalid_dest) + +/** Given that address is not a valid destination (as judged by + * address_is_invalid_destination() function), we want dns_resolve_impl() + * function to fail with return value -1. + */ + +static int +NS(router_my_exit_policy_is_reject_star)(void) +{ + return 0; +} + +static void +NS(test_main)(void *arg) +{ + int retval; + int made_pending; + + edge_connection_t *exitconn = create_valid_exitconn(); + or_circuit_t *on_circ = tor_malloc_zero(sizeof(or_circuit_t)); + + (void)arg; + + NS_MOCK(router_my_exit_policy_is_reject_star); + + TO_CONN(exitconn)->address = tor_strdup("invalid#@!.org"); + + retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, + NULL); + + tt_int_op(retval,==,-1); + + done: + NS_UNMOCK(router_my_exit_policy_is_reject_star); + tor_free(TO_CONN(exitconn)->address); + tor_free(exitconn); + tor_free(on_circ); + return; +} + +#undef NS_SUBMODULE + +#define NS_SUBMODULE ASPECT(resolve_impl, malformed_ptr) + +/** Given that address is a malformed PTR name, we want dns_resolve_impl to + * fail. + */ + +static int +NS(router_my_exit_policy_is_reject_star)(void) +{ + return 0; +} + +static void +NS(test_main)(void *arg) +{ + int retval; + int made_pending; + + edge_connection_t *exitconn = create_valid_exitconn(); + or_circuit_t *on_circ = tor_malloc_zero(sizeof(or_circuit_t)); + + (void)arg; + + TO_CONN(exitconn)->address = tor_strdup("127.0.0.1.in-addr.arpa"); + + NS_MOCK(router_my_exit_policy_is_reject_star); + + retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, + NULL); + + tt_int_op(retval,==,-1); + + tor_free(TO_CONN(exitconn)->address); + + TO_CONN(exitconn)->address = + tor_strdup("z01234567890123456789.in-addr.arpa"); + + retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, + NULL); + + tt_int_op(retval,==,-1); + + done: + NS_UNMOCK(router_my_exit_policy_is_reject_star); + tor_free(TO_CONN(exitconn)->address); + tor_free(exitconn); + tor_free(on_circ); + return; +} + +#undef NS_SUBMODULE + +#define NS_SUBMODULE ASPECT(resolve_impl, cache_hit_pending) + +/* Given that there is already a pending resolve for the given address, + * we want dns_resolve_impl to append our exit connection to list + * of pending connections for the pending DNS request and return 0. + */ + +static int +NS(router_my_exit_policy_is_reject_star)(void) +{ + return 0; +} + +static void +NS(test_main)(void *arg) +{ + int retval; + int made_pending = 0; + + pending_connection_t *pending_conn = NULL; + + edge_connection_t *exitconn = create_valid_exitconn(); + or_circuit_t *on_circ = tor_malloc_zero(sizeof(or_circuit_t)); + + cached_resolve_t *cache_entry = tor_malloc_zero(sizeof(cached_resolve_t)); + cache_entry->magic = CACHED_RESOLVE_MAGIC; + cache_entry->state = CACHE_STATE_PENDING; + cache_entry->minheap_idx = -1; + cache_entry->expire = time(NULL) + 60 * 60; + + (void)arg; + + TO_CONN(exitconn)->address = tor_strdup("torproject.org"); + + strlcpy(cache_entry->address, TO_CONN(exitconn)->address, + sizeof(cache_entry->address)); + + NS_MOCK(router_my_exit_policy_is_reject_star); + + dns_init(); + + dns_insert_cache_entry(cache_entry); + + retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, + NULL); + + tt_int_op(retval,==,0); + tt_int_op(made_pending,==,1); + + pending_conn = cache_entry->pending_connections; + + tt_assert(pending_conn != NULL); + tt_assert(pending_conn->conn == exitconn); + + done: + NS_UNMOCK(router_my_exit_policy_is_reject_star); + tor_free(on_circ); + tor_free(TO_CONN(exitconn)->address); + tor_free(cache_entry->pending_connections); + tor_free(cache_entry); + tor_free(exitconn); + return; +} + +#undef NS_SUBMODULE + +#define NS_SUBMODULE ASPECT(resolve_impl, cache_hit_cached) + +/* Given that a finished DNS resolve is available in our cache, we want + * dns_resolve_impl() return it to called via resolve_out and pass the + * handling to set_exitconn_info_from_resolve function. + */ +static int +NS(router_my_exit_policy_is_reject_star)(void) +{ + return 0; +} + +static edge_connection_t *last_exitconn = NULL; +static cached_resolve_t *last_resolve = NULL; + +static int +NS(set_exitconn_info_from_resolve)(edge_connection_t *exitconn, + const cached_resolve_t *resolve, + char **hostname_out) +{ + last_exitconn = exitconn; + last_resolve = (cached_resolve_t *)resolve; + + (void)hostname_out; + + return 0; +} + +static void +NS(test_main)(void *arg) +{ + int retval; + int made_pending = 0; + + edge_connection_t *exitconn = create_valid_exitconn(); + or_circuit_t *on_circ = tor_malloc_zero(sizeof(or_circuit_t)); + + cached_resolve_t *resolve_out = NULL; + + cached_resolve_t *cache_entry = tor_malloc_zero(sizeof(cached_resolve_t)); + cache_entry->magic = CACHED_RESOLVE_MAGIC; + cache_entry->state = CACHE_STATE_CACHED; + cache_entry->minheap_idx = -1; + cache_entry->expire = time(NULL) + 60 * 60; + + (void)arg; + + TO_CONN(exitconn)->address = tor_strdup("torproject.org"); + + strlcpy(cache_entry->address, TO_CONN(exitconn)->address, + sizeof(cache_entry->address)); + + NS_MOCK(router_my_exit_policy_is_reject_star); + NS_MOCK(set_exitconn_info_from_resolve); + + dns_init(); + + dns_insert_cache_entry(cache_entry); + + retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, + &resolve_out); + + tt_int_op(retval,==,0); + tt_int_op(made_pending,==,0); + tt_assert(resolve_out == cache_entry); + + tt_assert(last_exitconn == exitconn); + tt_assert(last_resolve == cache_entry); + + done: + NS_UNMOCK(router_my_exit_policy_is_reject_star); + NS_UNMOCK(set_exitconn_info_from_resolve); + tor_free(on_circ); + tor_free(TO_CONN(exitconn)->address); + tor_free(cache_entry->pending_connections); + tor_free(cache_entry); + return; +} + +#undef NS_SUBMODULE + +#define NS_SUBMODULE ASPECT(resolve_impl, cache_miss) + +/* Given that there are neither pending nor pre-cached resolve for a given + * address, we want dns_resolve_impl() to create a new cached_resolve_t + * object, mark it as pending, insert it into the cache, attach the exit + * connection to list of pending connections and call launch_resolve() + * with the cached_resolve_t object it created. + */ +static int +NS(router_my_exit_policy_is_reject_star)(void) +{ + return 0; +} + +static cached_resolve_t *last_launched_resolve = NULL; + +static int +NS(launch_resolve)(cached_resolve_t *resolve) +{ + last_launched_resolve = resolve; + + return 0; +} + +static void +NS(test_main)(void *arg) +{ + int retval; + int made_pending = 0; + + pending_connection_t *pending_conn = NULL; + + edge_connection_t *exitconn = create_valid_exitconn(); + or_circuit_t *on_circ = tor_malloc_zero(sizeof(or_circuit_t)); + + cached_resolve_t *cache_entry = NULL; + cached_resolve_t query; + + (void)arg; + + TO_CONN(exitconn)->address = tor_strdup("torproject.org"); + + strlcpy(query.address, TO_CONN(exitconn)->address, sizeof(query.address)); + + NS_MOCK(router_my_exit_policy_is_reject_star); + NS_MOCK(launch_resolve); + + dns_init(); + + retval = dns_resolve_impl(exitconn, 1, on_circ, NULL, &made_pending, + NULL); + + tt_int_op(retval,==,0); + tt_int_op(made_pending,==,1); + + cache_entry = dns_get_cache_entry(&query); + + tt_assert(cache_entry); + + pending_conn = cache_entry->pending_connections; + + tt_assert(pending_conn != NULL); + tt_assert(pending_conn->conn == exitconn); + + tt_assert(last_launched_resolve == cache_entry); + tt_str_op(cache_entry->address,==,TO_CONN(exitconn)->address); + + done: + NS_UNMOCK(router_my_exit_policy_is_reject_star); + NS_UNMOCK(launch_resolve); + tor_free(on_circ); + tor_free(TO_CONN(exitconn)->address); + if (cache_entry) + tor_free(cache_entry->pending_connections); + tor_free(cache_entry); + tor_free(exitconn); + return; +} + +#undef NS_SUBMODULE + struct testcase_t dns_tests[] = { - { "clip_ttl", test_dns_clip_ttl, 0, NULL, NULL }, - { "expiry_ttl", test_dns_expiry_ttl, 0, NULL, NULL }, - { "resolve_outer", test_dns_resolve_outer, TT_FORK, NULL, NULL }, + TEST_CASE(clip_ttl), + TEST_CASE(expiry_ttl), + TEST_CASE(resolve), + TEST_CASE_ASPECT(resolve_impl, addr_is_ip_no_need_to_resolve), + TEST_CASE_ASPECT(resolve_impl, non_exit), + TEST_CASE_ASPECT(resolve_impl, addr_is_invalid_dest), + TEST_CASE_ASPECT(resolve_impl, malformed_ptr), + TEST_CASE_ASPECT(resolve_impl, cache_hit_pending), + TEST_CASE_ASPECT(resolve_impl, cache_hit_cached), + TEST_CASE_ASPECT(resolve_impl, cache_miss), END_OF_TESTCASES }; +#undef NS_MODULE + diff --git a/src/test/test_extorport.c b/src/test/test_extorport.c index 2e5a32eef3..5d38ed8fa2 100644 --- a/src/test/test_extorport.c +++ b/src/test/test_extorport.c @@ -309,15 +309,14 @@ test_ext_or_cookie_auth(void *arg) tor_free(client_hash2); } -static int +static void crypto_rand_return_tse_str(char *to, size_t n) { if (n != 32) { TT_FAIL(("Asked for %d bytes, not 32", (int)n)); - return -1; + return; } memcpy(to, "te road There is always another ", 32); - return 0; } static void diff --git a/src/test/test_nodelist.c b/src/test/test_nodelist.c index a8693ec9b5..b0cb87dc60 100644 --- a/src/test/test_nodelist.c +++ b/src/test/test_nodelist.c @@ -60,12 +60,53 @@ test_nodelist_node_get_verbose_nickname_not_named(void *arg) return; } +/** A node should be considered a directory server if it has an open dirport + * of it accepts tunnelled directory requests. + */ +static void +test_nodelist_node_is_dir(void *arg) +{ + (void)arg; + + routerstatus_t rs; + routerinfo_t ri; + node_t node; + memset(&node, 0, sizeof(node_t)); + memset(&rs, 0, sizeof(routerstatus_t)); + memset(&ri, 0, sizeof(routerinfo_t)); + + tt_assert(!node_is_dir(&node)); + + node.rs = &rs; + tt_assert(!node_is_dir(&node)); + + rs.is_v2_dir = 1; + tt_assert(node_is_dir(&node)); + + rs.is_v2_dir = 0; + rs.dir_port = 1; + tt_assert(! node_is_dir(&node)); + + node.rs = NULL; + tt_assert(!node_is_dir(&node)); + node.ri = &ri; + ri.supports_tunnelled_dir_requests = 1; + tt_assert(node_is_dir(&node)); + ri.supports_tunnelled_dir_requests = 0; + ri.dir_port = 1; + tt_assert(! node_is_dir(&node)); + + done: + return; +} + #define NODE(name, flags) \ { #name, test_nodelist_##name, (flags), NULL, NULL } struct testcase_t nodelist_tests[] = { NODE(node_get_verbose_nickname_by_id_null_node, TT_FORK), NODE(node_get_verbose_nickname_not_named, TT_FORK), + NODE(node_is_dir, TT_FORK), END_OF_TESTCASES }; diff --git a/src/test/test_ntor_cl.c b/src/test/test_ntor_cl.c index bfbf13a476..915a5d0a7c 100644 --- a/src/test/test_ntor_cl.c +++ b/src/test/test_ntor_cl.c @@ -106,6 +106,7 @@ server1(int argc, char **argv) done: tor_free(keys); tor_free(hexkeys); + dimap_free(keymap, NULL); return result; } diff --git a/src/test/test_options.c b/src/test/test_options.c index a8ebadb14b..a8b0ba9603 100644 --- a/src/test/test_options.c +++ b/src/test/test_options.c @@ -8,6 +8,18 @@ #include "confparse.h" #include "config.h" #include "test.h" +#include "geoip.h" + +#define ROUTERSET_PRIVATE +#include "routerset.h" + +#include "log_test_helpers.h" + +#include "sandbox.h" +#include "memarea.h" +#include "policies.h" + +#define NS_MODULE test_options typedef struct { int severity; @@ -38,6 +50,7 @@ setup_log_callback(void) lst.masks[LOG_WARN - LOG_ERR] = ~0; lst.masks[LOG_NOTICE - LOG_ERR] = ~0; add_callback_log(&lst, log_cback); + mark_logs_temp(); } static char * @@ -69,22 +82,41 @@ clear_log_messages(void) messages = NULL; } +#define setup_options(opt,dflt) \ + do { \ + opt = options_new(); \ + opt->command = CMD_RUN_TOR; \ + options_init(opt); \ + \ + dflt = config_dup(&options_format, opt); \ + clear_log_messages(); \ + } while (0) + +#define VALID_DIR_AUTH "DirAuthority dizum orport=443 v3ident=E8A9C45" \ + "EDE6D711294FADF8E7951F4DE6CA56B58 194.109.206.212:80 7EA6 EAD6 FD83" \ + " 083C 538F 4403 8BBF A077 587D D755\n" +#define VALID_ALT_BRIDGE_AUTH \ + "AlternateBridgeAuthority dizum orport=443 v3ident=E8A9C45" \ + "EDE6D711294FADF8E7951F4DE6CA56B58 194.109.206.212:80 7EA6 EAD6 FD83" \ + " 083C 538F 4403 8BBF A077 587D D755\n" +#define VALID_ALT_DIR_AUTH \ + "AlternateDirAuthority dizum orport=443 v3ident=E8A9C45" \ + "EDE6D711294FADF8E7951F4DE6CA56B58 194.109.206.212:80 7EA6 EAD6 FD83" \ + " 083C 538F 4403 8BBF A077 587D D755\n" + static void test_options_validate_impl(const char *configuration, const char *expect_errmsg, int expect_log_severity, const char *expect_log) { - or_options_t *opt = options_new(); + or_options_t *opt=NULL; or_options_t *dflt; config_line_t *cl=NULL; char *msg=NULL; int r; - opt->command = CMD_RUN_TOR; - options_init(opt); - dflt = config_dup(&options_format, opt); - clear_log_messages(); + setup_options(opt, dflt); r = config_get_lines(configuration, &cl, 1); tt_int_op(r, OP_EQ, 0); @@ -126,6 +158,9 @@ test_options_validate_impl(const char *configuration, } done: + memarea_clear_freelist(); + escaped(NULL); + policies_free_all(); config_free_lines(cl); or_options_free(opt); or_options_free(dflt); @@ -147,6 +182,7 @@ test_options_validate(void *arg) { (void)arg; setup_log_callback(); + sandbox_disable_getaddrinfo_cache(); WANT_ERR("ExtORPort 500000", "Invalid ExtORPort"); @@ -159,12 +195,4098 @@ test_options_validate(void *arg) "ServerTransportOptions did not parse", LOG_WARN, "\"slingsnappy\" is not a k=v"); + WANT_ERR("DirPort 8080\nDirCache 0", + "DirPort configured but DirCache disabled."); + WANT_ERR("BridgeRelay 1\nDirCache 0", + "We're a bridge but DirCache is disabled."); + + close_temp_logs(); + clear_log_messages(); + return; +} + +#define MEGABYTEIFY(mb) (U64_LITERAL(mb) << 20) +static void +test_have_enough_mem_for_dircache(void *arg) +{ + (void)arg; + or_options_t *opt=NULL; + or_options_t *dflt; + config_line_t *cl=NULL; + char *msg=NULL;; + int r; + const char *configuration = "ORPort 8080\nDirCache 1", *expect_errmsg; + + setup_options(opt, dflt); + setup_log_callback(); + (void)dflt; + + r = config_get_lines(configuration, &cl, 1); + tt_int_op(r, OP_EQ, 0); + + r = config_assign(&options_format, opt, cl, 0, 0, &msg); + tt_int_op(r, OP_EQ, 0); + + /* 300 MB RAM available, DirCache enabled */ + r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg); + tt_int_op(r, OP_EQ, 0); + tt_assert(!msg); + + /* 200 MB RAM available, DirCache enabled */ + r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg); + tt_int_op(r, OP_EQ, -1); + expect_errmsg = "Being a directory cache (default) with less than "; + if (!strstr(msg, expect_errmsg)) { + TT_DIE(("Expected error message <%s> from <%s>, but got <%s>.", + expect_errmsg, configuration, msg)); + } + tor_free(msg); + + configuration = "ORPort 8080\nDirCache 1\nBridgeRelay 1"; + r = config_get_lines(configuration, &cl, 1); + tt_int_op(r, OP_EQ, 0); + + r = config_assign(&options_format, opt, cl, 0, 0, &msg); + tt_int_op(r, OP_EQ, 0); + + /* 300 MB RAM available, DirCache enabled, Bridge */ + r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg); + tt_int_op(r, OP_EQ, 0); + tt_assert(!msg); + + /* 200 MB RAM available, DirCache enabled, Bridge */ + r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg); + tt_int_op(r, OP_EQ, -1); + expect_errmsg = "Running a Bridge with less than "; + if (!strstr(msg, expect_errmsg)) { + TT_DIE(("Expected error message <%s> from <%s>, but got <%s>.", + expect_errmsg, configuration, msg)); + } + tor_free(msg); + + configuration = "ORPort 8080\nDirCache 0"; + r = config_get_lines(configuration, &cl, 1); + tt_int_op(r, OP_EQ, 0); + + r = config_assign(&options_format, opt, cl, 0, 0, &msg); + tt_int_op(r, OP_EQ, 0); + + /* 200 MB RAM available, DirCache disabled */ + r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(200), &msg); + tt_int_op(r, OP_EQ, 0); + tt_assert(!msg); + + /* 300 MB RAM available, DirCache disabled */ + r = have_enough_mem_for_dircache(opt, MEGABYTEIFY(300), &msg); + tt_int_op(r, OP_EQ, -1); + expect_errmsg = "DirCache is disabled and we are configured as a "; + if (!strstr(msg, expect_errmsg)) { + TT_DIE(("Expected error message <%s> from <%s>, but got <%s>.", + expect_errmsg, configuration, msg)); + } + tor_free(msg); + clear_log_messages(); + + done: + if (msg) + tor_free(msg); + tor_free(dflt); + tor_free(opt); + tor_free(cl); return; } +static const char *fixed_get_uname_result = NULL; + +static const char * +fixed_get_uname(void) +{ + return fixed_get_uname_result; +} + +#define TEST_OPTIONS_OLD_VALUES "TestingV3AuthInitialVotingInterval 1800\n" \ + "TestingClientBootstrapConsensusMaxDownloadTries 7\n" \ + "TestingClientBootstrapConsensusAuthorityOnlyMaxDownloadTries 4\n" \ + "TestingClientBootstrapConsensusMaxInProgressTries 3\n" \ + "TestingV3AuthInitialVoteDelay 300\n" \ + "TestingV3AuthInitialDistDelay 300\n" \ + "TestingClientMaxIntervalWithoutRequest 600\n" \ + "TestingDirConnectionMaxStall 600\n" \ + "TestingConsensusMaxDownloadTries 8\n" \ + "TestingDescriptorMaxDownloadTries 8\n" \ + "TestingMicrodescMaxDownloadTries 8\n" \ + "TestingCertMaxDownloadTries 8\n" + +#define TEST_OPTIONS_DEFAULT_VALUES TEST_OPTIONS_OLD_VALUES \ + "MaxClientCircuitsPending 1\n" \ + "RendPostPeriod 1000\n" \ + "KeepAlivePeriod 1\n" \ + "ConnLimit 1\n" \ + "V3AuthVotingInterval 300\n" \ + "V3AuthVoteDelay 20\n" \ + "V3AuthDistDelay 20\n" \ + "V3AuthNIntervalsValid 3\n" \ + "VirtualAddrNetworkIPv4 127.192.0.0/10\n" \ + "VirtualAddrNetworkIPv6 [FE80::]/10\n" \ + "SchedulerHighWaterMark__ 42\n" \ + "SchedulerLowWaterMark__ 10\n" + +typedef struct { + or_options_t *old_opt; + or_options_t *opt; + or_options_t *def_opt; +} options_test_data_t; + +static options_test_data_t * +get_options_test_data(const char *conf) +{ + config_line_t *cl=NULL; + options_test_data_t *result = tor_malloc(sizeof(options_test_data_t)); + result->opt = options_new(); + result->old_opt = options_new(); + result->def_opt = options_new(); + config_get_lines(conf, &cl, 1); + config_assign(&options_format, result->opt, cl, 0, 0, NULL); + config_free_lines(cl); + result->opt->LogTimeGranularity = 1; + result->opt->TokenBucketRefillInterval = 1; + config_get_lines(TEST_OPTIONS_OLD_VALUES, &cl, 1); + config_assign(&options_format, result->def_opt, cl, 0, 0, NULL); + config_free_lines(cl); + return result; +} + +static void +free_options_test_data(options_test_data_t *td) +{ + if (!td) return; + or_options_free(td->old_opt); + or_options_free(td->opt); + or_options_free(td->def_opt); + tor_free(td); +} + +static void +test_options_validate__uname_for_server(void *ignored) +{ + (void)ignored; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "ORListenAddress 127.0.0.1:5555"); + int previous_log = setup_capture_of_logs(LOG_WARN); + + MOCK(get_uname, fixed_get_uname); + fixed_get_uname_result = "Windows 95"; + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(0), OP_EQ, "Tor is running as a server, but you" + " are running Windows 95; this probably won't work. See https://www" + ".torproject.org/docs/faq.html#BestOSForRelay for details.\n"); + tor_free(msg); + + fixed_get_uname_result = "Windows 98"; + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(0), OP_EQ, "Tor is running as a server, but you" + " are running Windows 98; this probably won't work. See https://www" + ".torproject.org/docs/faq.html#BestOSForRelay for details.\n"); + tor_free(msg); + + fixed_get_uname_result = "Windows Me"; + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(0), OP_EQ, "Tor is running as a server, but you" + " are running Windows Me; this probably won't work. See https://www" + ".torproject.org/docs/faq.html#BestOSForRelay for details.\n"); + tor_free(msg); + + fixed_get_uname_result = "Windows 2000"; + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tor_free(msg); + + done: + UNMOCK(get_uname); + free_options_test_data(tdata); + tor_free(msg); + teardown_capture_of_logs(previous_log); +} + +static void +test_options_validate__outbound_addresses(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "OutboundBindAddress xxyy!!!sdfaf"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__data_directory(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "DataDirectory longreallyl" + "ongLONGLONGlongreallylong" + "LONGLONGlongreallylongLON" + "GLONGlongreallylongLONGLO" + "NGlongreallylongLONGLONGl" + "ongreallylongLONGLONGlong" + "reallylongLONGLONGlongrea" + "llylongLONGLONGlongreally" + "longLONGLONGlongreallylon" + "gLONGLONGlongreallylongLO" + "NGLONGlongreallylongLONGL" + "ONGlongreallylongLONGLONG" + "longreallylongLONGLONGlon" + "greallylongLONGLONGlongre" + "allylongLONGLONGlongreall" + "ylongLONGLONGlongreallylo" + "ngLONGLONGlongreallylongL" + "ONGLONGlongreallylongLONG" + "LONG"); // 440 characters + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Invalid DataDirectory"); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__nickname(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "Nickname ThisNickNameIsABitTooLong"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Nickname 'ThisNickNameIsABitTooLong' is wrong length or" + " contains illegal characters."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("Nickname AMoreValidNick"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(!msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("DataDirectory /tmp/somewhere"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(!msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__contactinfo(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "ORListenAddress 127.0.0.1:5555\nORPort 955"); + int previous_log = setup_capture_of_logs(LOG_DEBUG); + tdata->opt->ContactInfo = NULL; + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Your ContactInfo config option is not" + " set. Please consider setting it, so we can contact you if your" + " server is misconfigured or something else goes wrong.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ORListenAddress 127.0.0.1:5555\nORPort 955\n" + "ContactInfo hella@example.org"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_NE, + "Your ContactInfo config option is not" + " set. Please consider setting it, so we can contact you if your" + " server is misconfigured or something else goes wrong.\n"); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +extern int quiet_level; + +static void +test_options_validate__logs(void *ignored) +{ + (void)ignored; + int ret; + (void)ret; + char *msg; + int orig_quiet_level = quiet_level; + options_test_data_t *tdata = get_options_test_data(""); + tdata->opt->Logs = NULL; + tdata->opt->RunAsDaemon = 0; + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(tdata->opt->Logs->key, OP_EQ, "Log"); + tt_str_op(tdata->opt->Logs->value, OP_EQ, "notice stdout"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(""); + tdata->opt->Logs = NULL; + tdata->opt->RunAsDaemon = 0; + quiet_level = 1; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(tdata->opt->Logs->key, OP_EQ, "Log"); + tt_str_op(tdata->opt->Logs->value, OP_EQ, "warn stdout"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(""); + tdata->opt->Logs = NULL; + tdata->opt->RunAsDaemon = 0; + quiet_level = 2; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_assert(!tdata->opt->Logs); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(""); + tdata->opt->Logs = NULL; + tdata->opt->RunAsDaemon = 0; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 1, &msg); + tt_assert(!tdata->opt->Logs); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(""); + tdata->opt->Logs = NULL; + tdata->opt->RunAsDaemon = 1; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_assert(!tdata->opt->Logs); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(""); + tdata->opt->RunAsDaemon = 0; + config_line_t *cl=NULL; + config_get_lines("Log foo", &cl, 1); + tdata->opt->Logs = cl; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op((intptr_t)tdata->opt->Logs, OP_EQ, (intptr_t)cl); + + done: + quiet_level = orig_quiet_level; + free_options_test_data(tdata); + tor_free(msg); +} + +/* static config_line_t * */ +/* mock_config_line(const char *key, const char *val) */ +/* { */ +/* config_line_t *config_line = tor_malloc(sizeof(config_line_t)); */ +/* memset(config_line, 0, sizeof(config_line_t)); */ +/* config_line->key = tor_strdup(key); */ +/* config_line->value = tor_strdup(val); */ +/* return config_line; */ +/* } */ + +static void +test_options_validate__authdir(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_INFO); + options_test_data_t *tdata = get_options_test_data( + "AuthoritativeDirectory 1\n" + "Address this.should.not_exist.example.org"); + + sandbox_disable_getaddrinfo_cache(); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Failed to resolve/guess local address. See logs for" + " details."); + tt_str_op(mock_saved_log_at(0), OP_EQ, "Could not resolve local Address " + "'this.should.not_exist.example.org'. Failing.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(!msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Authoritative directory servers must set ContactInfo"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "TestingTorNetwork 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "AuthoritativeDir is set, but none of (Bridge/V3)" + "AuthoritativeDir is set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "AuthoritativeDir is set, but none of (Bridge/V3)" + "AuthoritativeDir is set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "RecommendedVersions 1.2, 3.14\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(tdata->opt->RecommendedClientVersions->value, OP_EQ, "1.2, 3.14"); + tt_str_op(tdata->opt->RecommendedServerVersions->value, OP_EQ, "1.2, 3.14"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "RecommendedVersions 1.2, 3.14\n" + "RecommendedClientVersions 25\n" + "RecommendedServerVersions 4.18\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(tdata->opt->RecommendedClientVersions->value, OP_EQ, "25"); + tt_str_op(tdata->opt->RecommendedServerVersions->value, OP_EQ, "4.18"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "VersioningAuthoritativeDirectory 1\n" + "RecommendedVersions 1.2, 3.14\n" + "RecommendedClientVersions 25\n" + "RecommendedServerVersions 4.18\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(msg, OP_EQ, "AuthoritativeDir is set, but none of (Bridge/V3)" + "AuthoritativeDir is set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "VersioningAuthoritativeDirectory 1\n" + "RecommendedServerVersions 4.18\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(msg, OP_EQ, "Versioning authoritative dir servers must set " + "Recommended*Versions."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "VersioningAuthoritativeDirectory 1\n" + "RecommendedClientVersions 4.18\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(msg, OP_EQ, "Versioning authoritative dir servers must set " + "Recommended*Versions."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "UseEntryGuards 1\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(0), OP_EQ, "Authoritative directory servers " + "can't set UseEntryGuards. Disabling.\n"); + tt_int_op(tdata->opt->UseEntryGuards, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "V3AuthoritativeDir 1\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(0), OP_EQ, "Authoritative directories always try" + " to download extra-info documents. Setting DownloadExtraInfo.\n"); + tt_int_op(tdata->opt->DownloadExtraInfo, OP_EQ, 1); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "DownloadExtraInfo 1\n" + "V3AuthoritativeDir 1\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(0), OP_NE, "Authoritative directories always try" + " to download extra-info documents. Setting DownloadExtraInfo.\n"); + tt_int_op(tdata->opt->DownloadExtraInfo, OP_EQ, 1); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(msg, OP_EQ, "AuthoritativeDir is set, but none of (Bridge/V3)" + "AuthoritativeDir is set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "BridgeAuthoritativeDir 1\n" + "ContactInfo hello@hello.com\n" + "V3BandwidthsFile non-existant-file\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(msg, OP_EQ, + "Running as authoritative directory, but no DirPort set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "BridgeAuthoritativeDir 1\n" + "ContactInfo hello@hello.com\n" + "V3BandwidthsFile non-existant-file\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(NULL, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(msg, OP_EQ, + "Running as authoritative directory, but no DirPort set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "BridgeAuthoritativeDir 1\n" + "ContactInfo hello@hello.com\n" + "GuardfractionFile non-existant-file\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(msg, OP_EQ, + "Running as authoritative directory, but no DirPort set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "BridgeAuthoritativeDir 1\n" + "ContactInfo hello@hello.com\n" + "GuardfractionFile non-existant-file\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + options_validate(NULL, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(msg, OP_EQ, + "Running as authoritative directory, but no DirPort set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "BridgeAuthoritativeDir 1\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Running as authoritative directory, but no DirPort set."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AuthoritativeDirectory 1\n" + "Address 100.200.10.1\n" + "DirPort 999\n" + "BridgeAuthoritativeDir 1\n" + "ContactInfo hello@hello.com\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Running as authoritative directory, but no ORPort set."); + tor_free(msg); + + // TODO: This case can't be reached, since clientonly is used to + // check when parsing port lines as well. + /* free_options_test_data(tdata); */ + /* tdata = get_options_test_data("AuthoritativeDirectory 1\n" */ + /* "Address 100.200.10.1\n" */ + /* "DirPort 999\n" */ + /* "ORPort 888\n" */ + /* "ClientOnly 1\n" */ + /* "BridgeAuthoritativeDir 1\n" */ + /* "ContactInfo hello@hello.com\n" */ + /* "SchedulerHighWaterMark__ 42\n" */ + /* "SchedulerLowWaterMark__ 10\n"); */ + /* mock_clean_saved_logs(); */ + /* ret = options_validate(tdata->old_opt, tdata->opt, */ + /* tdata->def_opt, 0, &msg); */ + /* tt_int_op(ret, OP_EQ, -1); */ + /* tt_str_op(msg, OP_EQ, "Running as authoritative directory, " */ + /* "but ClientOnly also set."); */ + + done: + teardown_capture_of_logs(previous_log); + sandbox_free_getaddrinfo_cache(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__relay_with_hidden_services(void *ignored) +{ + (void)ignored; + char *msg; + int previous_log = setup_capture_of_logs(LOG_DEBUG); + options_test_data_t *tdata = get_options_test_data( + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "HiddenServiceDir " + "/Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + ); + + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(1), OP_EQ, + "Tor is currently configured as a relay and a hidden service. " + "That's not very secure: you should probably run your hidden servi" + "ce in a separate Tor process, at least -- see " + "https://trac.torproject.org/8742\n"); + + done: + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +// TODO: it doesn't seem possible to hit the case of having no port lines at +// all, since there will be a default created for SocksPort +/* static void */ +/* test_options_validate__ports(void *ignored) */ +/* { */ +/* (void)ignored; */ +/* int ret; */ +/* char *msg; */ +/* int previous_log = setup_capture_of_logs(LOG_WARN); */ +/* options_test_data_t *tdata = get_options_test_data(""); */ +/* ret = options_validate(tdata->old_opt, tdata->opt, */ +/* tdata->def_opt, 0, &msg); */ +/* tt_str_op(mock_saved_log_at(0), OP_EQ, */ +/* "SocksPort, TransPort, NATDPort, DNSPort, and ORPort are all " */ +/* "undefined, and there aren't any hidden services configured. " */ +/* " Tor will still run, but probably won't do anything.\n"); */ +/* done: */ +/* teardown_capture_of_logs(previous_log); */ +/* free_options_test_data(tdata); */ +/* tor_free(msg); */ +/* } */ + +static void +test_options_validate__transproxy(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata; + +#ifdef USE_TRANSPARENT + // Test default trans proxy + tdata = get_options_test_data("TransProxyType default\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_DEFAULT); + tor_free(msg); + + // Test pf-divert trans proxy + free_options_test_data(tdata); + tdata = get_options_test_data("TransProxyType pf-divert\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + +#if !defined(__OpenBSD__) && !defined( DARWIN ) + tt_str_op(msg, OP_EQ, + "pf-divert is a OpenBSD-specific and OS X/Darwin-specific feature."); +#else + tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_PF_DIVERT); + tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without " + "any valid TransPort or TransListenAddress."); +#endif + tor_free(msg); + + // Test tproxy trans proxy + free_options_test_data(tdata); + tdata = get_options_test_data("TransProxyType tproxy\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + +#if !defined(__linux__) + tt_str_op(msg, OP_EQ, "TPROXY is a Linux-specific feature."); +#else + tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_TPROXY); + tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without any valid " + "TransPort or TransListenAddress."); +#endif + tor_free(msg); + + // Test ipfw trans proxy + free_options_test_data(tdata); + tdata = get_options_test_data("TransProxyType ipfw\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + +#if !defined(__FreeBSD__) && !defined( DARWIN ) + tt_str_op(msg, OP_EQ, "ipfw is a FreeBSD-specificand OS X/Darwin-specific " + "feature."); +#else + tt_int_op(tdata->opt->TransProxyType_parsed, OP_EQ, TPT_IPFW); + tt_str_op(msg, OP_EQ, "Cannot use TransProxyType without any valid " + "TransPort or TransListenAddress."); +#endif + tor_free(msg); + + // Test unknown trans proxy + free_options_test_data(tdata); + tdata = get_options_test_data("TransProxyType non-existant\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Unrecognized value for TransProxyType"); + tor_free(msg); + + // Test trans proxy success + free_options_test_data(tdata); + +#if defined(linux) + tdata = get_options_test_data("TransProxyType tproxy\n" + "TransPort 127.0.0.1:123\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(!msg); +#endif +#if defined(__FreeBSD__) || defined( DARWIN ) + tdata = get_options_test_data("TransProxyType ipfw\n" + "TransPort 127.0.0.1:123\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(!msg); +#endif +#if defined(__OpenBSD__) + tdata = get_options_test_data("TransProxyType pf-divert\n" + "TransPort 127.0.0.1:123\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(!msg); +#endif + +#else + tdata = get_options_test_data("TransPort 127.0.0.1:555\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TransPort and TransListenAddress are disabled in " + "this build."); + tor_free(msg); +#endif + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +NS_DECL(country_t, geoip_get_country, (const char *country)); + +static country_t +NS(geoip_get_country)(const char *countrycode) +{ + (void)countrycode; + CALLED(geoip_get_country)++; + + return 1; +} + +static void +test_options_validate__exclude_nodes(void *ignored) +{ + (void)ignored; + + NS_MOCK(geoip_get_country); + + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_WARN); + options_test_data_t *tdata = get_options_test_data( + "ExcludeExitNodes {us}\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(smartlist_len(tdata->opt->ExcludeExitNodesUnion_->list), OP_EQ, 1); + tt_str_op((char *) + (smartlist_get(tdata->opt->ExcludeExitNodesUnion_->list, 0)), + OP_EQ, "{us}"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ExcludeNodes {cn}\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(smartlist_len(tdata->opt->ExcludeExitNodesUnion_->list), OP_EQ, 1); + tt_str_op((char *) + (smartlist_get(tdata->opt->ExcludeExitNodesUnion_->list, 0)), + OP_EQ, "{cn}"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ExcludeNodes {cn}\n" + "ExcludeExitNodes {us} {cn}\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(smartlist_len(tdata->opt->ExcludeExitNodesUnion_->list), OP_EQ, 2); + tt_str_op((char *) + (smartlist_get(tdata->opt->ExcludeExitNodesUnion_->list, 0)), + OP_EQ, "{us} {cn}"); + tt_str_op((char *) + (smartlist_get(tdata->opt->ExcludeExitNodesUnion_->list, 1)), + OP_EQ, "{cn}"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ExcludeNodes {cn}\n" + "StrictNodes 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "You have asked to exclude certain relays from all positions " + "in your circuits. Expect hidden services and other Tor " + "features to be broken in unpredictable ways.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ExcludeNodes {cn}\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_NE, + "You have asked to exclude certain relays from all positions " + "in your circuits. Expect hidden services and other Tor " + "features to be broken in unpredictable ways.\n"); + tor_free(msg); + + done: + NS_UNMOCK(geoip_get_country); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__scheduler(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_DEBUG); + options_test_data_t *tdata = get_options_test_data( + "SchedulerLowWaterMark__ 0\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Bad SchedulerLowWaterMark__ option\n"); + tor_free(msg); + + // TODO: this test cannot run on platforms where UINT32_MAX == UINT64_MAX. + // I suspect it's unlikely this branch can actually happen + /* free_options_test_data(tdata); */ + /* tdata = get_options_test_data( */ + /* "SchedulerLowWaterMark 10000000000000000000\n"); */ + /* tdata->opt->SchedulerLowWaterMark__ = (uint64_t)UINT32_MAX; */ + /* tdata->opt->SchedulerLowWaterMark__++; */ + /* mock_clean_saved_logs(); */ + /* ret = options_validate(tdata->old_opt, tdata->opt, */ + /* tdata->def_opt, 0, &msg); */ + /* tt_int_op(ret, OP_EQ, -1); */ + /* tt_str_op(mock_saved_log_at(1), OP_EQ, */ + /* "Bad SchedulerLowWaterMark__ option\n"); */ + + free_options_test_data(tdata); + tdata = get_options_test_data("SchedulerLowWaterMark__ 42\n" + "SchedulerHighWaterMark__ 42\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Bad SchedulerHighWaterMark option\n"); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__node_families(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "NodeFamily flux, flax\n" + "NodeFamily somewhere\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(tdata->opt->NodeFamilySets); + tt_int_op(smartlist_len(tdata->opt->NodeFamilySets), OP_EQ, 2); + tt_str_op((char *)(smartlist_get( + ((routerset_t *)smartlist_get(tdata->opt->NodeFamilySets, 0))->list, 0)), + OP_EQ, "flux"); + tt_str_op((char *)(smartlist_get( + ((routerset_t *)smartlist_get(tdata->opt->NodeFamilySets, 0))->list, 1)), + OP_EQ, "flax"); + tt_str_op((char *)(smartlist_get( + ((routerset_t *)smartlist_get(tdata->opt->NodeFamilySets, 1))->list, 0)), + OP_EQ, "somewhere"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(!tdata->opt->NodeFamilySets); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("NodeFamily !flux\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(tdata->opt->NodeFamilySets); + tt_int_op(smartlist_len(tdata->opt->NodeFamilySets), OP_EQ, 0); + tor_free(msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__tlsec(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_DEBUG); + options_test_data_t *tdata = get_options_test_data( + "TLSECGroup ed25519\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Unrecognized TLSECGroup: Falling back to the default.\n"); + tt_assert(!tdata->opt->TLSECGroup); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("TLSECGroup P224\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_NE, + "Unrecognized TLSECGroup: Falling back to the default.\n"); + tt_assert(tdata->opt->TLSECGroup); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("TLSECGroup P256\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(0), OP_NE, + "Unrecognized TLSECGroup: Falling back to the default.\n"); + tt_assert(tdata->opt->TLSECGroup); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__token_bucket(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data(""); + + tdata->opt->TokenBucketRefillInterval = 0; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "TokenBucketRefillInterval must be between 1 and 1000 inclusive."); + tor_free(msg); + + tdata->opt->TokenBucketRefillInterval = 1001; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "TokenBucketRefillInterval must be between 1 and 1000 inclusive."); + tor_free(msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__recommended_packages(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_WARN); + options_test_data_t *tdata = get_options_test_data( + "RecommendedPackages foo 1.2 http://foo.com sha1=123123123123\n" + "RecommendedPackages invalid-package-line\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_NE, "Invalid RecommendedPackage line " + "invalid-package-line will be ignored\n"); + + done: + escaped(NULL); // This will free the leaking memory from the previous escaped + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__fetch_dir(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "FetchDirInfoExtraEarly 1\n" + "FetchDirInfoEarly 0\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "FetchDirInfoExtraEarly requires that you" + " also set FetchDirInfoEarly"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("FetchDirInfoExtraEarly 1\n" + "FetchDirInfoEarly 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_NE, "FetchDirInfoExtraEarly requires that you" + " also set FetchDirInfoEarly"); + tor_free(msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__conn_limit(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "ConnLimit 0\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "ConnLimit must be greater than 0, but was set to 0"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "MaxClientCircuitsPending must be between 1 and 1024, " + "but was set to 0"); + tor_free(msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__paths_needed(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_WARN); + options_test_data_t *tdata = get_options_test_data( + "PathsNeededToBuildCircuits 0.1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(tdata->opt->PathsNeededToBuildCircuits > 0.24 && + tdata->opt->PathsNeededToBuildCircuits < 0.26); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "PathsNeededToBuildCircuits is too low. Increasing to 0.25\n"); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data("PathsNeededToBuildCircuits 0.99\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(tdata->opt->PathsNeededToBuildCircuits > 0.94 && + tdata->opt->PathsNeededToBuildCircuits < 0.96); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "PathsNeededToBuildCircuits is " + "too high. Decreasing to 0.95\n"); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data("PathsNeededToBuildCircuits 0.91\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(tdata->opt->PathsNeededToBuildCircuits > 0.90 && + tdata->opt->PathsNeededToBuildCircuits < 0.92); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__max_client_circuits(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "MaxClientCircuitsPending 0\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "MaxClientCircuitsPending must be between 1 and 1024," + " but was set to 0"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("MaxClientCircuitsPending 1025\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "MaxClientCircuitsPending must be between 1 and 1024," + " but was set to 1025"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "KeepalivePeriod option must be positive."); + tor_free(msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__ports(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "FirewallPorts 65537\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Port '65537' out of range in FirewallPorts"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("FirewallPorts 1\n" + "LongLivedPorts 124444\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Port '124444' out of range in LongLivedPorts"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("FirewallPorts 1\n" + "LongLivedPorts 2\n" + "RejectPlaintextPorts 112233\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Port '112233' out of range in RejectPlaintextPorts"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("FirewallPorts 1\n" + "LongLivedPorts 2\n" + "RejectPlaintextPorts 3\n" + "WarnPlaintextPorts 65536\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Port '65536' out of range in WarnPlaintextPorts"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("FirewallPorts 1\n" + "LongLivedPorts 2\n" + "RejectPlaintextPorts 3\n" + "WarnPlaintextPorts 4\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "KeepalivePeriod option must be positive."); + tor_free(msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__reachable_addresses(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_NOTICE); + options_test_data_t *tdata = get_options_test_data( + "FascistFirewall 1\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(mock_saved_log_number(), OP_EQ, 6); + tt_str_op(mock_saved_log_at(1), OP_EQ, "Converting FascistFirewall config " + "option to new format: \"ReachableDirAddresses *:80\"\n"); + tt_str_op(tdata->opt->ReachableDirAddresses->value, OP_EQ, "*:80"); + tt_str_op(mock_saved_log_at(2), OP_EQ, "Converting FascistFirewall config " + "option to new format: \"ReachableORAddresses *:443\"\n"); + tt_str_op(tdata->opt->ReachableORAddresses->value, OP_EQ, "*:443"); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data("FascistFirewall 1\n" + "ReachableDirAddresses *:81\n" + "ReachableORAddresses *:444\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + tdata->opt->FirewallPorts = smartlist_new(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(mock_saved_log_number(), OP_EQ, 4); + tt_str_op(tdata->opt->ReachableDirAddresses->value, OP_EQ, "*:81"); + tt_str_op(tdata->opt->ReachableORAddresses->value, OP_EQ, "*:444"); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data("FascistFirewall 1\n" + "FirewallPort 123\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(mock_saved_log_number(), OP_EQ, 5); + tt_str_op(mock_saved_log_at(1), OP_EQ, "Converting FascistFirewall and " + "FirewallPorts config options to new format: " + "\"ReachableAddresses *:123\"\n"); + tt_str_op(tdata->opt->ReachableAddresses->value, OP_EQ, "*:123"); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data("FascistFirewall 1\n" + "ReachableAddresses *:82\n" + "ReachableAddresses *:83\n" + "ReachableAddresses reject *:*\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(mock_saved_log_number(), OP_EQ, 4); + tt_str_op(tdata->opt->ReachableAddresses->value, OP_EQ, "*:82"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ReachableAddresses *:82\n" + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Servers must be able to freely connect to the rest of" + " the Internet, so they must not set Reachable*Addresses or" + " FascistFirewall."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ReachableORAddresses *:82\n" + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Servers must be able to freely connect to the rest of" + " the Internet, so they must not set Reachable*Addresses or" + " FascistFirewall."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("ReachableDirAddresses *:82\n" + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Servers must be able to freely connect to the rest of" + " the Internet, so they must not set Reachable*Addresses or" + " FascistFirewall."); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__use_bridges(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "UseBridges 1\n" + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Servers must be able to freely connect to the rest of" + " the Internet, so they must not set UseBridges."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("UseBridges 1\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_NE, "Servers must be able to freely connect to the rest of" + " the Internet, so they must not set UseBridges."); + tor_free(msg); + + NS_MOCK(geoip_get_country); + free_options_test_data(tdata); + tdata = get_options_test_data("UseBridges 1\n" + "EntryNodes {cn}\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "You cannot set both UseBridges and EntryNodes."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "UseBridges 1\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "If you set UseBridges, you must specify at least one bridge."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "UseBridges 1\n" + "Bridge 10.0.0.1\n" + "Bridge !!!\n" + ); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Bridge line did not parse. See logs for details."); + tor_free(msg); + + done: + NS_UNMOCK(geoip_get_country); + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__entry_nodes(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + NS_MOCK(geoip_get_country); + options_test_data_t *tdata = get_options_test_data( + "EntryNodes {cn}\n" + "UseEntryGuards 0\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "If EntryNodes is set, UseEntryGuards must be enabled."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("EntryNodes {cn}\n" + "UseEntryGuards 1\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "KeepalivePeriod option must be positive."); + tor_free(msg); + + done: + NS_UNMOCK(geoip_get_country); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__invalid_nodes(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "AllowInvalidNodes something_stupid\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Unrecognized value 'something_stupid' in AllowInvalidNodes"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AllowInvalidNodes entry, middle, exit\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(tdata->opt->AllowInvalid_, OP_EQ, ALLOW_INVALID_ENTRY | + ALLOW_INVALID_EXIT | ALLOW_INVALID_MIDDLE); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("AllowInvalidNodes introduction, rendezvous\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(tdata->opt->AllowInvalid_, OP_EQ, ALLOW_INVALID_INTRODUCTION | + ALLOW_INVALID_RENDEZVOUS); + tor_free(msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__safe_logging(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = get_options_test_data( + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(tdata->opt->SafeLogging_, OP_EQ, SAFELOG_SCRUB_NONE); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("SafeLogging 0\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(tdata->opt->SafeLogging_, OP_EQ, SAFELOG_SCRUB_NONE); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("SafeLogging Relay\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(tdata->opt->SafeLogging_, OP_EQ, SAFELOG_SCRUB_RELAY); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("SafeLogging 1\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_int_op(tdata->opt->SafeLogging_, OP_EQ, SAFELOG_SCRUB_ALL); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("SafeLogging stuffy\n" + "MaxClientCircuitsPending 1\n" + "ConnLimit 1\n" + "SchedulerHighWaterMark__ 42\n" + "SchedulerLowWaterMark__ 10\n"); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Unrecognized value '\"stuffy\"' in SafeLogging"); + tor_free(msg); + + done: + escaped(NULL); // This will free the leaking memory from the previous escaped + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__publish_server_descriptor(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_WARN); + options_test_data_t *tdata = get_options_test_data( + "PublishServerDescriptor bridge\n" TEST_OPTIONS_DEFAULT_VALUES + ); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_assert(!msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("PublishServerDescriptor humma\n" + TEST_OPTIONS_DEFAULT_VALUES); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Unrecognized value in PublishServerDescriptor"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("PublishServerDescriptor bridge, v3\n" + TEST_OPTIONS_DEFAULT_VALUES); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Bridges are not supposed to publish router " + "descriptors to the directory authorities. Please correct your " + "PublishServerDescriptor line."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("BridgeRelay 1\n" + "PublishServerDescriptor v3\n" + TEST_OPTIONS_DEFAULT_VALUES); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Bridges are not supposed to publish router " + "descriptors to the directory authorities. Please correct your " + "PublishServerDescriptor line."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("BridgeRelay 1\n" TEST_OPTIONS_DEFAULT_VALUES); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_NE, "Bridges are not supposed to publish router " + "descriptors to the directory authorities. Please correct your " + "PublishServerDescriptor line."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data("BridgeRelay 1\n" + "DirPort 999\n" TEST_OPTIONS_DEFAULT_VALUES); + + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(mock_saved_log_at(1), OP_EQ, "Can't set a DirPort on a bridge " + "relay; disabling DirPort\n"); + tt_assert(!tdata->opt->DirPort_lines); + tt_assert(!tdata->opt->DirPort_set); + + done: + teardown_capture_of_logs(previous_log); + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__testing(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + +#define ENSURE_DEFAULT(varname, varval) \ + STMT_BEGIN \ + free_options_test_data(tdata); \ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES \ + #varname " " #varval "\n"); \ + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);\ + tt_str_op(msg, OP_EQ, \ + #varname " may only be changed in testing Tor networks!"); \ + tt_int_op(ret, OP_EQ, -1); \ + tor_free(msg); \ + \ + free_options_test_data(tdata); \ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES \ + #varname " " #varval "\n" \ + VALID_DIR_AUTH \ + "TestingTorNetwork 1\n"); \ + \ + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);\ + if (msg) { \ + tt_str_op(msg, OP_NE, \ + #varname " may only be changed in testing Tor networks!"); \ + tor_free(msg); \ + } \ + \ + free_options_test_data(tdata); \ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES \ + #varname " " #varval "\n" \ + "___UsingTestNetworkDefaults 1\n"); \ + \ + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);\ + if (msg) { \ + tt_str_op(msg, OP_NE, \ + #varname " may only be changed in testing Tor networks!"); \ + tor_free(msg); \ + } \ + STMT_END + + ENSURE_DEFAULT(TestingV3AuthInitialVotingInterval, 3600); + ENSURE_DEFAULT(TestingV3AuthInitialVoteDelay, 3000); + ENSURE_DEFAULT(TestingV3AuthInitialDistDelay, 3000); + ENSURE_DEFAULT(TestingV3AuthVotingStartOffset, 3000); + ENSURE_DEFAULT(TestingAuthDirTimeToLearnReachability, 3000); + ENSURE_DEFAULT(TestingEstimatedDescriptorPropagationTime, 3000); + ENSURE_DEFAULT(TestingServerDownloadSchedule, 3000); + ENSURE_DEFAULT(TestingClientDownloadSchedule, 3000); + ENSURE_DEFAULT(TestingServerConsensusDownloadSchedule, 3000); + ENSURE_DEFAULT(TestingClientConsensusDownloadSchedule, 3000); + ENSURE_DEFAULT(TestingBridgeDownloadSchedule, 3000); + ENSURE_DEFAULT(TestingClientMaxIntervalWithoutRequest, 3000); + ENSURE_DEFAULT(TestingDirConnectionMaxStall, 3000); + ENSURE_DEFAULT(TestingConsensusMaxDownloadTries, 3000); + ENSURE_DEFAULT(TestingDescriptorMaxDownloadTries, 3000); + ENSURE_DEFAULT(TestingMicrodescMaxDownloadTries, 3000); + ENSURE_DEFAULT(TestingCertMaxDownloadTries, 3000); + ENSURE_DEFAULT(TestingAuthKeyLifetime, 3000); + ENSURE_DEFAULT(TestingLinkCertLifetime, 3000); + ENSURE_DEFAULT(TestingSigningKeySlop, 3000); + ENSURE_DEFAULT(TestingAuthKeySlop, 3000); + ENSURE_DEFAULT(TestingLinkKeySlop, 3000); + + done: + escaped(NULL); // This will free the leaking memory from the previous escaped + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__hidserv(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_WARN); + + options_test_data_t *tdata = get_options_test_data( + TEST_OPTIONS_DEFAULT_VALUES); + tdata->opt->MinUptimeHidServDirectoryV2 = -1; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(1), OP_EQ, "MinUptimeHidServDirectoryV2 " + "option must be at least 0 seconds. Changing to 0.\n"); + tt_int_op(tdata->opt->MinUptimeHidServDirectoryV2, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "RendPostPeriod 1\n" ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(1), OP_EQ, "RendPostPeriod option is too short;" + " raising to 600 seconds.\n"); + tt_int_op(tdata->opt->RendPostPeriod, OP_EQ, 600); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "RendPostPeriod 302401\n" ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(1), OP_EQ, "RendPostPeriod is too large; " + "clipping to 302400s.\n"); + tt_int_op(tdata->opt->RendPostPeriod, OP_EQ, 302400); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__predicted_ports(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + int previous_log = setup_capture_of_logs(LOG_WARN); + + options_test_data_t *tdata = get_options_test_data( + "PredictedPortsRelevanceTime 100000000\n" + TEST_OPTIONS_DEFAULT_VALUES); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(1), OP_EQ, "PredictedPortsRelevanceTime is too " + "large; clipping to 3600s.\n"); + tt_int_op(tdata->opt->PredictedPortsRelevanceTime, OP_EQ, 3600); + + done: + teardown_capture_of_logs(previous_log); + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__path_bias(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + + options_test_data_t *tdata = get_options_test_data( + TEST_OPTIONS_DEFAULT_VALUES + "PathBiasNoticeRate 1.1\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "PathBiasNoticeRate is too high. It must be between 0 and 1.0"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "PathBiasWarnRate 1.1\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "PathBiasWarnRate is too high. It must be between 0 and 1.0"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "PathBiasExtremeRate 1.1\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "PathBiasExtremeRate is too high. It must be between 0 and 1.0"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "PathBiasNoticeUseRate 1.1\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "PathBiasNoticeUseRate is too high. It must be between 0 and 1.0"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "PathBiasExtremeUseRate 1.1\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "PathBiasExtremeUseRate is too high. It must be between 0 and 1.0"); + tor_free(msg); + + done: + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__bandwidth(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + +#define ENSURE_BANDWIDTH_PARAM(p) \ + STMT_BEGIN \ + free_options_test_data(tdata); \ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES #p " 3Gb\n"); \ + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);\ + tt_int_op(ret, OP_EQ, -1); \ + tt_mem_op(msg, OP_EQ, #p " (3221225471) must be at most 2147483647", 40); \ + tor_free(msg); \ + STMT_END + + ENSURE_BANDWIDTH_PARAM(BandwidthRate); + ENSURE_BANDWIDTH_PARAM(BandwidthBurst); + ENSURE_BANDWIDTH_PARAM(MaxAdvertisedBandwidth); + ENSURE_BANDWIDTH_PARAM(RelayBandwidthRate); + ENSURE_BANDWIDTH_PARAM(RelayBandwidthBurst); + ENSURE_BANDWIDTH_PARAM(PerConnBWRate); + ENSURE_BANDWIDTH_PARAM(PerConnBWBurst); + ENSURE_BANDWIDTH_PARAM(AuthDirFastGuarantee); + ENSURE_BANDWIDTH_PARAM(AuthDirGuardBWGuarantee); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "RelayBandwidthRate 1000\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->RelayBandwidthBurst, OP_EQ, 1000); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "RelayBandwidthBurst 1001\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->RelayBandwidthRate, OP_EQ, 1001); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "RelayBandwidthRate 1001\n" + "RelayBandwidthBurst 1000\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "RelayBandwidthBurst must be at least equal to " + "RelayBandwidthRate."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "BandwidthRate 1001\n" + "BandwidthBurst 1000\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "BandwidthBurst must be at least equal to BandwidthRate."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "RelayBandwidthRate 1001\n" + "BandwidthRate 1000\n" + "BandwidthBurst 1000\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->BandwidthRate, OP_EQ, 1001); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "RelayBandwidthRate 1001\n" + "BandwidthRate 1000\n" + "RelayBandwidthBurst 1001\n" + "BandwidthBurst 1000\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->BandwidthBurst, OP_EQ, 1001); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "BandwidthRate 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "BandwidthRate is set to 1 bytes/second. For servers," + " it must be at least 76800."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "BandwidthRate 76800\n" + "MaxAdvertisedBandwidth 30000\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "MaxAdvertisedBandwidth is set to 30000 bytes/second." + " For servers, it must be at least 38400."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "BandwidthRate 76800\n" + "RelayBandwidthRate 1\n" + "MaxAdvertisedBandwidth 38400\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "RelayBandwidthRate is set to 1 bytes/second. For " + "servers, it must be at least 76800."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "BandwidthRate 76800\n" + "BandwidthBurst 76800\n" + "RelayBandwidthRate 76800\n" + "MaxAdvertisedBandwidth 38400\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__circuits(void *ignored) +{ + (void)ignored; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "MaxCircuitDirtiness 2592001\n"); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(1), OP_EQ, "MaxCircuitDirtiness option is too " + "high; setting to 30 days.\n"); + tt_int_op(tdata->opt->MaxCircuitDirtiness, OP_EQ, 2592000); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "CircuitStreamTimeout 1\n"); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(2), OP_EQ, "CircuitStreamTimeout option is too" + " short; raising to 10 seconds.\n"); + tt_int_op(tdata->opt->CircuitStreamTimeout, OP_EQ, 10); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "CircuitStreamTimeout 111\n"); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(2), OP_NE, "CircuitStreamTimeout option is too" + " short; raising to 10 seconds.\n"); + tt_int_op(tdata->opt->CircuitStreamTimeout, OP_EQ, 111); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HeartbeatPeriod 1\n"); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(2), OP_EQ, "HeartbeatPeriod option is too short;" + " raising to 1800 seconds.\n"); + tt_int_op(tdata->opt->HeartbeatPeriod, OP_EQ, 1800); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HeartbeatPeriod 1982\n"); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(2), OP_NE, "HeartbeatPeriod option is too short;" + " raising to 1800 seconds.\n"); + tt_int_op(tdata->opt->HeartbeatPeriod, OP_EQ, 1982); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "CircuitBuildTimeout 1\n" + ); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(1), OP_EQ, "CircuitBuildTimeout is shorter (1" + " seconds) than the recommended minimum (10 seconds), and " + "LearnCircuitBuildTimeout is disabled. If tor isn't working, " + "raise this value or enable LearnCircuitBuildTimeout.\n"); + tor_free(msg); + + free_options_test_data(tdata); + mock_clean_saved_logs(); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "CircuitBuildTimeout 11\n" + ); + options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_str_op(mock_saved_log_at(1), OP_NE, "CircuitBuildTimeout is shorter (1 " + "seconds) than the recommended minimum (10 seconds), and " + "LearnCircuitBuildTimeout is disabled. If tor isn't working, " + "raise this value or enable LearnCircuitBuildTimeout.\n"); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__port_forwarding(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "PortForwarding 1\nSandbox 1\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "PortForwarding is not compatible with Sandbox;" + " at most one can be set"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "PortForwarding 1\nSandbox 0\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_assert(!msg); + tor_free(msg); + + done: + free_options_test_data(tdata); + memarea_clear_freelist(); + policies_free_all(); + tor_free(msg); +} + +static void +test_options_validate__tor2web(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Tor2webRendezvousPoints 1\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Tor2webRendezvousPoints cannot be set without Tor2webMode."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Tor2webRendezvousPoints 1\nTor2webMode 1\n"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__rend(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "UseEntryGuards 0\n" + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(1), OP_EQ, "UseEntryGuards is disabled, but you" + " have configured one or more hidden services on this Tor " + "instance. Your hidden services will be very easy to locate using" + " a well-known attack -- see http://freehaven.net/anonbib/#hs-" + "attack06 for details.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data( + TEST_OPTIONS_DEFAULT_VALUES + "UseEntryGuards 1\n" + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(1), OP_NE, "UseEntryGuards is disabled, but you" + " have configured one or more hidden services on this Tor " + "instance. Your hidden services will be very easy to locate using" + " a well-known attack -- see http://freehaven.net/anonbib/#hs-" + "attack06 for details.\n"); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HiddenServicePort 80 127.0.0.1:8080\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Failed to configure rendezvous options. See logs for details."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HidServAuth failed\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Failed to configure client authorization for hidden " + "services. See logs for details."); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__accounting(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccountingRule something_bad\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "AccountingRule must be 'sum', 'max', 'in', or 'out'"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccountingRule sum\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->AccountingRule, OP_EQ, ACCT_SUM); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccountingRule max\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->AccountingRule, OP_EQ, ACCT_MAX); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccountingStart fail\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Failed to parse accounting options. See logs for details."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccountingMax 10\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data( + TEST_OPTIONS_DEFAULT_VALUES + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "BandwidthRate 76800\n" + "BandwidthBurst 76800\n" + "MaxAdvertisedBandwidth 38400\n" + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + "AccountingMax 10\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(4), OP_EQ, "Using accounting with a hidden " + "service and an ORPort is risky: your hidden service(s) and " + "your public address will all turn off at the same time, " + "which may alert observers that they are being run by the " + "same party.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data( + TEST_OPTIONS_DEFAULT_VALUES + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + "AccountingMax 10\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(4), OP_NE, "Using accounting with a hidden " + "service and an ORPort is risky: your hidden service(s) and " + "your public address will all turn off at the same time, " + "which may alert observers that they are being run by the " + "same party.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data( + TEST_OPTIONS_DEFAULT_VALUES + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service/\n" + "HiddenServicePort 80 127.0.0.1:8080\n" + "HiddenServiceDir /Library/Tor/var/lib/tor/hidden_service2/\n" + "HiddenServicePort 81 127.0.0.1:8081\n" + "AccountingMax 10\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(3), OP_EQ, "Using accounting with multiple " + "hidden services is risky: they will all turn off at the same" + " time, which may alert observers that they are being run by " + "the same party.\n"); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__proxy(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + sandbox_disable_getaddrinfo_cache(); + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxy 127.0.42.1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->HTTPProxyPort, OP_EQ, 80); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxy 127.0.42.1:444\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->HTTPProxyPort, OP_EQ, 444); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxy not_so_valid!\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "HTTPProxy failed to parse or resolve. Please fix."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxyAuthenticator " + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreeonetwothreeonetwothree" + + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "HTTPProxyAuthenticator is too long (>= 512 chars)."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxyAuthenticator validauth\n" + + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpsProxy 127.0.42.1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->HTTPSProxyPort, OP_EQ, 443); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpsProxy 127.0.42.1:444\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->HTTPSProxyPort, OP_EQ, 444); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpsProxy not_so_valid!\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "HTTPSProxy failed to parse or resolve. Please fix."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpsProxyAuthenticator " + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreonetwothreonetwothreonetwothre" + "onetwothreonetwothreonetwothreonetwothreonetw" + "othreonetwothreeonetwothreeonetwothree" + + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "HTTPSProxyAuthenticator is too long (>= 512 chars)."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpsProxyAuthenticator validauth\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks4Proxy 127.0.42.1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->Socks4ProxyPort, OP_EQ, 1080); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks4Proxy 127.0.42.1:444\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->Socks4ProxyPort, OP_EQ, 444); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks4Proxy not_so_valid!\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Socks4Proxy failed to parse or resolve. Please fix."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks5Proxy 127.0.42.1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->Socks5ProxyPort, OP_EQ, 1080); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks5Proxy 127.0.42.1:444\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->Socks5ProxyPort, OP_EQ, 444); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks5Proxy not_so_valid!\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Socks5Proxy failed to parse or resolve. Please fix."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks4Proxy 215.1.1.1\n" + "Socks5Proxy 215.1.1.2\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "You have configured more than one proxy type. " + "(Socks4Proxy|Socks5Proxy|HTTPSProxy)"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxy 215.1.1.1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_EQ, "HTTPProxy configured, but no SOCKS " + "proxy or HTTPS proxy configured. Watch out: this configuration " + "will proxy unencrypted directory connections only.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxy 215.1.1.1\n" + "Socks4Proxy 215.1.1.1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, "HTTPProxy configured, but no SOCKS " + "proxy or HTTPS proxy configured. Watch out: this configuration " + "will proxy unencrypted directory connections only.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxy 215.1.1.1\n" + "Socks5Proxy 215.1.1.1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, "HTTPProxy configured, but no SOCKS " + "proxy or HTTPS proxy configured. Watch out: this configuration " + "will proxy unencrypted directory connections only.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HttpProxy 215.1.1.1\n" + "HttpsProxy 215.1.1.1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "HTTPProxy configured, but no SOCKS proxy or HTTPS proxy " + "configured. Watch out: this configuration will proxy " + "unencrypted directory connections only.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + ); + tdata->opt->Socks5ProxyUsername = tor_strdup(""); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Socks5ProxyUsername must be between 1 and 255 characters."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + ); + tdata->opt->Socks5ProxyUsername = + tor_strdup("ABCDEABCDE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789AB" + "CDEABCDE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789ABCD" + "EABCDE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789ABCDEA" + "BCDE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789ABCDEABC" + "DE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Socks5ProxyUsername must be between 1 and 255 characters."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks5ProxyUsername hello_world\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Socks5ProxyPassword must be included with " + "Socks5ProxyUsername."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks5ProxyUsername hello_world\n" + ); + tdata->opt->Socks5ProxyPassword = tor_strdup(""); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Socks5ProxyPassword must be between 1 and 255 characters."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks5ProxyUsername hello_world\n" + ); + tdata->opt->Socks5ProxyPassword = + tor_strdup("ABCDEABCDE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789AB" + "CDEABCDE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789ABCD" + "EABCDE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789ABCDEA" + "BCDE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789ABCDEABC" + "DE0123456789ABCDEABCDE0123456789ABCDEABCDE0123456789"); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Socks5ProxyPassword must be between 1 and 255 characters."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks5ProxyUsername hello_world\n" + "Socks5ProxyPassword world_hello\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "Socks5ProxyPassword hello_world\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Socks5ProxyPassword must be included with " + "Socks5ProxyUsername."); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + memarea_clear_freelist(); + policies_free_all(); + sandbox_free_getaddrinfo_cache(); + tor_free(msg); +} + +static void +test_options_validate__control(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HashedControlPassword something_incorrect\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Bad HashedControlPassword: wrong length or bad encoding"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "HashedControlPassword 16:872860B76453A77D60CA" + "2BB8C1A7042072093276A3D701AD684053EC4C\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data( + TEST_OPTIONS_DEFAULT_VALUES + "__HashedControlSessionPassword something_incorrect\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Bad HashedControlSessionPassword: wrong length or " + "bad encoding"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "__HashedControlSessionPassword 16:872860B7645" + "3A77D60CA2BB8C1A7042072093276A3D701AD684053EC" + "4C\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data( + TEST_OPTIONS_DEFAULT_VALUES + "__OwningControllerProcess something_incorrect\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Bad OwningControllerProcess: invalid PID"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "__OwningControllerProcess 123\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ControlPort 127.0.0.1:1234\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_EQ, + "ControlPort is open, but no authentication method has been " + "configured. This means that any program on your computer can " + "reconfigure your Tor. That's bad! You should upgrade your Tor" + " controller as soon as possible.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ControlPort 127.0.0.1:1234\n" + "HashedControlPassword 16:872860B76453A77D60CA" + "2BB8C1A7042072093276A3D701AD684053EC4C\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "ControlPort is open, but no authentication method has been " + "configured. This means that any program on your computer can " + "reconfigure your Tor. That's bad! You should upgrade your Tor " + "controller as soon as possible.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ControlPort 127.0.0.1:1234\n" + "__HashedControlSessionPassword 16:872860B7645" + "3A77D60CA2BB8C1A7042072093276A3D701AD684053EC" + "4C\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "ControlPort is open, but no authentication method has been " + "configured. This means that any program on your computer can " + "reconfigure your Tor. That's bad! You should upgrade your Tor " + "controller as soon as possible.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ControlPort 127.0.0.1:1234\n" + "CookieAuthentication 1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "ControlPort is open, but no authentication method has been " + "configured. This means that any program on your computer can " + "reconfigure your Tor. That's bad! You should upgrade your Tor " + "controller as soon as possible.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ControlSocket unix:/tmp WorldWritable\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_EQ, + "ControlSocket is world writable, but no authentication method has" + " been configured. This means that any program on your computer " + "can reconfigure your Tor. That's bad! You should upgrade your " + "Tor controller as soon as possible.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ControlSocket unix:/tmp WorldWritable\n" + "HashedControlPassword 16:872860B76453A77D60CA" + "2BB8C1A7042072093276A3D701AD684053EC4C\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "ControlSocket is world writable, but no authentication method has" + " been configured. This means that any program on your computer " + "can reconfigure your Tor. That's bad! You should upgrade your " + "Tor controller as soon as possible.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ControlSocket unix:/tmp WorldWritable\n" + "__HashedControlSessionPassword 16:872860B7645" + "3A77D60CA2BB8C1A7042072093276A3D701AD684053EC" + "4C\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "ControlSocket is world writable, but no authentication method has" + " been configured. This means that any program on your computer " + "can reconfigure your Tor. That's bad! You should upgrade your " + "Tor controller as soon as possible.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ControlSocket unix:/tmp WorldWritable\n" + "CookieAuthentication 1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "ControlSocket is world writable, but no authentication method has" + " been configured. This means that any program on your computer " + "can reconfigure your Tor. That's bad! You should upgrade your " + "Tor controller as soon as possible.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "CookieAuthFileGroupReadable 1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_EQ, + "CookieAuthFileGroupReadable is set, but will have no effect: you " + "must specify an explicit CookieAuthFile to have it " + "group-readable.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "CookieAuthFileGroupReadable 1\n" + "CookieAuthFile /tmp/somewhere\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "CookieAuthFileGroupReadable is set, but will have no effect: you " + "must specify an explicit CookieAuthFile to have it " + "group-readable.\n"); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__families(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "MyFamily home\n" + "BridgeRelay 1\n" + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "BandwidthRate 51300\n" + "BandwidthBurst 51300\n" + "MaxAdvertisedBandwidth 25700\n" + "DirCache 1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_EQ, + "Listing a family for a bridge relay is not supported: it can " + "reveal bridge fingerprints to censors. You should also make sure " + "you aren't listing this bridge's fingerprint in any other " + "MyFamily.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "MyFamily home\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "Listing a family for a bridge relay is not supported: it can " + "reveal bridge fingerprints to censors. You should also make sure " + "you aren't listing this bridge's fingerprint in any other " + "MyFamily.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "MyFamily !\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Invalid nickname '!' in MyFamily line"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "NodeFamily foo\n" + "NodeFamily !\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_assert(!msg); + tor_free(msg); + + done: + teardown_capture_of_logs(previous_log); + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__addr_policies(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ExitPolicy !!!\n" + "ExitRelay 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Error in ExitPolicy entry."); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__dir_auth(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + VALID_DIR_AUTH + VALID_ALT_DIR_AUTH + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Directory authority/fallback line did not parse. See logs for " + "details."); + tt_str_op(mock_saved_log_at(2), OP_EQ, + "You cannot set both DirAuthority and Alternate*Authority.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingTorNetwork 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "TestingTorNetwork may only be configured in combination with a " + "non-default set of DirAuthority or both of AlternateDirAuthority " + "and AlternateBridgeAuthority configured."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingTorNetwork 1\n" + VALID_ALT_DIR_AUTH + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "TestingTorNetwork may only be configured in combination with a " + "non-default set of DirAuthority or both of AlternateDirAuthority " + "and AlternateBridgeAuthority configured."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingTorNetwork 1\n" + VALID_ALT_BRIDGE_AUTH + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TestingTorNetwork may only be configured in " + "combination with a non-default set of DirAuthority or both of " + "AlternateDirAuthority and AlternateBridgeAuthority configured."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + VALID_ALT_DIR_AUTH + VALID_ALT_BRIDGE_AUTH + "TestingTorNetwork 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__transport(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_NOTICE); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ClientTransportPlugin !!\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Invalid client transport line. See logs for details."); + tt_str_op(mock_saved_log_at(3), OP_EQ, + "Too few arguments on ClientTransportPlugin line.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ClientTransportPlugin foo exec bar\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ServerTransportPlugin !!\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Invalid server transport line. See logs for details."); + tt_str_op(mock_saved_log_at(3), OP_EQ, + "Too few arguments on ServerTransportPlugin line.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ServerTransportPlugin foo exec bar\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(3), OP_EQ, + "Tor is not configured as a relay but you specified a " + "ServerTransportPlugin line (\"foo exec bar\"). The " + "ServerTransportPlugin line will be ignored.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ServerTransportPlugin foo exec bar\n" + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "BandwidthRate 76900\n" + "BandwidthBurst 76900\n" + "MaxAdvertisedBandwidth 38500\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(3), OP_NE, + "Tor is not configured as a relay but you specified a " + "ServerTransportPlugin line (\"foo exec bar\"). The " + "ServerTransportPlugin line will be ignored.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ServerTransportListenAddr foo 127.0.0.42:55\n" + "ServerTransportListenAddr !\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "ServerTransportListenAddr did not parse. See logs for details."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ServerTransportListenAddr foo 127.0.0.42:55\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(3), OP_EQ, + "You need at least a single managed-proxy to specify a transport " + "listen address. The ServerTransportListenAddr line will be " + "ignored.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ServerTransportListenAddr foo 127.0.0.42:55\n" + "ServerTransportPlugin foo exec bar\n" + "ORListenAddress 127.0.0.1:5555\n" + "ORPort 955\n" + "BandwidthRate 76900\n" + "BandwidthBurst 76900\n" + "MaxAdvertisedBandwidth 38500\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(3), OP_NE, + "You need at least a single managed-proxy to specify a transport " + "listen address. The ServerTransportListenAddr line will be " + "ignored.\n"); + + done: + escaped(NULL); // This will free the leaking memory from the previous escaped + memarea_clear_freelist(); + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__constrained_sockets(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ConstrainedSockets 1\n" + "ConstrainedSockSize 0\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "ConstrainedSockSize is invalid. Must be a value " + "between 2048 and 262144 in 1024 byte increments."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ConstrainedSockets 1\n" + "ConstrainedSockSize 263168\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "ConstrainedSockSize is invalid. Must be a value " + "between 2048 and 262144 in 1024 byte increments."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ConstrainedSockets 1\n" + "ConstrainedSockSize 2047\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "ConstrainedSockSize is invalid. Must be a value " + "between 2048 and 262144 in 1024 byte increments."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ConstrainedSockets 1\n" + "ConstrainedSockSize 2048\n" + "DirPort 999\n" + "DirCache 1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_EQ, "You have requested constrained " + "socket buffers while also serving directory entries via DirPort." + " It is strongly suggested that you disable serving directory" + " requests when system TCP buffer resources are scarce.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "ConstrainedSockets 1\n" + "ConstrainedSockSize 2048\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, + "You have requested constrained socket buffers while also serving" + " directory entries via DirPort. It is strongly suggested that " + "you disable serving directory requests when system TCP buffer " + "resources are scarce.\n"); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__v3_auth(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthVoteDelay 1000\n" + "V3AuthDistDelay 1000\n" + "V3AuthVotingInterval 1000\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "V3AuthVoteDelay plus V3AuthDistDelay must be less than half " + "V3AuthVotingInterval"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthVoteDelay 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "V3AuthVoteDelay is way too low."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthVoteDelay 1\n" + "TestingTorNetwork 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "V3AuthVoteDelay is way too low."); + tor_free(msg); + + // TODO: we can't reach the case of v3authvotedelay lower + // than MIN_VOTE_SECONDS but not lower than MIN_VOTE_SECONDS_TESTING, + // since they are the same + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthDistDelay 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "V3AuthDistDelay is way too low."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthDistDelay 1\n" + "TestingTorNetwork 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "V3AuthDistDelay is way too low."); + tor_free(msg); + + // TODO: we can't reach the case of v3authdistdelay lower than + // MIN_DIST_SECONDS but not lower than MIN_DIST_SECONDS_TESTING, + // since they are the same + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthNIntervalsValid 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "V3AuthNIntervalsValid must be at least 2."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthVoteDelay 49\n" + "V3AuthDistDelay 49\n" + "V3AuthVotingInterval 200\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "V3AuthVotingInterval is insanely low."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthVoteDelay 49\n" + "V3AuthDistDelay 49\n" + "V3AuthVotingInterval 200000\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "V3AuthVotingInterval is insanely high."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthVoteDelay 49\n" + "V3AuthDistDelay 49\n" + "V3AuthVotingInterval 1441\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_EQ, "V3AuthVotingInterval does not divide" + " evenly into 24 hours.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthVoteDelay 49\n" + "V3AuthDistDelay 49\n" + "V3AuthVotingInterval 1440\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, "V3AuthVotingInterval does not divide" + " evenly into 24 hours.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "V3AuthVoteDelay 49\n" + "V3AuthDistDelay 49\n" + "V3AuthVotingInterval 299\n" + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(3), OP_EQ, "V3AuthVotingInterval is very low. " + "This may lead to failure to synchronise for a consensus.\n"); + tor_free(msg); + + // TODO: It is impossible to reach the case of testingtor network, with + // v3authvotinginterval too low + /* free_options_test_data(tdata); */ + /* tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES */ + /* "V3AuthVoteDelay 1\n" */ + /* "V3AuthDistDelay 1\n" */ + /* "V3AuthVotingInterval 9\n" */ + /* VALID_DIR_AUTH */ + /* "TestingTorNetwork 1\n" */ + /* ); */ + /* ret = options_validate(tdata->old_opt, tdata->opt, */ + /* tdata->def_opt, 0, &msg); */ + /* tt_int_op(ret, OP_EQ, -1); */ + /* tt_str_op(msg, OP_EQ, "V3AuthVotingInterval is insanely low."); */ + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingV3AuthInitialVoteDelay 1\n" + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TestingV3AuthInitialVoteDelay is way too low."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingV3AuthInitialDistDelay 1\n" + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TestingV3AuthInitialDistDelay is way too low."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + ); + tdata->opt->TestingV3AuthVotingStartOffset = 100000; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TestingV3AuthVotingStartOffset is higher than the " + "voting interval."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + ); + tdata->opt->TestingV3AuthVotingStartOffset = -1; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "TestingV3AuthVotingStartOffset must be non-negative."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + "TestingV3AuthInitialVotingInterval 4\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TestingV3AuthInitialVotingInterval is insanely low."); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__virtual_addr(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "VirtualAddrNetworkIPv4 !!" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Error parsing VirtualAddressNetwork !!"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "VirtualAddrNetworkIPv6 !!" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "Error parsing VirtualAddressNetworkIPv6 !!"); + tor_free(msg); + + done: + escaped(NULL); // This will free the leaking memory from the previous escaped + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__exits(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AllowSingleHopExits 1" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_EQ, "You have set AllowSingleHopExits; " + "now your relay will allow others to make one-hop exits. However," + " since by default most clients avoid relays that set this option," + " most clients will ignore you.\n"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AllowSingleHopExits 1\n" + VALID_DIR_AUTH + ); + mock_clean_saved_logs(); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_str_op(mock_saved_log_at(2), OP_NE, "You have set AllowSingleHopExits; " + "now your relay will allow others to make one-hop exits. However," + " since by default most clients avoid relays that set this option," + " most clients will ignore you.\n"); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__testing_options(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + +#define TEST_TESTING_OPTION(name, low_val, high_val, err_low) \ + STMT_BEGIN \ + free_options_test_data(tdata); \ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES \ + VALID_DIR_AUTH \ + "TestingTorNetwork 1\n" \ + ); \ + tdata->opt-> name = low_val; \ + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);\ + tt_int_op(ret, OP_EQ, -1); \ + tt_str_op(msg, OP_EQ, #name " " err_low); \ + tor_free(msg); \ + \ + free_options_test_data(tdata); \ + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES \ + VALID_DIR_AUTH \ + "TestingTorNetwork 1\n" \ + ); \ + tdata->opt-> name = high_val; \ + mock_clean_saved_logs(); \ + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg);\ + tt_int_op(ret, OP_EQ, 0); \ + tt_str_op(mock_saved_log_at(3), OP_EQ, #name " is insanely high.\n"); \ + tor_free(msg); \ + STMT_END + + TEST_TESTING_OPTION(TestingAuthDirTimeToLearnReachability, -1, 8000, + "must be non-negative."); + TEST_TESTING_OPTION(TestingEstimatedDescriptorPropagationTime, -1, 3601, + "must be non-negative."); + TEST_TESTING_OPTION(TestingClientMaxIntervalWithoutRequest, -1, 3601, + "is way too low."); + TEST_TESTING_OPTION(TestingDirConnectionMaxStall, 1, 3601, + "is way too low."); + // TODO: I think this points to a bug/regression in options_validate + TEST_TESTING_OPTION(TestingConsensusMaxDownloadTries, 1, 801, + "must be greater than 2."); + TEST_TESTING_OPTION(TestingDescriptorMaxDownloadTries, 1, 801, + "must be greater than 1."); + TEST_TESTING_OPTION(TestingMicrodescMaxDownloadTries, 1, 801, + "must be greater than 1."); + TEST_TESTING_OPTION(TestingCertMaxDownloadTries, 1, 801, + "must be greater than 1."); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableConnBwEvent 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TestingEnableConnBwEvent may only be changed in " + "testing Tor networks!"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableConnBwEvent 1\n" + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + "___UsingTestNetworkDefaults 0\n" + ); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_assert(!msg); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableConnBwEvent 1\n" + VALID_DIR_AUTH + "TestingTorNetwork 0\n" + "___UsingTestNetworkDefaults 1\n" + ); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_assert(!msg); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableCellStatsEvent 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TestingEnableCellStatsEvent may only be changed in " + "testing Tor networks!"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableCellStatsEvent 1\n" + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + "___UsingTestNetworkDefaults 0\n" + ); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_assert(!msg); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableCellStatsEvent 1\n" + VALID_DIR_AUTH + "TestingTorNetwork 0\n" + "___UsingTestNetworkDefaults 1\n" + ); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_assert(!msg); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableTbEmptyEvent 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, "TestingEnableTbEmptyEvent may only be changed " + "in testing Tor networks!"); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableTbEmptyEvent 1\n" + VALID_DIR_AUTH + "TestingTorNetwork 1\n" + "___UsingTestNetworkDefaults 0\n" + ); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_assert(!msg); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "TestingEnableTbEmptyEvent 1\n" + VALID_DIR_AUTH + "TestingTorNetwork 0\n" + "___UsingTestNetworkDefaults 1\n" + ); + + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_assert(!msg); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + teardown_capture_of_logs(previous_log); + free_options_test_data(tdata); + tor_free(msg); +} + +static void +test_options_validate__accel(void *ignored) +{ + (void)ignored; + int ret; + char *msg; + options_test_data_t *tdata = NULL; + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccelName foo\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->HardwareAccel, OP_EQ, 1); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccelName foo\n" + ); + tdata->opt->HardwareAccel = 2; + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tdata->opt->HardwareAccel, OP_EQ, 2); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccelDir 1\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, -1); + tt_str_op(msg, OP_EQ, + "Can't use hardware crypto accelerator dir without engine name."); + tor_free(msg); + + free_options_test_data(tdata); + tdata = get_options_test_data(TEST_OPTIONS_DEFAULT_VALUES + "AccelDir 1\n" + "AccelName something\n" + ); + ret = options_validate(tdata->old_opt, tdata->opt, tdata->def_opt, 0, &msg); + tt_int_op(ret, OP_EQ, 0); + tor_free(msg); + + done: + memarea_clear_freelist(); + policies_free_all(); + free_options_test_data(tdata); + tor_free(msg); +} + +#define LOCAL_VALIDATE_TEST(name) \ + { "validate__" #name, test_options_validate__ ## name, TT_FORK, NULL, NULL } + struct testcase_t options_tests[] = { { "validate", test_options_validate, TT_FORK, NULL, NULL }, - END_OF_TESTCASES + { "mem_dircache", test_have_enough_mem_for_dircache, TT_FORK, NULL, NULL }, + LOCAL_VALIDATE_TEST(uname_for_server), + LOCAL_VALIDATE_TEST(outbound_addresses), + LOCAL_VALIDATE_TEST(data_directory), + LOCAL_VALIDATE_TEST(nickname), + LOCAL_VALIDATE_TEST(contactinfo), + LOCAL_VALIDATE_TEST(logs), + LOCAL_VALIDATE_TEST(authdir), + LOCAL_VALIDATE_TEST(relay_with_hidden_services), + LOCAL_VALIDATE_TEST(transproxy), + LOCAL_VALIDATE_TEST(exclude_nodes), + LOCAL_VALIDATE_TEST(scheduler), + LOCAL_VALIDATE_TEST(node_families), + LOCAL_VALIDATE_TEST(tlsec), + LOCAL_VALIDATE_TEST(token_bucket), + LOCAL_VALIDATE_TEST(recommended_packages), + LOCAL_VALIDATE_TEST(fetch_dir), + LOCAL_VALIDATE_TEST(conn_limit), + LOCAL_VALIDATE_TEST(paths_needed), + LOCAL_VALIDATE_TEST(max_client_circuits), + LOCAL_VALIDATE_TEST(ports), + LOCAL_VALIDATE_TEST(reachable_addresses), + LOCAL_VALIDATE_TEST(use_bridges), + LOCAL_VALIDATE_TEST(entry_nodes), + LOCAL_VALIDATE_TEST(invalid_nodes), + LOCAL_VALIDATE_TEST(safe_logging), + LOCAL_VALIDATE_TEST(publish_server_descriptor), + LOCAL_VALIDATE_TEST(testing), + LOCAL_VALIDATE_TEST(hidserv), + LOCAL_VALIDATE_TEST(predicted_ports), + LOCAL_VALIDATE_TEST(path_bias), + LOCAL_VALIDATE_TEST(bandwidth), + LOCAL_VALIDATE_TEST(circuits), + LOCAL_VALIDATE_TEST(port_forwarding), + LOCAL_VALIDATE_TEST(tor2web), + LOCAL_VALIDATE_TEST(rend), + LOCAL_VALIDATE_TEST(accounting), + LOCAL_VALIDATE_TEST(proxy), + LOCAL_VALIDATE_TEST(control), + LOCAL_VALIDATE_TEST(families), + LOCAL_VALIDATE_TEST(addr_policies), + LOCAL_VALIDATE_TEST(dir_auth), + LOCAL_VALIDATE_TEST(transport), + LOCAL_VALIDATE_TEST(constrained_sockets), + LOCAL_VALIDATE_TEST(v3_auth), + LOCAL_VALIDATE_TEST(virtual_addr), + LOCAL_VALIDATE_TEST(exits), + LOCAL_VALIDATE_TEST(testing_options), + LOCAL_VALIDATE_TEST(accel), + END_OF_TESTCASES /* */ }; diff --git a/src/test/test_policy.c b/src/test/test_policy.c index 37c36fed99..794978fc42 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -2,8 +2,11 @@ /* See LICENSE for licensing information */ #include "or.h" +#define CONFIG_PRIVATE +#include "config.h" #include "router.h" #include "routerparse.h" +#define POLICIES_PRIVATE #include "policies.h" #include "test.h" @@ -49,7 +52,7 @@ test_policy_summary_helper(const char *policy_str, r = policies_parse_exit_policy(&line, &policy, EXIT_POLICY_IPV6_ENABLED | - EXIT_POLICY_ADD_DEFAULT, 0, NULL, 0); + EXIT_POLICY_ADD_DEFAULT, NULL); tt_int_op(r,OP_EQ, 0); summary = policy_summarize(policy, AF_INET); @@ -80,7 +83,8 @@ test_policies_general(void *arg) *policy7 = NULL, *policy8 = NULL, *policy9 = NULL, *policy10 = NULL, *policy11 = NULL, *policy12 = NULL; addr_policy_t *p; - tor_addr_t tar; + tor_addr_t tar, tar2; + smartlist_t *addr_list = NULL; config_line_t line; smartlist_t *sm = NULL; char *policy_str = NULL; @@ -115,17 +119,22 @@ test_policies_general(void *arg) tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy2, EXIT_POLICY_IPV6_ENABLED | EXIT_POLICY_REJECT_PRIVATE | - EXIT_POLICY_ADD_DEFAULT, 0, - NULL, 0)); + EXIT_POLICY_ADD_DEFAULT, NULL)); tt_assert(policy2); - tor_addr_parse(&tar, "[2000::1234]"); + tor_addr_from_ipv4h(&tar, 0x0306090cu); + tor_addr_parse(&tar2, "[2000::1234]"); + addr_list = smartlist_new(); + smartlist_add(addr_list, &tar); + smartlist_add(addr_list, &tar2); tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy12, EXIT_POLICY_IPV6_ENABLED | EXIT_POLICY_REJECT_PRIVATE | EXIT_POLICY_ADD_DEFAULT, - 0x0306090cu, &tar, 1)); + addr_list)); + smartlist_free(addr_list); + addr_list = NULL; tt_assert(policy12); @@ -206,15 +215,15 @@ test_policies_general(void *arg) tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy8, EXIT_POLICY_IPV6_ENABLED | EXIT_POLICY_REJECT_PRIVATE | - EXIT_POLICY_ADD_DEFAULT, 0, - NULL, 0)); + EXIT_POLICY_ADD_DEFAULT, + NULL)); tt_assert(policy8); tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy9, EXIT_POLICY_REJECT_PRIVATE | - EXIT_POLICY_ADD_DEFAULT, 0, - NULL, 0)); + EXIT_POLICY_ADD_DEFAULT, + NULL)); tt_assert(policy9); @@ -261,6 +270,93 @@ test_policies_general(void *arg) addr_policy_list_free(policy); policy = NULL; + /* make sure assume_action works */ + malformed_list = 0; + p = router_parse_addr_policy_item_from_string("127.0.0.1", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + p = router_parse_addr_policy_item_from_string("127.0.0.1:*", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + p = router_parse_addr_policy_item_from_string("[::]", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + p = router_parse_addr_policy_item_from_string("[::]:*", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + p = router_parse_addr_policy_item_from_string("[face::b]", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + p = router_parse_addr_policy_item_from_string("[b::aaaa]", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + p = router_parse_addr_policy_item_from_string("*", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + p = router_parse_addr_policy_item_from_string("*4", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + p = router_parse_addr_policy_item_from_string("*6", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(p); + addr_policy_free(p); + tt_assert(!malformed_list); + + /* These are all ambiguous IPv6 addresses, it's good that we reject them */ + p = router_parse_addr_policy_item_from_string("acce::abcd", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(!p); + tt_assert(malformed_list); + malformed_list = 0; + + p = router_parse_addr_policy_item_from_string("7:1234", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(!p); + tt_assert(malformed_list); + malformed_list = 0; + + p = router_parse_addr_policy_item_from_string("::", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(!p); + tt_assert(malformed_list); + malformed_list = 0; + /* make sure compacting logic works. */ policy = NULL; line.key = (char*)"foo"; @@ -268,8 +364,7 @@ test_policies_general(void *arg) line.next = NULL; tt_int_op(0, OP_EQ, policies_parse_exit_policy(&line,&policy, EXIT_POLICY_IPV6_ENABLED | - EXIT_POLICY_ADD_DEFAULT, 0, - NULL, 0)); + EXIT_POLICY_ADD_DEFAULT, NULL)); tt_assert(policy); //test_streq(policy->string, "accept *:80"); @@ -489,6 +584,324 @@ test_policies_general(void *arg) short_policy_free(short_parsed); } +/** Helper: Check that policy_list contains address */ +static int +test_policy_has_address_helper(const smartlist_t *policy_list, + const tor_addr_t *addr) +{ + int found = 0; + + tt_assert(policy_list); + tt_assert(addr); + + SMARTLIST_FOREACH_BEGIN(policy_list, addr_policy_t*, p) { + if (tor_addr_eq(&p->addr, addr)) { + found = 1; + } + } SMARTLIST_FOREACH_END(p); + + return found; + + done: + return 0; +} + +#define TEST_IPV4_ADDR (0x01020304) +#define TEST_IPV6_ADDR ("2002::abcd") + +/** Run unit tests for rejecting the configured addresses on this exit relay + * using policies_parse_exit_policy_reject_private */ +static void +test_policies_reject_exit_address(void *arg) +{ + smartlist_t *policy = NULL; + tor_addr_t ipv4_addr, ipv6_addr; + smartlist_t *ipv4_list, *ipv6_list, *both_list, *dupl_list; + (void)arg; + + tor_addr_from_ipv4h(&ipv4_addr, TEST_IPV4_ADDR); + tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR); + + ipv4_list = smartlist_new(); + ipv6_list = smartlist_new(); + both_list = smartlist_new(); + dupl_list = smartlist_new(); + + smartlist_add(ipv4_list, &ipv4_addr); + smartlist_add(both_list, &ipv4_addr); + smartlist_add(dupl_list, &ipv4_addr); + smartlist_add(dupl_list, &ipv4_addr); + smartlist_add(dupl_list, &ipv4_addr); + + smartlist_add(ipv6_list, &ipv6_addr); + smartlist_add(both_list, &ipv6_addr); + smartlist_add(dupl_list, &ipv6_addr); + smartlist_add(dupl_list, &ipv6_addr); + + /* IPv4-Only Exits */ + + /* test that IPv4 addresses are rejected on an IPv4-only exit */ + policies_parse_exit_policy_reject_private(&policy, 0, ipv4_list, 0, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 1); + tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); + addr_policy_list_free(policy); + policy = NULL; + + /* test that IPv6 addresses are NOT rejected on an IPv4-only exit + * (all IPv6 addresses are rejected by policies_parse_exit_policy_internal + * on IPv4-only exits, so policies_parse_exit_policy_reject_private doesn't + * need to do anything) */ + policies_parse_exit_policy_reject_private(&policy, 0, ipv6_list, 0, 0); + tt_assert(policy == NULL); + + /* test that only IPv4 addresses are rejected on an IPv4-only exit */ + policies_parse_exit_policy_reject_private(&policy, 0, both_list, 0, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 1); + tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); + addr_policy_list_free(policy); + policy = NULL; + + /* Test that lists with duplicate entries produce the same results */ + policies_parse_exit_policy_reject_private(&policy, 0, dupl_list, 0, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 1); + tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); + addr_policy_list_free(policy); + policy = NULL; + + /* IPv4/IPv6 Exits */ + + /* test that IPv4 addresses are rejected on an IPv4/IPv6 exit */ + policies_parse_exit_policy_reject_private(&policy, 1, ipv4_list, 0, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 1); + tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); + addr_policy_list_free(policy); + policy = NULL; + + /* test that IPv6 addresses are rejected on an IPv4/IPv6 exit */ + policies_parse_exit_policy_reject_private(&policy, 1, ipv6_list, 0, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 1); + tt_assert(test_policy_has_address_helper(policy, &ipv6_addr)); + addr_policy_list_free(policy); + policy = NULL; + + /* test that IPv4 and IPv6 addresses are rejected on an IPv4/IPv6 exit */ + policies_parse_exit_policy_reject_private(&policy, 1, both_list, 0, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 2); + tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); + tt_assert(test_policy_has_address_helper(policy, &ipv6_addr)); + addr_policy_list_free(policy); + policy = NULL; + + /* Test that lists with duplicate entries produce the same results */ + policies_parse_exit_policy_reject_private(&policy, 1, dupl_list, 0, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 2); + tt_assert(test_policy_has_address_helper(policy, &ipv4_addr)); + tt_assert(test_policy_has_address_helper(policy, &ipv6_addr)); + addr_policy_list_free(policy); + policy = NULL; + + done: + addr_policy_list_free(policy); + smartlist_free(ipv4_list); + smartlist_free(ipv6_list); + smartlist_free(both_list); + smartlist_free(dupl_list); +} + +static smartlist_t *test_configured_ports = NULL; +const smartlist_t *mock_get_configured_ports(void); + +/** Returns test_configured_ports */ +const smartlist_t * +mock_get_configured_ports(void) +{ + return test_configured_ports; +} + +/** Run unit tests for rejecting publicly routable configured port addresses + * on this exit relay using policies_parse_exit_policy_reject_private */ +static void +test_policies_reject_port_address(void *arg) +{ + smartlist_t *policy = NULL; + port_cfg_t *ipv4_port = NULL; + port_cfg_t *ipv6_port = NULL; + (void)arg; + + test_configured_ports = smartlist_new(); + + ipv4_port = port_cfg_new(0); + tor_addr_from_ipv4h(&ipv4_port->addr, TEST_IPV4_ADDR); + smartlist_add(test_configured_ports, ipv4_port); + + ipv6_port = port_cfg_new(0); + tor_addr_parse(&ipv6_port->addr, TEST_IPV6_ADDR); + smartlist_add(test_configured_ports, ipv6_port); + + MOCK(get_configured_ports, mock_get_configured_ports); + + /* test that an IPv4 port is rejected on an IPv4-only exit, but an IPv6 port + * is NOT rejected (all IPv6 addresses are rejected by + * policies_parse_exit_policy_internal on IPv4-only exits, so + * policies_parse_exit_policy_reject_private doesn't need to do anything + * with IPv6 addresses on IPv4-only exits) */ + policies_parse_exit_policy_reject_private(&policy, 0, NULL, 0, 1); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 1); + tt_assert(test_policy_has_address_helper(policy, &ipv4_port->addr)); + addr_policy_list_free(policy); + policy = NULL; + + /* test that IPv4 and IPv6 ports are rejected on an IPv4/IPv6 exit */ + policies_parse_exit_policy_reject_private(&policy, 1, NULL, 0, 1); + tt_assert(policy); + tt_assert(smartlist_len(policy) == 2); + tt_assert(test_policy_has_address_helper(policy, &ipv4_port->addr)); + tt_assert(test_policy_has_address_helper(policy, &ipv6_port->addr)); + addr_policy_list_free(policy); + policy = NULL; + + done: + addr_policy_list_free(policy); + if (test_configured_ports) { + SMARTLIST_FOREACH(test_configured_ports, + port_cfg_t *, p, port_cfg_free(p)); + smartlist_free(test_configured_ports); + test_configured_ports = NULL; + } + UNMOCK(get_configured_ports); +} + +smartlist_t *mock_ipv4_addrs = NULL; +smartlist_t *mock_ipv6_addrs = NULL; + +/* mock get_interface_address6_list, returning a deep copy of the template + * address list ipv4_interface_address_list or ipv6_interface_address_list */ +static smartlist_t * +mock_get_interface_address6_list(int severity, + sa_family_t family, + int include_internal) +{ + (void)severity; + (void)include_internal; + smartlist_t *clone_list = smartlist_new(); + smartlist_t *template_list = NULL; + + if (family == AF_INET) { + template_list = mock_ipv4_addrs; + } else if (family == AF_INET6) { + template_list = mock_ipv6_addrs; + } else { + return NULL; + } + + tt_assert(template_list); + + SMARTLIST_FOREACH_BEGIN(template_list, tor_addr_t *, src_addr) { + tor_addr_t *dest_addr = malloc(sizeof(tor_addr_t)); + memset(dest_addr, 0, sizeof(*dest_addr)); + tor_addr_copy_tight(dest_addr, src_addr); + smartlist_add(clone_list, dest_addr); + } SMARTLIST_FOREACH_END(src_addr); + + return clone_list; + + done: + free_interface_address6_list(clone_list); + return NULL; +} + +/** Run unit tests for rejecting publicly routable interface addresses on this + * exit relay using policies_parse_exit_policy_reject_private */ +static void +test_policies_reject_interface_address(void *arg) +{ + smartlist_t *policy = NULL; + smartlist_t *public_ipv4_addrs = + get_interface_address6_list(LOG_INFO, AF_INET, 0); + smartlist_t *public_ipv6_addrs = + get_interface_address6_list(LOG_INFO, AF_INET6, 0); + tor_addr_t ipv4_addr, ipv6_addr; + (void)arg; + + /* test that no addresses are rejected when none are supplied/requested */ + policies_parse_exit_policy_reject_private(&policy, 0, NULL, 0, 0); + tt_assert(policy == NULL); + + /* test that only IPv4 interface addresses are rejected on an IPv4-only exit + * (and allow for duplicates) + */ + policies_parse_exit_policy_reject_private(&policy, 0, NULL, 1, 0); + if (policy) { + tt_assert(smartlist_len(policy) <= smartlist_len(public_ipv4_addrs)); + addr_policy_list_free(policy); + policy = NULL; + } + + /* test that IPv4 and IPv6 interface addresses are rejected on an IPv4/IPv6 + * exit (and allow for duplicates) */ + policies_parse_exit_policy_reject_private(&policy, 1, NULL, 1, 0); + if (policy) { + tt_assert(smartlist_len(policy) <= (smartlist_len(public_ipv4_addrs) + + smartlist_len(public_ipv6_addrs))); + addr_policy_list_free(policy); + policy = NULL; + } + + /* Now do it all again, but mocked */ + tor_addr_from_ipv4h(&ipv4_addr, TEST_IPV4_ADDR); + mock_ipv4_addrs = smartlist_new(); + smartlist_add(mock_ipv4_addrs, (void *)&ipv4_addr); + + tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR); + mock_ipv6_addrs = smartlist_new(); + smartlist_add(mock_ipv6_addrs, (void *)&ipv6_addr); + + MOCK(get_interface_address6_list, mock_get_interface_address6_list); + + /* test that no addresses are rejected when none are supplied/requested */ + policies_parse_exit_policy_reject_private(&policy, 0, NULL, 0, 0); + tt_assert(policy == NULL); + + /* test that only IPv4 interface addresses are rejected on an IPv4-only exit + */ + policies_parse_exit_policy_reject_private(&policy, 0, NULL, 1, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == smartlist_len(mock_ipv4_addrs)); + addr_policy_list_free(policy); + policy = NULL; + + /* test that IPv4 and IPv6 interface addresses are rejected on an IPv4/IPv6 + * exit */ + policies_parse_exit_policy_reject_private(&policy, 1, NULL, 1, 0); + tt_assert(policy); + tt_assert(smartlist_len(policy) == (smartlist_len(mock_ipv4_addrs) + + smartlist_len(mock_ipv6_addrs))); + addr_policy_list_free(policy); + policy = NULL; + + done: + addr_policy_list_free(policy); + free_interface_address6_list(public_ipv4_addrs); + free_interface_address6_list(public_ipv6_addrs); + + UNMOCK(get_interface_address6_list); + /* we don't use free_interface_address6_list on these lists because their + * address pointers are stack-based */ + smartlist_free(mock_ipv4_addrs); + smartlist_free(mock_ipv6_addrs); +} + +#undef TEST_IPV4_ADDR +#undef TEST_IPV6_ADDR + static void test_dump_exit_policy_to_string(void *arg) { @@ -578,10 +991,154 @@ test_dump_exit_policy_to_string(void *arg) tor_free(ep); } +static routerinfo_t *mock_desc_routerinfo = NULL; +static const routerinfo_t * +mock_router_get_my_routerinfo(void) +{ + return mock_desc_routerinfo; +} + +#define DEFAULT_POLICY_STRING "reject *:*" +#define TEST_IPV4_ADDR (0x02040608) +#define TEST_IPV6_ADDR ("2003::ef01") + +static or_options_t mock_options; + +static const or_options_t * +mock_get_options(void) +{ + return &mock_options; +} + +/** Run unit tests for generating summary lines of exit policies */ +static void +test_policies_getinfo_helper_policies(void *arg) +{ + (void)arg; + int rv = 0; + size_t ipv4_len = 0, ipv6_len = 0; + char *answer = NULL; + const char *errmsg = NULL; + routerinfo_t mock_my_routerinfo; + + memset(&mock_my_routerinfo, 0, sizeof(mock_my_routerinfo)); + + rv = getinfo_helper_policies(NULL, "exit-policy/default", &answer, &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + tt_assert(strlen(answer) > 0); + tor_free(answer); + + rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/default", + &answer, &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + tt_assert(strlen(answer) > 0); + tor_free(answer); + + memset(&mock_my_routerinfo, 0, sizeof(routerinfo_t)); + MOCK(router_get_my_routerinfo, mock_router_get_my_routerinfo); + mock_my_routerinfo.exit_policy = smartlist_new(); + mock_desc_routerinfo = &mock_my_routerinfo; + + memset(&mock_options, 0, sizeof(or_options_t)); + MOCK(get_options, mock_get_options); + + rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay", + &answer, &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + tt_assert(strlen(answer) == 0); + tor_free(answer); + + rv = getinfo_helper_policies(NULL, "exit-policy/ipv4", &answer, + &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + ipv4_len = strlen(answer); + tt_assert(ipv4_len == 0 || ipv4_len == strlen(DEFAULT_POLICY_STRING)); + tt_assert(ipv4_len == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING)); + tor_free(answer); + + rv = getinfo_helper_policies(NULL, "exit-policy/ipv6", &answer, + &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + ipv6_len = strlen(answer); + tt_assert(ipv6_len == 0 || ipv6_len == strlen(DEFAULT_POLICY_STRING)); + tt_assert(ipv6_len == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING)); + tor_free(answer); + + rv = getinfo_helper_policies(NULL, "exit-policy/full", &answer, + &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + /* It's either empty or it's the default */ + tt_assert(strlen(answer) == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING)); + tor_free(answer); + + mock_my_routerinfo.addr = TEST_IPV4_ADDR; + tor_addr_parse(&mock_my_routerinfo.ipv6_addr, TEST_IPV6_ADDR); + append_exit_policy_string(&mock_my_routerinfo.exit_policy, "accept *4:*"); + append_exit_policy_string(&mock_my_routerinfo.exit_policy, "reject *6:*"); + + mock_options.IPv6Exit = 1; + mock_options.ExitPolicyRejectPrivate = 1; + tor_addr_from_ipv4h(&mock_options.OutboundBindAddressIPv4_, TEST_IPV4_ADDR); + tor_addr_parse(&mock_options.OutboundBindAddressIPv6_, TEST_IPV6_ADDR); + + rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay", + &answer, &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + tt_assert(strlen(answer) > 0); + tor_free(answer); + + rv = getinfo_helper_policies(NULL, "exit-policy/ipv4", &answer, + &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + ipv4_len = strlen(answer); + tt_assert(ipv4_len > 0); + tor_free(answer); + + rv = getinfo_helper_policies(NULL, "exit-policy/ipv6", &answer, + &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + ipv6_len = strlen(answer); + tt_assert(ipv6_len > 0); + tor_free(answer); + + rv = getinfo_helper_policies(NULL, "exit-policy/full", &answer, + &errmsg); + tt_assert(rv == 0); + tt_assert(answer != NULL); + tt_assert(strlen(answer) > 0); + tt_assert(strlen(answer) == ipv4_len + ipv6_len + 1); + tor_free(answer); + + done: + tor_free(answer); + UNMOCK(get_options); + UNMOCK(router_get_my_routerinfo); + addr_policy_list_free(mock_my_routerinfo.exit_policy); +} + +#undef DEFAULT_POLICY_STRING +#undef TEST_IPV4_ADDR +#undef TEST_IPV6_ADDR + struct testcase_t policy_tests[] = { { "router_dump_exit_policy_to_string", test_dump_exit_policy_to_string, 0, NULL, NULL }, { "general", test_policies_general, 0, NULL, NULL }, + { "getinfo_helper_policies", test_policies_getinfo_helper_policies, 0, NULL, + NULL }, + { "reject_exit_address", test_policies_reject_exit_address, 0, NULL, NULL }, + { "reject_interface_address", test_policies_reject_interface_address, 0, + NULL, NULL }, + { "reject_port_address", test_policies_reject_port_address, 0, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_procmon.c b/src/test/test_procmon.c new file mode 100644 index 0000000000..2855178788 --- /dev/null +++ b/src/test/test_procmon.c @@ -0,0 +1,58 @@ +/* Copyright (c) 2010-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define PROCMON_PRIVATE +#include "orconfig.h" +#include "or.h" +#include "test.h" + +#include "procmon.h" + +#include "log_test_helpers.h" + +#define NS_MODULE procmon + +struct event_base; + +static void +test_procmon_tor_process_monitor_new(void *ignored) +{ + (void)ignored; + tor_process_monitor_t *res; + const char *msg; + + res = tor_process_monitor_new(NULL, "probably invalid", 0, NULL, NULL, &msg); + tt_assert(!res); + tt_str_op(msg, OP_EQ, "invalid PID"); + + res = tor_process_monitor_new(NULL, "243443535345454", 0, NULL, NULL, &msg); + tt_assert(!res); + tt_str_op(msg, OP_EQ, "invalid PID"); + + res = tor_process_monitor_new(tor_libevent_get_base(), "43", 0, + NULL, NULL, &msg); + tt_assert(res); + tt_assert(!msg); + tor_process_monitor_free(res); + + res = tor_process_monitor_new(tor_libevent_get_base(), "44 hello", 0, + NULL, NULL, &msg); + tt_assert(res); + tt_assert(!msg); + tor_process_monitor_free(res); + + res = tor_process_monitor_new(tor_libevent_get_base(), "45:hello", 0, + NULL, NULL, &msg); + tt_assert(res); + tt_assert(!msg); + + done: + tor_process_monitor_free(res); +} + +struct testcase_t procmon_tests[] = { + { "tor_process_monitor_new", test_procmon_tor_process_monitor_new, + TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_rendcache.c b/src/test/test_rendcache.c new file mode 100644 index 0000000000..77796994b4 --- /dev/null +++ b/src/test/test_rendcache.c @@ -0,0 +1,1340 @@ +/* Copyright (c) 2010-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "or.h" + +#include "test.h" +#define RENDCACHE_PRIVATE +#include "rendcache.h" +#include "router.h" +#include "routerlist.h" +#include "config.h" +#include <openssl/rsa.h> +#include "rend_test_helpers.h" + +#define NS_MODULE rend_cache + +static const int RECENT_TIME = -10; +static const int TIME_IN_THE_PAST = -(REND_CACHE_MAX_AGE + \ + REND_CACHE_MAX_SKEW + 10); +static const int TIME_IN_THE_FUTURE = REND_CACHE_MAX_SKEW + 10; + +extern strmap_t *rend_cache; +extern digestmap_t *rend_cache_v2_dir; +extern strmap_t *rend_cache_failure; +extern size_t rend_cache_total_allocation; + +static rend_data_t * +mock_rend_data(const char *onion_address) +{ + rend_data_t *rend_query = tor_malloc_zero(sizeof(rend_data_t)); + + strlcpy(rend_query->onion_address, onion_address, + sizeof(rend_query->onion_address)); + rend_query->auth_type = REND_NO_AUTH; + rend_query->hsdirs_fp = smartlist_new(); + smartlist_add(rend_query->hsdirs_fp, tor_memdup("aaaaaaaaaaaaaaaaaaaaaaaa", + DIGEST_LEN)); + + return rend_query; +} + +static void +test_rend_cache_lookup_entry(void *data) +{ + int ret; + rend_data_t *mock_rend_query = NULL; + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + rend_cache_entry_t *entry = NULL; + rend_encoded_v2_service_descriptor_t *desc_holder = NULL; + char *service_id = NULL; + (void)data; + + rend_cache_init(); + + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + + ret = rend_cache_lookup_entry("abababababababab", 0, NULL); + tt_int_op(ret, OP_EQ, -ENOENT); + + ret = rend_cache_lookup_entry("invalid query", 2, NULL); + tt_int_op(ret, OP_EQ, -EINVAL); + + ret = rend_cache_lookup_entry("abababababababab", 2, NULL); + tt_int_op(ret, OP_EQ, -ENOENT); + + ret = rend_cache_lookup_entry("abababababababab", 4224, NULL); + tt_int_op(ret, OP_EQ, -ENOENT); + + mock_rend_query = mock_rend_data(service_id); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + rend_cache_store_v2_desc_as_client(desc_holder->desc_str, desc_id_base32, + mock_rend_query, NULL); + + ret = rend_cache_lookup_entry(service_id, 2, NULL); + tt_int_op(ret, OP_EQ, 0); + + ret = rend_cache_lookup_entry(service_id, 2, &entry); + tt_assert(entry); + tt_int_op(entry->len, OP_EQ, strlen(desc_holder->desc_str)); + tt_str_op(entry->desc, OP_EQ, desc_holder->desc_str); + + done: + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_cache_free_all(); + rend_data_free(mock_rend_query); +} + +static void +test_rend_cache_store_v2_desc_as_client(void *data) +{ + rend_cache_store_status_t ret; + rend_data_t *mock_rend_query; + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + rend_cache_entry_t *entry = NULL; + rend_encoded_v2_service_descriptor_t *desc_holder = NULL; + char *service_id = NULL; + char client_cookie[REND_DESC_COOKIE_LEN]; + (void)data; + + rend_cache_init(); + + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + + // Test success + mock_rend_query = mock_rend_data(service_id); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + &entry); + + tt_int_op(ret, OP_EQ, RCS_OKAY); + tt_assert(entry); + tt_int_op(entry->len, OP_EQ, strlen(desc_holder->desc_str)); + tt_str_op(entry->desc, OP_EQ, desc_holder->desc_str); + + // Test various failure modes + + // TODO: a too long desc_id_base32 argument crashes the function + /* ret = rend_cache_store_v2_desc_as_client( */ + /* desc_holder->desc_str, */ + /* "3TOOLONG3TOOLONG3TOOLONG3TOOLONG3TOOLONG3TOOLONG", */ + /* &mock_rend_query, NULL); */ + /* tt_int_op(ret, OP_EQ, RCS_BADDESC); */ + + // Test bad base32 failure + // This causes an assertion failure if we're running with assertions. + // But when doing coverage, we can test it. +#ifdef TOR_COVERAGE + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + "!xqunszqnaolrrfmtzgaki7mxelgvkj", mock_rend_query, NULL); + tt_int_op(ret, OP_EQ, RCS_BADDESC); +#endif + + // Test invalid descriptor + ret = rend_cache_store_v2_desc_as_client("invalid descriptor", + "3xqunszqnaolrrfmtzgaki7mxelgvkje", mock_rend_query, NULL); + tt_int_op(ret, OP_EQ, RCS_BADDESC); + + // TODO: it doesn't seem to be possible to test invalid service ID condition. + // that means it is likely not possible to have that condition without + // earlier conditions failing first (such as signature checking of the desc) + + rend_cache_free_all(); + + // Test mismatch between service ID and onion address + rend_cache_init(); + strncpy(mock_rend_query->onion_address, "abc", REND_SERVICE_ID_LEN_BASE32+1); + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, + mock_rend_query, NULL); + tt_int_op(ret, OP_EQ, RCS_BADDESC); + rend_cache_free_all(); + rend_data_free(mock_rend_query); + + // Test incorrect descriptor ID + rend_cache_init(); + mock_rend_query = mock_rend_data(service_id); + desc_id_base32[0]++; + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_BADDESC); + desc_id_base32[0]--; + rend_cache_free_all(); + + // Test too old descriptor + rend_cache_init(); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_data_free(mock_rend_query); + + generate_desc(TIME_IN_THE_PAST, &desc_holder, &service_id, 3); + mock_rend_query = mock_rend_data(service_id); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, + mock_rend_query, NULL); + tt_int_op(ret, OP_EQ, RCS_BADDESC); + rend_cache_free_all(); + + // Test too new descriptor (in the future) + rend_cache_init(); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_data_free(mock_rend_query); + + generate_desc(TIME_IN_THE_FUTURE, &desc_holder, &service_id, 3); + mock_rend_query = mock_rend_data(service_id); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_BADDESC); + rend_cache_free_all(); + + // Test when a descriptor is already in the cache + rend_cache_init(); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_data_free(mock_rend_query); + + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + mock_rend_query = mock_rend_data(service_id); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + + rend_cache_store_v2_desc_as_client(desc_holder->desc_str, desc_id_base32, + mock_rend_query, NULL); + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + &entry); + tt_int_op(ret, OP_EQ, RCS_OKAY); + tt_assert(entry); + rend_cache_free_all(); + + // Test unsuccessful decrypting of introduction points + rend_cache_init(); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_data_free(mock_rend_query); + + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + mock_rend_query = mock_rend_data(service_id); + mock_rend_query->auth_type = REND_BASIC_AUTH; + client_cookie[0] = 'A'; + memcpy(mock_rend_query->descriptor_cookie, client_cookie, + REND_DESC_COOKIE_LEN); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_OKAY); + rend_cache_free_all(); + + // Test successful run when we have REND_BASIC_AUTH but not cookie + rend_cache_init(); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_data_free(mock_rend_query); + + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + mock_rend_query = mock_rend_data(service_id); + mock_rend_query->auth_type = REND_BASIC_AUTH; + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + rend_cache_free_all(); + + // Test when we have no introduction points + rend_cache_init(); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_data_free(mock_rend_query); + + generate_desc(RECENT_TIME, &desc_holder, &service_id, 0); + mock_rend_query = mock_rend_data(service_id); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_BADDESC); + rend_cache_free_all(); + + // Test when we have too many intro points + rend_cache_init(); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_data_free(mock_rend_query); + + generate_desc(RECENT_TIME, &desc_holder, &service_id, MAX_INTRO_POINTS+1); + mock_rend_query = mock_rend_data(service_id); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + ret = rend_cache_store_v2_desc_as_client(desc_holder->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_BADDESC); + + done: + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_cache_free_all(); + rend_data_free(mock_rend_query); +} + +static void +test_rend_cache_store_v2_desc_as_client_with_different_time(void *data) +{ + rend_cache_store_status_t ret; + rend_data_t *mock_rend_query; + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + rend_service_descriptor_t *generated = NULL; + smartlist_t *descs = smartlist_new(); + time_t t; + char *service_id = NULL; + rend_encoded_v2_service_descriptor_t *desc_holder_newer; + rend_encoded_v2_service_descriptor_t *desc_holder_older; + + t = time(NULL); + rend_cache_init(); + + create_descriptor(&generated, &service_id, 3); + + generated->timestamp = t + RECENT_TIME; + rend_encode_v2_descriptors(descs, generated, t + RECENT_TIME, 0, + REND_NO_AUTH, NULL, NULL); + desc_holder_newer = ((rend_encoded_v2_service_descriptor_t *) + smartlist_get(descs, 0)); + smartlist_set(descs, 0, NULL); + + SMARTLIST_FOREACH(descs, rend_encoded_v2_service_descriptor_t *, d, + rend_encoded_v2_service_descriptor_free(d)); + smartlist_free(descs); + descs = smartlist_new(); + + generated->timestamp = (t + RECENT_TIME) - 20; + rend_encode_v2_descriptors(descs, generated, t + RECENT_TIME, 0, + REND_NO_AUTH, NULL, NULL); + desc_holder_older = ((rend_encoded_v2_service_descriptor_t *) + smartlist_get(descs, 0)); + smartlist_set(descs, 0, NULL); + (void)data; + + // Test when a descriptor is already in the cache and it is newer than the + // one we submit + mock_rend_query = mock_rend_data(service_id); + base32_encode(desc_id_base32, sizeof(desc_id_base32), + desc_holder_newer->desc_id, DIGEST_LEN); + rend_cache_store_v2_desc_as_client(desc_holder_newer->desc_str, + desc_id_base32, mock_rend_query, NULL); + ret = rend_cache_store_v2_desc_as_client(desc_holder_older->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + rend_cache_free_all(); + + // Test when an old descriptor is in the cache and we submit a newer one + rend_cache_init(); + rend_cache_store_v2_desc_as_client(desc_holder_older->desc_str, + desc_id_base32, mock_rend_query, NULL); + ret = rend_cache_store_v2_desc_as_client(desc_holder_newer->desc_str, + desc_id_base32, mock_rend_query, + NULL); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + done: + rend_encoded_v2_service_descriptor_free(desc_holder_newer); + rend_encoded_v2_service_descriptor_free(desc_holder_older); + SMARTLIST_FOREACH(descs, rend_encoded_v2_service_descriptor_t *, d, + rend_encoded_v2_service_descriptor_free(d)); + smartlist_free(descs); + rend_service_descriptor_free(generated); + tor_free(service_id); + rend_cache_free_all(); + rend_data_free(mock_rend_query); +} + +#define NS_SUBMODULE lookup_v2_desc_as_dir +NS_DECL(const routerinfo_t *, router_get_my_routerinfo, (void)); +NS_DECL(int, hid_serv_responsible_for_desc_id, (const char *id)); + +static routerinfo_t *mock_routerinfo; +static int hid_serv_responsible_for_desc_id_response; + +static const routerinfo_t * +NS(router_get_my_routerinfo)(void) +{ + if (!mock_routerinfo) { + mock_routerinfo = tor_malloc(sizeof(routerinfo_t)); + } + + return mock_routerinfo; +} + +static int +NS(hid_serv_responsible_for_desc_id)(const char *id) +{ + (void)id; + return hid_serv_responsible_for_desc_id_response; +} + +static void +test_rend_cache_lookup_v2_desc_as_dir(void *data) +{ + int ret; + char desc_id_base32[REND_DESC_ID_V2_LEN_BASE32 + 1]; + rend_encoded_v2_service_descriptor_t *desc_holder = NULL; + char *service_id = NULL; + const char *ret_desc = NULL; + + (void)data; + + NS_MOCK(router_get_my_routerinfo); + NS_MOCK(hid_serv_responsible_for_desc_id); + + rend_cache_init(); + + // Test invalid base32 + ret = rend_cache_lookup_v2_desc_as_dir("!bababababababab", NULL); + tt_int_op(ret, OP_EQ, -1); + + // Test non-existent descriptor but well formed + ret = rend_cache_lookup_v2_desc_as_dir("3xqunszqnaolrrfmtzgaki7mxelgvkje", + NULL); + tt_int_op(ret, OP_EQ, 0); + + // Test existing descriptor + hid_serv_responsible_for_desc_id_response = 1; + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + rend_cache_store_v2_desc_as_dir(desc_holder->desc_str); + base32_encode(desc_id_base32, sizeof(desc_id_base32), desc_holder->desc_id, + DIGEST_LEN); + ret = rend_cache_lookup_v2_desc_as_dir(desc_id_base32, &ret_desc); + tt_int_op(ret, OP_EQ, 1); + tt_assert(ret_desc); + + done: + NS_UNMOCK(router_get_my_routerinfo); + NS_UNMOCK(hid_serv_responsible_for_desc_id); + tor_free(mock_routerinfo); + rend_cache_free_all(); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); +} + +#undef NS_SUBMODULE + +#define NS_SUBMODULE store_v2_desc_as_dir +NS_DECL(const routerinfo_t *, router_get_my_routerinfo, (void)); +NS_DECL(int, hid_serv_responsible_for_desc_id, (const char *id)); + +static const routerinfo_t * +NS(router_get_my_routerinfo)(void) +{ + return mock_routerinfo; +} + +static int +NS(hid_serv_responsible_for_desc_id)(const char *id) +{ + (void)id; + return hid_serv_responsible_for_desc_id_response; +} + +static void +test_rend_cache_store_v2_desc_as_dir(void *data) +{ + (void)data; + rend_cache_store_status_t ret; + rend_encoded_v2_service_descriptor_t *desc_holder = NULL; + char *service_id = NULL; + + NS_MOCK(router_get_my_routerinfo); + NS_MOCK(hid_serv_responsible_for_desc_id); + + rend_cache_init(); + + // Test when we are not an HS dir + mock_routerinfo = NULL; + ret = rend_cache_store_v2_desc_as_dir(""); + tt_int_op(ret, OP_EQ, RCS_NOTDIR); + + // Test when we can't parse the descriptor + mock_routerinfo = tor_malloc(sizeof(routerinfo_t)); + hid_serv_responsible_for_desc_id_response = 1; + ret = rend_cache_store_v2_desc_as_dir("unparseable"); + tt_int_op(ret, OP_EQ, RCS_BADDESC); + + // Test when we are not responsible for an HS + hid_serv_responsible_for_desc_id_response = 0; + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + ret = rend_cache_store_v2_desc_as_dir(desc_holder->desc_str); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + + // Test when we have an old descriptor + hid_serv_responsible_for_desc_id_response = 1; + generate_desc(TIME_IN_THE_PAST, &desc_holder, &service_id, 3); + ret = rend_cache_store_v2_desc_as_dir(desc_holder->desc_str); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + + // Test when we have a descriptor in the future + generate_desc(TIME_IN_THE_FUTURE, &desc_holder, &service_id, 3); + ret = rend_cache_store_v2_desc_as_dir(desc_holder->desc_str); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + + // Test when two descriptors + generate_desc(TIME_IN_THE_FUTURE, &desc_holder, &service_id, 3); + ret = rend_cache_store_v2_desc_as_dir(desc_holder->desc_str); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + + // Test when asking for hidden service statistics HiddenServiceStatistics + rend_cache_purge(); + generate_desc(RECENT_TIME, &desc_holder, &service_id, 3); + get_options_mutable()->HiddenServiceStatistics = 1; + ret = rend_cache_store_v2_desc_as_dir(desc_holder->desc_str); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + done: + NS_UNMOCK(router_get_my_routerinfo); + NS_UNMOCK(hid_serv_responsible_for_desc_id); + rend_encoded_v2_service_descriptor_free(desc_holder); + tor_free(service_id); + rend_cache_free_all(); + tor_free(mock_routerinfo); +} + +static void +test_rend_cache_store_v2_desc_as_dir_with_different_time(void *data) +{ + (void)data; + + rend_cache_store_status_t ret; + rend_service_descriptor_t *generated = NULL; + smartlist_t *descs = smartlist_new(); + time_t t; + char *service_id = NULL; + rend_encoded_v2_service_descriptor_t *desc_holder_newer; + rend_encoded_v2_service_descriptor_t *desc_holder_older; + + NS_MOCK(router_get_my_routerinfo); + NS_MOCK(hid_serv_responsible_for_desc_id); + + rend_cache_init(); + + t = time(NULL); + + create_descriptor(&generated, &service_id, 3); + generated->timestamp = t + RECENT_TIME; + rend_encode_v2_descriptors(descs, generated, t + RECENT_TIME, 0, + REND_NO_AUTH, NULL, NULL); + desc_holder_newer = ((rend_encoded_v2_service_descriptor_t *) + smartlist_get(descs, 0)); + smartlist_set(descs, 0, NULL); + SMARTLIST_FOREACH(descs, rend_encoded_v2_service_descriptor_t *, d, + rend_encoded_v2_service_descriptor_free(d)); + smartlist_free(descs); + descs = smartlist_new(); + + generated->timestamp = (t + RECENT_TIME) - 20; + rend_encode_v2_descriptors(descs, generated, t + RECENT_TIME, 0, + REND_NO_AUTH, NULL, NULL); + desc_holder_older = ((rend_encoded_v2_service_descriptor_t *) + smartlist_get(descs, 0)); + smartlist_set(descs, 0, NULL); + + // Test when we have a newer descriptor stored + mock_routerinfo = tor_malloc(sizeof(routerinfo_t)); + hid_serv_responsible_for_desc_id_response = 1; + rend_cache_store_v2_desc_as_dir(desc_holder_newer->desc_str); + ret = rend_cache_store_v2_desc_as_dir(desc_holder_older->desc_str); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + // Test when we have an old descriptor stored + rend_cache_purge(); + rend_cache_store_v2_desc_as_dir(desc_holder_older->desc_str); + ret = rend_cache_store_v2_desc_as_dir(desc_holder_newer->desc_str); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + done: + NS_UNMOCK(router_get_my_routerinfo); + NS_UNMOCK(hid_serv_responsible_for_desc_id); + rend_cache_free_all(); + rend_service_descriptor_free(generated); + tor_free(service_id); + SMARTLIST_FOREACH(descs, rend_encoded_v2_service_descriptor_t *, d, + rend_encoded_v2_service_descriptor_free(d)); + smartlist_free(descs); + rend_encoded_v2_service_descriptor_free(desc_holder_newer); + rend_encoded_v2_service_descriptor_free(desc_holder_older); + tor_free(mock_routerinfo); +} + +static void +test_rend_cache_store_v2_desc_as_dir_with_different_content(void *data) +{ + (void)data; + + rend_cache_store_status_t ret; + rend_service_descriptor_t *generated = NULL; + smartlist_t *descs = smartlist_new(); + time_t t; + char *service_id = NULL; + rend_encoded_v2_service_descriptor_t *desc_holder_one = NULL; + rend_encoded_v2_service_descriptor_t *desc_holder_two = NULL; + + NS_MOCK(router_get_my_routerinfo); + NS_MOCK(hid_serv_responsible_for_desc_id); + + rend_cache_init(); + + t = time(NULL); + + create_descriptor(&generated, &service_id, 3); + generated->timestamp = t + RECENT_TIME; + rend_encode_v2_descriptors(descs, generated, t + RECENT_TIME, 0, + REND_NO_AUTH, NULL, NULL); + desc_holder_one = ((rend_encoded_v2_service_descriptor_t *) + smartlist_get(descs, 0)); + smartlist_set(descs, 0, NULL); + + SMARTLIST_FOREACH(descs, rend_encoded_v2_service_descriptor_t *, d, + rend_encoded_v2_service_descriptor_free(d)); + smartlist_free(descs); + descs = smartlist_new(); + + generated->timestamp = t + RECENT_TIME; + generated->protocols = 41; + rend_encode_v2_descriptors(descs, generated, t + RECENT_TIME, 0, + REND_NO_AUTH, NULL, NULL); + desc_holder_two = ((rend_encoded_v2_service_descriptor_t *) + smartlist_get(descs, 0)); + smartlist_set(descs, 0, NULL); + + // Test when we have another descriptor stored, with a different descriptor + mock_routerinfo = tor_malloc(sizeof(routerinfo_t)); + hid_serv_responsible_for_desc_id_response = 1; + rend_cache_store_v2_desc_as_dir(desc_holder_one->desc_str); + ret = rend_cache_store_v2_desc_as_dir(desc_holder_two->desc_str); + tt_int_op(ret, OP_EQ, RCS_OKAY); + + done: + NS_UNMOCK(router_get_my_routerinfo); + NS_UNMOCK(hid_serv_responsible_for_desc_id); + rend_cache_free_all(); + rend_service_descriptor_free(generated); + tor_free(service_id); + SMARTLIST_FOREACH(descs, rend_encoded_v2_service_descriptor_t *, d, + rend_encoded_v2_service_descriptor_free(d)); + smartlist_free(descs); + rend_encoded_v2_service_descriptor_free(desc_holder_one); + rend_encoded_v2_service_descriptor_free(desc_holder_two); +} + +#undef NS_SUBMODULE + +static void +test_rend_cache_init(void *data) +{ + (void)data; + + tt_assert_msg(!rend_cache, "rend_cache should be NULL when starting"); + tt_assert_msg(!rend_cache_v2_dir, "rend_cache_v2_dir should be NULL " + "when starting"); + tt_assert_msg(!rend_cache_failure, "rend_cache_failure should be NULL when " + "starting"); + + rend_cache_init(); + + tt_assert_msg(rend_cache, "rend_cache should not be NULL after initing"); + tt_assert_msg(rend_cache_v2_dir, "rend_cache_v2_dir should not be NULL " + "after initing"); + tt_assert_msg(rend_cache_failure, "rend_cache_failure should not be NULL " + "after initing"); + + tt_int_op(strmap_size(rend_cache), OP_EQ, 0); + tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 0); + tt_int_op(strmap_size(rend_cache_failure), OP_EQ, 0); + + done: + rend_cache_free_all(); +} + +static void +test_rend_cache_decrement_allocation(void *data) +{ + (void)data; + + // Test when the cache has enough allocations + rend_cache_total_allocation = 10; + rend_cache_decrement_allocation(3); + tt_int_op(rend_cache_total_allocation, OP_EQ, 7); + + // Test when there are not enough allocations + rend_cache_total_allocation = 1; + rend_cache_decrement_allocation(2); + tt_int_op(rend_cache_total_allocation, OP_EQ, 0); + + // And again + rend_cache_decrement_allocation(2); + tt_int_op(rend_cache_total_allocation, OP_EQ, 0); + + done: + (void)0; +} + +static void +test_rend_cache_increment_allocation(void *data) +{ + (void)data; + + // Test when the cache is not overflowing + rend_cache_total_allocation = 5; + rend_cache_increment_allocation(3); + tt_int_op(rend_cache_total_allocation, OP_EQ, 8); + + // Test when there are too many allocations + rend_cache_total_allocation = SIZE_MAX-1; + rend_cache_increment_allocation(2); + tt_u64_op(rend_cache_total_allocation, OP_EQ, SIZE_MAX); + + // And again + rend_cache_increment_allocation(2); + tt_u64_op(rend_cache_total_allocation, OP_EQ, SIZE_MAX); + + done: + (void)0; +} + +static void +test_rend_cache_failure_intro_entry_new(void *data) +{ + time_t now; + rend_cache_failure_intro_t *entry; + rend_intro_point_failure_t failure; + + (void)data; + + failure = INTRO_POINT_FAILURE_TIMEOUT; + now = time(NULL); + entry = rend_cache_failure_intro_entry_new(failure); + + tt_int_op(entry->failure_type, OP_EQ, INTRO_POINT_FAILURE_TIMEOUT); + tt_int_op(entry->created_ts, OP_GE, now-5); + tt_int_op(entry->created_ts, OP_LE, now+5); + + done: + tor_free(entry); +} + +static void +test_rend_cache_failure_intro_lookup(void *data) +{ + (void)data; + int ret; + rend_cache_failure_t *failure; + rend_cache_failure_intro_t *ip; + rend_cache_failure_intro_t *entry; + const char key_ip_one[DIGEST_LEN] = "ip1"; + const char key_ip_two[DIGEST_LEN] = "ip2"; + const char key_foo[DIGEST_LEN] = "foo1"; + + rend_cache_init(); + + failure = rend_cache_failure_entry_new(); + ip = rend_cache_failure_intro_entry_new(INTRO_POINT_FAILURE_TIMEOUT); + digestmap_set(failure->intro_failures, key_ip_one, ip); + strmap_set_lc(rend_cache_failure, "foo1", failure); + + // Test not found + ret = cache_failure_intro_lookup((const uint8_t *) key_foo, "foo2", NULL); + tt_int_op(ret, OP_EQ, 0); + + // Test found with no intro failures in it + ret = cache_failure_intro_lookup((const uint8_t *) key_ip_two, "foo1", NULL); + tt_int_op(ret, OP_EQ, 0); + + // Test found + ret = cache_failure_intro_lookup((const uint8_t *) key_ip_one, "foo1", NULL); + tt_int_op(ret, OP_EQ, 1); + + // Test found and asking for entry + cache_failure_intro_lookup((const uint8_t *) key_ip_one, "foo1", &entry); + tt_assert(entry); + tt_assert(entry == ip); + + done: + rend_cache_free_all(); +} + +static void +test_rend_cache_clean(void *data) +{ + rend_cache_entry_t *one, *two; + rend_service_descriptor_t *desc_one, *desc_two; + strmap_iter_t *iter = NULL; + const char *key; + void *val; + + (void)data; + + rend_cache_init(); + + // Test with empty rendcache + rend_cache_clean(time(NULL), REND_CACHE_TYPE_CLIENT); + tt_int_op(strmap_size(rend_cache), OP_EQ, 0); + + // Test with two old entries + one = tor_malloc_zero(sizeof(rend_cache_entry_t)); + two = tor_malloc_zero(sizeof(rend_cache_entry_t)); + desc_one = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + desc_two = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + one->parsed = desc_one; + two->parsed = desc_two; + + desc_one->timestamp = time(NULL) + TIME_IN_THE_PAST; + desc_two->timestamp = (time(NULL) + TIME_IN_THE_PAST) - 10; + desc_one->pk = pk_generate(0); + desc_two->pk = pk_generate(1); + + strmap_set_lc(rend_cache, "foo1", one); + strmap_set_lc(rend_cache, "foo2", two); + + rend_cache_clean(time(NULL), REND_CACHE_TYPE_CLIENT); + tt_int_op(strmap_size(rend_cache), OP_EQ, 0); + + // Test with one old entry and one newer entry + one = tor_malloc_zero(sizeof(rend_cache_entry_t)); + two = tor_malloc_zero(sizeof(rend_cache_entry_t)); + desc_one = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + desc_two = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + one->parsed = desc_one; + two->parsed = desc_two; + + desc_one->timestamp = (time(NULL) + TIME_IN_THE_PAST) - 10; + desc_two->timestamp = time(NULL) - 100; + desc_one->pk = pk_generate(0); + desc_two->pk = pk_generate(1); + + strmap_set_lc(rend_cache, "foo1", one); + strmap_set_lc(rend_cache, "foo2", two); + + rend_cache_clean(time(NULL), REND_CACHE_TYPE_CLIENT); + tt_int_op(strmap_size(rend_cache), OP_EQ, 1); + + iter = strmap_iter_init(rend_cache); + strmap_iter_get(iter, &key, &val); + tt_str_op(key, OP_EQ, "foo2"); + + done: + rend_cache_free_all(); +} + +static void +test_rend_cache_failure_entry_new(void *data) +{ + rend_cache_failure_t *failure; + + (void)data; + + failure = rend_cache_failure_entry_new(); + tt_assert(failure); + tt_int_op(digestmap_size(failure->intro_failures), OP_EQ, 0); + + done: + rend_cache_failure_entry_free(failure); +} + +static void +test_rend_cache_failure_entry_free(void *data) +{ + (void)data; + + // Test that it can deal with a NULL argument + rend_cache_failure_entry_free(NULL); + + /* done: */ + /* (void)0; */ +} + +static void +test_rend_cache_failure_clean(void *data) +{ + rend_cache_failure_t *failure; + rend_cache_failure_intro_t *ip_one, *ip_two; + + const char key_one[DIGEST_LEN] = "ip1"; + const char key_two[DIGEST_LEN] = "ip2"; + + (void)data; + + rend_cache_init(); + + // Test with empty failure cache + rend_cache_failure_clean(time(NULL)); + tt_int_op(strmap_size(rend_cache_failure), OP_EQ, 0); + + // Test with one empty failure entry + failure = rend_cache_failure_entry_new(); + strmap_set_lc(rend_cache_failure, "foo1", failure); + rend_cache_failure_clean(time(NULL)); + tt_int_op(strmap_size(rend_cache_failure), OP_EQ, 0); + + // Test with one new intro point + failure = rend_cache_failure_entry_new(); + ip_one = rend_cache_failure_intro_entry_new(INTRO_POINT_FAILURE_TIMEOUT); + digestmap_set(failure->intro_failures, key_one, ip_one); + strmap_set_lc(rend_cache_failure, "foo1", failure); + rend_cache_failure_clean(time(NULL)); + tt_int_op(strmap_size(rend_cache_failure), OP_EQ, 1); + + // Test with one old intro point + rend_cache_failure_purge(); + failure = rend_cache_failure_entry_new(); + ip_one = rend_cache_failure_intro_entry_new(INTRO_POINT_FAILURE_TIMEOUT); + ip_one->created_ts = time(NULL) - 7*60; + digestmap_set(failure->intro_failures, key_one, ip_one); + strmap_set_lc(rend_cache_failure, "foo1", failure); + rend_cache_failure_clean(time(NULL)); + tt_int_op(strmap_size(rend_cache_failure), OP_EQ, 0); + + // Test with one old intro point and one new one + rend_cache_failure_purge(); + failure = rend_cache_failure_entry_new(); + ip_one = rend_cache_failure_intro_entry_new(INTRO_POINT_FAILURE_TIMEOUT); + ip_one->created_ts = time(NULL) - 7*60; + digestmap_set(failure->intro_failures, key_one, ip_one); + ip_two = rend_cache_failure_intro_entry_new(INTRO_POINT_FAILURE_TIMEOUT); + ip_two->created_ts = time(NULL) - 2*60; + digestmap_set(failure->intro_failures, key_two, ip_two); + strmap_set_lc(rend_cache_failure, "foo1", failure); + rend_cache_failure_clean(time(NULL)); + tt_int_op(strmap_size(rend_cache_failure), OP_EQ, 1); + tt_int_op(digestmap_size(failure->intro_failures), OP_EQ, 1); + + done: + rend_cache_free_all(); +} + +static void +test_rend_cache_failure_remove(void *data) +{ + rend_service_descriptor_t *desc; + (void)data; + + rend_cache_init(); + + // Test that it deals well with a NULL desc + rend_cache_failure_remove(NULL); + + // Test a descriptor that isn't in the cache + desc = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + desc->pk = pk_generate(0); + rend_cache_failure_remove(desc); + + // There seems to not exist any way of getting rend_cache_failure_remove() + // to fail because of a problem with rend_get_service_id from here + rend_cache_free_all(); + + rend_service_descriptor_free(desc); + /* done: */ + /* (void)0; */ +} + +static void +test_rend_cache_free_all(void *data) +{ + rend_cache_failure_t *failure; + rend_cache_entry_t *one; + rend_service_descriptor_t *desc_one; + + (void)data; + + rend_cache_init(); + + failure = rend_cache_failure_entry_new(); + strmap_set_lc(rend_cache_failure, "foo1", failure); + + one = tor_malloc_zero(sizeof(rend_cache_entry_t)); + desc_one = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + one->parsed = desc_one; + desc_one->timestamp = time(NULL) + TIME_IN_THE_PAST; + desc_one->pk = pk_generate(0); + strmap_set_lc(rend_cache, "foo1", one); + + rend_cache_free_all(); + + tt_assert(!rend_cache); + tt_assert(!rend_cache_v2_dir); + tt_assert(!rend_cache_failure); + tt_assert(!rend_cache_total_allocation); + + done: + rend_cache_free_all(); +} + +static void +test_rend_cache_entry_free(void *data) +{ + (void)data; + rend_cache_entry_t *e; + + // Handles NULL correctly + rend_cache_entry_free(NULL); + + // Handles NULL descriptor correctly + e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + rend_cache_entry_free(e); + + // Handles non-NULL descriptor correctly + e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + e->desc = (char *)malloc(10); + rend_cache_entry_free(e); + + /* done: */ + /* (void)0; */ +} + +static void +test_rend_cache_purge(void *data) +{ + (void)data; + + // Deals with a NULL rend_cache + rend_cache_purge(); + tt_assert(rend_cache); + tt_assert(strmap_size(rend_cache) == 0); + + // Deals with existing rend_cache + rend_cache_free_all(); + rend_cache_init(); + tt_assert(rend_cache); + tt_assert(strmap_size(rend_cache) == 0); + + rend_cache_purge(); + tt_assert(rend_cache); + tt_assert(strmap_size(rend_cache) == 0); + + done: + rend_cache_free_all(); +} + +static void +test_rend_cache_failure_intro_add(void *data) +{ + (void)data; + rend_cache_failure_t *fail_entry; + rend_cache_failure_intro_t *entry; + const char identity[DIGEST_LEN] = "foo1"; + + rend_cache_init(); + + // Adds non-existing entry + cache_failure_intro_add((const uint8_t *) identity, "foo2", + INTRO_POINT_FAILURE_TIMEOUT); + fail_entry = strmap_get_lc(rend_cache_failure, "foo2"); + tt_assert(fail_entry); + tt_int_op(digestmap_size(fail_entry->intro_failures), OP_EQ, 1); + entry = digestmap_get(fail_entry->intro_failures, identity); + tt_assert(entry); + + // Adds existing entry + cache_failure_intro_add((const uint8_t *) identity, "foo2", + INTRO_POINT_FAILURE_TIMEOUT); + fail_entry = strmap_get_lc(rend_cache_failure, "foo2"); + tt_assert(fail_entry); + tt_int_op(digestmap_size(fail_entry->intro_failures), OP_EQ, 1); + entry = digestmap_get(fail_entry->intro_failures, identity); + tt_assert(entry); + + done: + rend_cache_free_all(); +} + +static void +test_rend_cache_intro_failure_note(void *data) +{ + (void)data; + rend_cache_failure_t *fail_entry; + rend_cache_failure_intro_t *entry; + const char key[DIGEST_LEN] = "foo1"; + + rend_cache_init(); + + // Test not found + rend_cache_intro_failure_note(INTRO_POINT_FAILURE_TIMEOUT, + (const uint8_t *) key, "foo2"); + fail_entry = strmap_get_lc(rend_cache_failure, "foo2"); + tt_assert(fail_entry); + tt_int_op(digestmap_size(fail_entry->intro_failures), OP_EQ, 1); + entry = digestmap_get(fail_entry->intro_failures, key); + tt_assert(entry); + tt_int_op(entry->failure_type, OP_EQ, INTRO_POINT_FAILURE_TIMEOUT); + + // Test found + rend_cache_intro_failure_note(INTRO_POINT_FAILURE_UNREACHABLE, + (const uint8_t *) key, "foo2"); + tt_int_op(entry->failure_type, OP_EQ, INTRO_POINT_FAILURE_UNREACHABLE); + + done: + rend_cache_free_all(); +} + +#define NS_SUBMODULE clean_v2_descs_as_dir +NS_DECL(int, hid_serv_responsible_for_desc_id, (const char *id)); + +static int +NS(hid_serv_responsible_for_desc_id)(const char *id) +{ + (void)id; + return hid_serv_responsible_for_desc_id_response; +} + +static void +test_rend_cache_clean_v2_descs_as_dir(void *data) +{ + rend_cache_entry_t *e; + time_t now; + rend_service_descriptor_t *desc; + now = time(NULL); + const char key[DIGEST_LEN] = "abcde"; + + (void)data; + + NS_MOCK(hid_serv_responsible_for_desc_id); + rend_cache_init(); + + // Test running with an empty cache + rend_cache_clean_v2_descs_as_dir(now, 0); + tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 0); + + // Test with only one new entry + e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + e->last_served = now; + desc = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + desc->timestamp = now; + desc->pk = pk_generate(0); + e->parsed = desc; + digestmap_set(rend_cache_v2_dir, key, e); + + hid_serv_responsible_for_desc_id_response = 1; + rend_cache_clean_v2_descs_as_dir(now, 0); + tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 1); + + // Test with one old entry + desc->timestamp = now - (REND_CACHE_MAX_AGE + REND_CACHE_MAX_SKEW + 1000); + rend_cache_clean_v2_descs_as_dir(now, 0); + tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 0); + + // Test with one entry that is not under the responsibility of this + // hidden service + e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + e->last_served = now; + desc = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + desc->timestamp = now; + desc->pk = pk_generate(0); + e->parsed = desc; + digestmap_set(rend_cache_v2_dir, key, e); + + hid_serv_responsible_for_desc_id_response = 0; + rend_cache_clean_v2_descs_as_dir(now, 0); + tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 0); + + // Test with one entry that has an old last served + e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + e->last_served = now - (REND_CACHE_MAX_AGE + REND_CACHE_MAX_SKEW + 1000); + desc = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + desc->timestamp = now; + desc->pk = pk_generate(0); + e->parsed = desc; + digestmap_set(rend_cache_v2_dir, key, e); + + hid_serv_responsible_for_desc_id_response = 1; + rend_cache_clean_v2_descs_as_dir(now, 0); + tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 0); + + // Test a run through asking for a large force_remove + e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + e->last_served = now; + desc = tor_malloc_zero(sizeof(rend_service_descriptor_t)); + desc->timestamp = now; + desc->pk = pk_generate(0); + e->parsed = desc; + digestmap_set(rend_cache_v2_dir, key, e); + + hid_serv_responsible_for_desc_id_response = 1; + rend_cache_clean_v2_descs_as_dir(now, 20000); + tt_int_op(digestmap_size(rend_cache_v2_dir), OP_EQ, 1); + + done: + NS_UNMOCK(hid_serv_responsible_for_desc_id); + rend_cache_free_all(); +} + +#undef NS_SUBMODULE + +static void +test_rend_cache_entry_allocation(void *data) +{ + (void)data; + + size_t ret; + rend_cache_entry_t *e = NULL; + + // Handles a null argument + ret = rend_cache_entry_allocation(NULL); + tt_int_op(ret, OP_EQ, 0); + + // Handles a non-null argument + e = tor_malloc_zero(sizeof(rend_cache_entry_t)); + ret = rend_cache_entry_allocation(e); + tt_int_op(ret, OP_GT, sizeof(rend_cache_entry_t)); + + done: + tor_free(e); +} + +static void +test_rend_cache_failure_intro_entry_free(void *data) +{ + (void)data; + rend_cache_failure_intro_t *entry; + + // Handles a null argument + rend_cache_failure_intro_entry_free(NULL); + + // Handles a non-null argument + entry = rend_cache_failure_intro_entry_new(INTRO_POINT_FAILURE_TIMEOUT); + rend_cache_failure_intro_entry_free(entry); +} + +static void +test_rend_cache_failure_purge(void *data) +{ + (void)data; + + // Handles a null failure cache + strmap_free(rend_cache_failure, rend_cache_failure_entry_free_); + rend_cache_failure = NULL; + + rend_cache_failure_purge(); + + tt_ptr_op(rend_cache_failure, OP_NE, NULL); + tt_int_op(strmap_size(rend_cache_failure), OP_EQ, 0); + + done: + rend_cache_free_all(); +} + +static void +test_rend_cache_validate_intro_point_failure(void *data) +{ + (void)data; + rend_service_descriptor_t *desc = NULL; + char *service_id = NULL; + rend_intro_point_t *intro = NULL; + const char *identity = NULL; + rend_cache_failure_t *failure; + rend_cache_failure_intro_t *ip; + + rend_cache_init(); + + create_descriptor(&desc, &service_id, 3); + desc->timestamp = time(NULL) + RECENT_TIME; + + intro = (rend_intro_point_t *)smartlist_get(desc->intro_nodes, 0); + identity = intro->extend_info->identity_digest; + + failure = rend_cache_failure_entry_new(); + ip = rend_cache_failure_intro_entry_new(INTRO_POINT_FAILURE_TIMEOUT); + digestmap_set(failure->intro_failures, identity, ip); + strmap_set_lc(rend_cache_failure, service_id, failure); + + // Test when we have an intro point in our cache + validate_intro_point_failure(desc, service_id); + tt_int_op(smartlist_len(desc->intro_nodes), OP_EQ, 2); + + done: + rend_cache_free_all(); + rend_service_descriptor_free(desc); + tor_free(service_id); +} + +struct testcase_t rend_cache_tests[] = { + { "init", test_rend_cache_init, 0, NULL, NULL }, + { "decrement_allocation", test_rend_cache_decrement_allocation, 0, + NULL, NULL }, + { "increment_allocation", test_rend_cache_increment_allocation, 0, + NULL, NULL }, + { "clean", test_rend_cache_clean, TT_FORK, NULL, NULL }, + { "clean_v2_descs_as_dir", test_rend_cache_clean_v2_descs_as_dir, 0, + NULL, NULL }, + { "entry_allocation", test_rend_cache_entry_allocation, 0, NULL, NULL }, + { "entry_free", test_rend_cache_entry_free, 0, NULL, NULL }, + { "failure_intro_entry_free", test_rend_cache_failure_intro_entry_free, 0, + NULL, NULL }, + { "free_all", test_rend_cache_free_all, 0, NULL, NULL }, + { "purge", test_rend_cache_purge, 0, NULL, NULL }, + { "failure_clean", test_rend_cache_failure_clean, 0, NULL, NULL }, + { "failure_entry_new", test_rend_cache_failure_entry_new, 0, NULL, NULL }, + { "failure_entry_free", test_rend_cache_failure_entry_free, 0, NULL, NULL }, + { "failure_intro_add", test_rend_cache_failure_intro_add, 0, NULL, NULL }, + { "failure_intro_entry_new", test_rend_cache_failure_intro_entry_new, 0, + NULL, NULL }, + { "failure_intro_lookup", test_rend_cache_failure_intro_lookup, 0, + NULL, NULL }, + { "failure_purge", test_rend_cache_failure_purge, 0, NULL, NULL }, + { "failure_remove", test_rend_cache_failure_remove, 0, NULL, NULL }, + { "intro_failure_note", test_rend_cache_intro_failure_note, 0, NULL, NULL }, + { "lookup", test_rend_cache_lookup_entry, 0, NULL, NULL }, + { "lookup_v2_desc_as_dir", test_rend_cache_lookup_v2_desc_as_dir, 0, + NULL, NULL }, + { "store_v2_desc_as_client", test_rend_cache_store_v2_desc_as_client, 0, + NULL, NULL }, + { "store_v2_desc_as_client_with_different_time", + test_rend_cache_store_v2_desc_as_client_with_different_time, 0, + NULL, NULL }, + { "store_v2_desc_as_dir", test_rend_cache_store_v2_desc_as_dir, 0, + NULL, NULL }, + { "store_v2_desc_as_dir_with_different_time", + test_rend_cache_store_v2_desc_as_dir_with_different_time, 0, NULL, NULL }, + { "store_v2_desc_as_dir_with_different_content", + test_rend_cache_store_v2_desc_as_dir_with_different_content, 0, + NULL, NULL }, + { "validate_intro_point_failure", + test_rend_cache_validate_intro_point_failure, 0, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_replay.c b/src/test/test_replay.c index a02c160365..7a0f098776 100644 --- a/src/test/test_replay.c +++ b/src/test/test_replay.c @@ -17,6 +17,20 @@ static const char *test_buffer = " occaecat cupidatat non proident, sunt in culpa qui officia deserunt" " mollit anim id est laborum."; +static const char *test_buffer_2 = + "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis" + " praesentium voluptatum deleniti atque corrupti quos dolores et quas" + " molestias excepturi sint occaecati cupiditate non provident, similique" + " sunt in culpa qui officia deserunt mollitia animi, id est laborum et" + " dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio." + " Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil" + " impedit quo minus id quod maxime placeat facere possimus, omnis voluptas" + " assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut" + " officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates" + " repudiandae sint et molestiae non recusandae. Itaque earum rerum hic" + " tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias" + " consequatur aut perferendis doloribus asperiores repellat."; + static void test_replaycache_alloc(void *arg) { @@ -83,6 +97,12 @@ test_replaycache_miss(void *arg) strlen(test_buffer), NULL); tt_int_op(result,OP_EQ, 0); + /* make sure a different buffer misses as well */ + result = + replaycache_add_and_test_internal(1200, NULL, test_buffer_2, + strlen(test_buffer_2), NULL); + tt_int_op(result,OP_EQ, 0); + /* poke the bad-parameter error case too */ result = replaycache_add_and_test_internal(1200, NULL, test_buffer, @@ -115,6 +135,18 @@ test_replaycache_hit(void *arg) strlen(test_buffer), NULL); tt_int_op(result,OP_EQ, 1); + /* make sure a different buffer misses then hits as well */ + + result = + replaycache_add_and_test_internal(1200, r, test_buffer_2, + strlen(test_buffer_2), NULL); + tt_int_op(result,OP_EQ, 0); + + result = + replaycache_add_and_test_internal(1300, r, test_buffer_2, + strlen(test_buffer_2), NULL); + tt_int_op(result,OP_EQ, 1); + done: if (r) replaycache_free(r); @@ -245,7 +277,7 @@ test_replaycache_scrub(void *arg) /* Make sure we hit the aging-out case too */ replaycache_scrub_if_needed_internal(1500, r); /* Assert that we aged it */ - tt_int_op(digestmap_size(r->digests_seen),OP_EQ, 0); + tt_int_op(digest256map_size(r->digests_seen),OP_EQ, 0); done: if (r) replaycache_free(r); diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index 381a592c5b..090a60766c 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -1,22 +1,48 @@ /* Copyright (c) 2014, The Tor Project, Inc. */ /* See LICENSE for licensing information */ +#include "orconfig.h" +#include <math.h> +#include <time.h> + +#define DIRVOTE_PRIVATE +#define NETWORKSTATUS_PRIVATE #define ROUTERLIST_PRIVATE +#define TOR_UNIT_TESTING #include "or.h" -#include "routerlist.h" +#include "config.h" +#include "container.h" #include "directory.h" +#include "dirvote.h" +#include "networkstatus.h" +#include "nodelist.h" +#include "policies.h" +#include "routerlist.h" +#include "routerparse.h" #include "test.h" +#include "test_dir_common.h" + +extern const char AUTHORITY_CERT_1[]; +extern const char AUTHORITY_SIGNKEY_1[]; +extern const char AUTHORITY_CERT_2[]; +extern const char AUTHORITY_SIGNKEY_2[]; +extern const char AUTHORITY_CERT_3[]; +extern const char AUTHORITY_SIGNKEY_3[]; + +void construct_consensus(const char **consensus_text_md); /* 4 digests + 3 sep + pre + post + NULL */ static char output[4*BASE64_DIGEST256_LEN+3+2+2+1]; static void mock_get_from_dirserver(uint8_t dir_purpose, uint8_t router_purpose, - const char *resource, int pds_flags) + const char *resource, int pds_flags, + download_want_authority_t want_authority) { (void)dir_purpose; (void)router_purpose; (void)pds_flags; + (void)want_authority; tt_assert(resource); strlcpy(output, resource, sizeof(output)); done: @@ -92,12 +118,268 @@ test_routerlist_launch_descriptor_downloads(void *arg) smartlist_free(downloadable); } +void +construct_consensus(const char **consensus_text_md) +{ + networkstatus_t *vote = NULL; + networkstatus_t *v1 = NULL, *v2 = NULL, *v3 = NULL; + networkstatus_voter_info_t *voter = NULL; + authority_cert_t *cert1=NULL, *cert2=NULL, *cert3=NULL; + crypto_pk_t *sign_skey_1=NULL, *sign_skey_2=NULL, *sign_skey_3=NULL; + crypto_pk_t *sign_skey_leg=NULL; + time_t now = time(NULL); + smartlist_t *votes = NULL; + addr_policy_t *pol1 = NULL, *pol2 = NULL, *pol3 = NULL; + int n_vrs; + + tt_assert(!dir_common_authority_pk_init(&cert1, &cert2, &cert3, + &sign_skey_1, &sign_skey_2, + &sign_skey_3)); + sign_skey_leg = pk_generate(4); + + dir_common_construct_vote_1(&vote, cert1, sign_skey_1, + &dir_common_gen_routerstatus_for_v3ns, + &v1, &n_vrs, now, 1); + + tt_assert(v1); + tt_int_op(n_vrs, ==, 4); + tt_int_op(smartlist_len(v1->routerstatus_list), ==, 4); + + dir_common_construct_vote_2(&vote, cert2, sign_skey_2, + &dir_common_gen_routerstatus_for_v3ns, + &v2, &n_vrs, now, 1); + + tt_assert(v2); + tt_int_op(n_vrs, ==, 4); + tt_int_op(smartlist_len(v2->routerstatus_list), ==, 4); + + dir_common_construct_vote_3(&vote, cert3, sign_skey_3, + &dir_common_gen_routerstatus_for_v3ns, + &v3, &n_vrs, now, 1); + + tt_assert(v3); + tt_int_op(n_vrs, ==, 4); + tt_int_op(smartlist_len(v3->routerstatus_list), ==, 4); + + votes = smartlist_new(); + smartlist_add(votes, v1); + smartlist_add(votes, v2); + smartlist_add(votes, v3); + + *consensus_text_md = networkstatus_compute_consensus(votes, 3, + cert1->identity_key, + sign_skey_1, + "AAAAAAAAAAAAAAAAAAAA", + sign_skey_leg, + FLAV_MICRODESC); + + tt_assert(*consensus_text_md); + + done: + if (vote) + tor_free(vote); + if (voter) + tor_free(voter); + if (pol1) + tor_free(pol1); + if (pol2) + tor_free(pol2); + if (pol3) + tor_free(pol3); +} + +static void +test_router_pick_directory_server_impl(void *arg) +{ + (void)arg; + + networkstatus_t *con_md = NULL; + const char *consensus_text_md = NULL; + int flags = PDS_IGNORE_FASCISTFIREWALL|PDS_RETRY_IF_NO_SERVERS; + or_options_t *options = get_options_mutable(); + const routerstatus_t *rs = NULL; + options->UseMicrodescriptors = 1; + char *router1_id = NULL, *router2_id = NULL, *router3_id = NULL; + node_t *node_router1 = NULL, *node_router2 = NULL, *node_router3 = NULL; + config_line_t *policy_line = NULL; + time_t now = time(NULL); + int tmp_dirport1, tmp_dirport3; + + (void)arg; + + /* No consensus available, fail early */ + rs = router_pick_directory_server_impl(V3_DIRINFO, (const int) 0, NULL); + tt_assert(rs == NULL); + + construct_consensus(&consensus_text_md); + tt_assert(consensus_text_md); + con_md = networkstatus_parse_vote_from_string(consensus_text_md, NULL, + NS_TYPE_CONSENSUS); + tt_assert(con_md); + tt_int_op(con_md->flavor,==, FLAV_MICRODESC); + tt_assert(con_md->routerstatus_list); + tt_int_op(smartlist_len(con_md->routerstatus_list), ==, 3); + tt_assert(!networkstatus_set_current_consensus_from_ns(con_md, + "microdesc")); + nodelist_set_consensus(con_md); + nodelist_assert_ok(); + + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + /* We should not fail now we have a consensus and routerstatus_list + * and nodelist are populated. */ + tt_assert(rs != NULL); + + /* Manipulate the nodes so we get the dir server we expect */ + router1_id = tor_malloc(DIGEST_LEN); + memset(router1_id, TEST_DIR_ROUTER_ID_1, DIGEST_LEN); + router2_id = tor_malloc(DIGEST_LEN); + memset(router2_id, TEST_DIR_ROUTER_ID_2, DIGEST_LEN); + router3_id = tor_malloc(DIGEST_LEN); + memset(router3_id, TEST_DIR_ROUTER_ID_3, DIGEST_LEN); + + node_router1 = node_get_mutable_by_id(router1_id); + node_router2 = node_get_mutable_by_id(router2_id); + node_router3 = node_get_mutable_by_id(router3_id); + + node_router1->is_possible_guard = 1; + + node_router1->is_running = 0; + node_router3->is_running = 0; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN)); + rs = NULL; + node_router1->is_running = 1; + node_router3->is_running = 1; + + node_router1->rs->is_v2_dir = 0; + node_router3->rs->is_v2_dir = 0; + tmp_dirport1 = node_router1->rs->dir_port; + tmp_dirport3 = node_router3->rs->dir_port; + node_router1->rs->dir_port = 0; + node_router3->rs->dir_port = 0; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN)); + rs = NULL; + node_router1->rs->is_v2_dir = 1; + node_router3->rs->is_v2_dir = 1; + node_router1->rs->dir_port = tmp_dirport1; + node_router3->rs->dir_port = tmp_dirport3; + + node_router1->is_valid = 0; + node_router3->is_valid = 0; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router2_id, DIGEST_LEN)); + rs = NULL; + node_router1->is_valid = 1; + node_router3->is_valid = 1; + + flags |= PDS_FOR_GUARD; + node_router1->using_as_guard = 1; + node_router2->using_as_guard = 1; + node_router3->using_as_guard = 1; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs == NULL); + node_router1->using_as_guard = 0; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); + rs = NULL; + node_router2->using_as_guard = 0; + node_router3->using_as_guard = 0; + + /* One not valid, one guard. This should leave one remaining */ + node_router1->is_valid = 0; + node_router2->using_as_guard = 1; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router3_id, DIGEST_LEN)); + rs = NULL; + node_router1->is_valid = 1; + node_router2->using_as_guard = 0; + + /* Manipulate overloaded */ + + node_router2->rs->last_dir_503_at = now; + node_router3->rs->last_dir_503_at = now; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); + node_router2->rs->last_dir_503_at = 0; + node_router3->rs->last_dir_503_at = 0; + + /* Set a Fascist firewall */ + flags &= ! PDS_IGNORE_FASCISTFIREWALL; + policy_line = tor_malloc_zero(sizeof(config_line_t)); + policy_line->key = tor_strdup("ReachableORAddresses"); + policy_line->value = tor_strdup("accept *:442, reject *:*"); + options->ReachableORAddresses = policy_line; + policies_parse_from_options(options); + + node_router1->rs->or_port = 444; + node_router2->rs->or_port = 443; + node_router3->rs->or_port = 442; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router3_id, DIGEST_LEN)); + node_router1->rs->or_port = 442; + node_router2->rs->or_port = 443; + node_router3->rs->or_port = 444; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); + + /* Fascist firewall and overloaded */ + node_router1->rs->or_port = 442; + node_router2->rs->or_port = 443; + node_router3->rs->or_port = 442; + node_router3->rs->last_dir_503_at = now; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); + node_router3->rs->last_dir_503_at = 0; + + /* Fascists against OR and Dir */ + policy_line = tor_malloc_zero(sizeof(config_line_t)); + policy_line->key = tor_strdup("ReachableAddresses"); + policy_line->value = tor_strdup("accept *:80, reject *:*"); + options->ReachableDirAddresses = policy_line; + policies_parse_from_options(options); + node_router1->rs->or_port = 442; + node_router2->rs->or_port = 441; + node_router3->rs->or_port = 443; + node_router1->rs->dir_port = 80; + node_router2->rs->dir_port = 80; + node_router3->rs->dir_port = 81; + node_router1->rs->last_dir_503_at = now; + rs = router_pick_directory_server_impl(V3_DIRINFO, flags, NULL); + tt_assert(rs != NULL); + tt_assert(tor_memeq(rs->identity_digest, router1_id, DIGEST_LEN)); + node_router1->rs->last_dir_503_at = 0; + + done: + if (router1_id) + tor_free(router1_id); + if (router2_id) + tor_free(router2_id); + if (router3_id) + tor_free(router3_id); + if (options->ReachableORAddresses || + options->ReachableDirAddresses) + policies_free_all(); +} + #define NODE(name, flags) \ { #name, test_routerlist_##name, (flags), NULL, NULL } +#define ROUTER(name,flags) \ + { #name, test_router_##name, (flags), NULL, NULL } struct testcase_t routerlist_tests[] = { NODE(initiate_descriptor_downloads, 0), NODE(launch_descriptor_downloads, 0), + ROUTER(pick_directory_server_impl, TT_FORK), END_OF_TESTCASES }; diff --git a/src/test/test_routerset.c b/src/test/test_routerset.c index 90dfb28c6b..74b39c0486 100644 --- a/src/test/test_routerset.c +++ b/src/test/test_routerset.c @@ -423,10 +423,10 @@ NS(test_main)(void *arg) } #undef NS_SUBMODULE -#define NS_SUBMODULE ASPECT(routerset_parse, policy) +#define NS_SUBMODULE ASPECT(routerset_parse, policy_wildcard) /* - * Structural test for routerset_parse, when given a valid policy. + * Structural test for routerset_parse, when given a valid wildcard policy. */ NS_DECL(addr_policy_t *, router_parse_addr_policy_item_from_string, @@ -470,6 +470,100 @@ NS(router_parse_addr_policy_item_from_string)(const char *s, } #undef NS_SUBMODULE +#define NS_SUBMODULE ASPECT(routerset_parse, policy_ipv4) + +/* + * Structural test for routerset_parse, when given a valid IPv4 address + * literal policy. + */ + +NS_DECL(addr_policy_t *, router_parse_addr_policy_item_from_string, + (const char *s, int assume_action, int *bogus)); + +addr_policy_t *NS(mock_addr_policy); + +static void +NS(test_main)(void *arg) +{ + routerset_t *set; + const char *s; + int r; + (void)arg; + + NS_MOCK(router_parse_addr_policy_item_from_string); + NS(mock_addr_policy) = tor_malloc_zero(sizeof(addr_policy_t)); + + set = routerset_new(); + s = "127.0.0.1"; + r = routerset_parse(set, s, ""); + tt_int_op(r, OP_EQ, 0); + tt_int_op(smartlist_len(set->policies), OP_NE, 0); + tt_int_op(CALLED(router_parse_addr_policy_item_from_string), OP_EQ, 1); + + done: + routerset_free(set); +} + +addr_policy_t * +NS(router_parse_addr_policy_item_from_string)(const char *s, int assume_action, + int *bogus) +{ + (void)s; + (void)assume_action; + CALLED(router_parse_addr_policy_item_from_string)++; + *bogus = 0; + + return NS(mock_addr_policy); +} + +#undef NS_SUBMODULE +#define NS_SUBMODULE ASPECT(routerset_parse, policy_ipv6) + +/* + * Structural test for routerset_parse, when given a valid IPv6 address + * literal policy. + */ + +NS_DECL(addr_policy_t *, router_parse_addr_policy_item_from_string, + (const char *s, int assume_action, int *bad)); + +addr_policy_t *NS(mock_addr_policy); + +static void +NS(test_main)(void *arg) +{ + routerset_t *set; + const char *s; + int r; + (void)arg; + + NS_MOCK(router_parse_addr_policy_item_from_string); + NS(mock_addr_policy) = tor_malloc_zero(sizeof(addr_policy_t)); + + set = routerset_new(); + s = "::1"; + r = routerset_parse(set, s, ""); + tt_int_op(r, OP_EQ, 0); + tt_int_op(smartlist_len(set->policies), OP_NE, 0); + tt_int_op(CALLED(router_parse_addr_policy_item_from_string), OP_EQ, 1); + + done: + routerset_free(set); +} + +addr_policy_t * +NS(router_parse_addr_policy_item_from_string)(const char *s, + int assume_action, int *bad) +{ + (void)s; + (void)assume_action; + CALLED(router_parse_addr_policy_item_from_string)++; + *bad = 0; + + return NS(mock_addr_policy); +} + +#undef NS_SUBMODULE #define NS_SUBMODULE ASPECT(routerset_union, source_bad) /* @@ -2109,7 +2203,9 @@ struct testcase_t routerset_tests[] = { TEST_CASE_ASPECT(routerset_parse, valid_hexdigest), TEST_CASE_ASPECT(routerset_parse, valid_nickname), TEST_CASE_ASPECT(routerset_parse, get_countryname), - TEST_CASE_ASPECT(routerset_parse, policy), + TEST_CASE_ASPECT(routerset_parse, policy_wildcard), + TEST_CASE_ASPECT(routerset_parse, policy_ipv4), + TEST_CASE_ASPECT(routerset_parse, policy_ipv6), TEST_CASE(routerset_subtract_nodes), TEST_CASE_ASPECT(routerset_subtract_nodes, null_routerset), TEST_CASE(routerset_to_string), diff --git a/src/test/test_status.c b/src/test/test_status.c index cbc8af188c..84a0f6c024 100644 --- a/src/test/test_status.c +++ b/src/test/test_status.c @@ -707,15 +707,18 @@ NS(logv)(int severity, log_domain_mask_t domain, tt_ptr_op(strstr(funcname, "log_accounting"), OP_NE, NULL); tt_ptr_op(suffix, OP_EQ, NULL); tt_str_op(format, OP_EQ, - "Heartbeat: Accounting enabled. Sent: %s / %s, Received: %s / %s. " - "The current accounting interval ends on %s, in %s."); + "Heartbeat: Accounting enabled. Sent: %s, Received: %s, Used: %s / " + "%s, Rule: %s. The current accounting interval ends on %s, in %s."); tt_str_op(va_arg(ap, char *), OP_EQ, "0 kB"); /* acc_sent */ - tt_str_op(va_arg(ap, char *), OP_EQ, "0 kB"); /* acc_max */ tt_str_op(va_arg(ap, char *), OP_EQ, "0 kB"); /* acc_rcvd */ + tt_str_op(va_arg(ap, char *), OP_EQ, "0 kB"); /* acc_used */ tt_str_op(va_arg(ap, char *), OP_EQ, "0 kB"); /* acc_max */ - /* format_local_iso_time uses local tz, just check mins and secs. */ - tt_ptr_op(strstr(va_arg(ap, char *), ":01:00"), - OP_NE, NULL); /* end_buf */ + tt_str_op(va_arg(ap, char *), OP_EQ, "max"); /* acc_rule */ + /* format_local_iso_time uses local tz, so we can't just compare + * the string against a constant */ + char datetime[ISO_TIME_LEN+1]; + format_local_iso_time(datetime, 60); + tt_str_op(va_arg(ap, char *), OP_EQ, datetime); /* end_buf */ tt_str_op(va_arg(ap, char *), OP_EQ, "0:01 hours"); /* remaining */ break; case 2: diff --git a/src/test/test_switch_id.c b/src/test/test_switch_id.c new file mode 100644 index 0000000000..3d9c1c8bba --- /dev/null +++ b/src/test/test_switch_id.c @@ -0,0 +1,191 @@ +/* Copyright (c) 2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "or.h" + +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif + +#define TEST_BUILT_WITH_CAPS 0 +#define TEST_HAVE_CAPS 1 +#define TEST_ROOT_CAN_BIND_LOW 2 +#define TEST_SETUID 3 +#define TEST_SETUID_KEEPCAPS 4 +#define TEST_SETUID_STRICT 5 + +static const struct { + const char *name; + int test_id; +} which_test[] = { + { "built-with-caps", TEST_BUILT_WITH_CAPS }, + { "have-caps", TEST_HAVE_CAPS }, + { "root-bind-low", TEST_ROOT_CAN_BIND_LOW }, + { "setuid", TEST_SETUID }, + { "setuid-keepcaps", TEST_SETUID_KEEPCAPS }, + { "setuid-strict", TEST_SETUID_STRICT }, + { NULL, 0 } +}; + +#if !defined(_WIN32) +/* 0 on no, 1 on yes, -1 on failure. */ +static int +check_can_bind_low_ports(void) +{ + int port; + struct sockaddr_in sin; + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + + for (port = 600; port < 1024; ++port) { + sin.sin_port = htons(port); + tor_socket_t fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (! SOCKET_OK(fd)) { + perror("socket"); + return -1; + } + + int one = 1; + if (setsockopt(fd, SOL_SOCKET,SO_REUSEADDR, (void*)&one, + (socklen_t)sizeof(one))) { + perror("setsockopt"); + tor_close_socket_simple(fd); + return -1; + } + + int res = bind(fd, (struct sockaddr *)&sin, sizeof(sin)); + tor_close_socket_simple(fd); + + if (res == 0) { + /* bind was successful */ + return 1; + } else if (errno == EACCES || errno == EPERM) { + /* Got a permission-denied error. */ + return 0; + } else if (errno == EADDRINUSE) { + /* Huh; somebody is using that port. */ + } else { + perror("bind"); + } + } + + return -1; +} +#endif + +int +main(int argc, char **argv) +{ +#if defined(_WIN32) + (void) argc; + (void) argv; + + fprintf(stderr, "This test is not supported on your OS.\n"); + return 77; +#else + const char *username; + const char *testname; + if (argc != 3) { + fprintf(stderr, "I want 2 arguments: a username and a command.\n"); + return 1; + } + if (getuid() != 0) { + fprintf(stderr, "This test only works when it's run as root.\n"); + return 1; + } + username = argv[1]; + testname = argv[2]; + int test_id = -1; + int i; + for (i = 0; which_test[i].name; ++i) { + if (!strcmp(which_test[i].name, testname)) { + test_id = which_test[i].test_id; + break; + } + } + if (test_id == -1) { + fprintf(stderr, "Unrecognized test '%s'\n", testname); + return 1; + } + +#ifdef HAVE_LINUX_CAPABILITIES + const int have_cap_support = 1; +#else + const int have_cap_support = 0; +#endif + + int okay; + + init_logging(1); + log_severity_list_t sev; + memset(&sev, 0, sizeof(sev)); + set_log_severity_config(LOG_WARN, LOG_ERR, &sev); + add_stream_log(&sev, "", fileno(stderr)); + + switch (test_id) + { + case TEST_BUILT_WITH_CAPS: + /* Succeed if we were built with capability support. */ + okay = have_cap_support; + break; + case TEST_HAVE_CAPS: + /* Succeed if "capabilities work" == "we were built with capability + * support." */ + okay = have_cap_support == have_capability_support(); + break; + case TEST_ROOT_CAN_BIND_LOW: + /* Succeed if root can bind low ports. */ + okay = check_can_bind_low_ports() == 1; + break; + case TEST_SETUID: + /* Succeed if we can do a setuid with no capability retention, and doing + * so makes us lose the ability to bind low ports */ + case TEST_SETUID_KEEPCAPS: + /* Succeed if we can do a setuid with capability retention, and doing so + * does not make us lose the ability to bind low ports */ + { + int keepcaps = (test_id == TEST_SETUID_KEEPCAPS); + okay = switch_id(username, keepcaps ? SWITCH_ID_KEEP_BINDLOW : 0) == 0; + if (okay) { + okay = check_can_bind_low_ports() == keepcaps; + } + break; + } + case TEST_SETUID_STRICT: + /* Succeed if, after a setuid, we cannot setuid back, and we cannot + * re-grab any capabilities. */ + okay = switch_id(username, SWITCH_ID_KEEP_BINDLOW) == 0; + if (okay) { + /* We'd better not be able to setuid back! */ + if (setuid(0) == 0 || errno != EPERM) { + okay = 0; + } + } +#ifdef HAVE_LINUX_CAPABILITIES + if (okay) { + cap_t caps = cap_get_proc(); + const cap_value_t caplist[] = { + CAP_SETUID, + }; + cap_set_flag(caps, CAP_PERMITTED, 1, caplist, CAP_SET); + if (cap_set_proc(caps) == 0 || errno != EPERM) { + okay = 0; + } + cap_free(caps); + } +#endif + break; + default: + fprintf(stderr, "Unsupported test '%s'\n", testname); + okay = 0; + break; + } + + if (!okay) { + fprintf(stderr, "Test %s failed!\n", testname); + } + + return (okay ? 0 : 1); +#endif +} + diff --git a/src/test/test_switch_id.sh b/src/test/test_switch_id.sh new file mode 100755 index 0000000000..1b4e0998b5 --- /dev/null +++ b/src/test/test_switch_id.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +if test "`id -u`" != '0'; then + echo "This test only works when run as root. Skipping." >&2 + exit 77 +fi + +if test "`id -u nobody`" = ""; then + echo "This test requires that your system have a 'nobody' user. Sorry." >&2 + exit 1 +fi + +"${builddir:-.}/src/test/test-switch-id" nobody setuid || exit 1 +"${builddir:-.}/src/test/test-switch-id" nobody root-bind-low || exit 1 +"${builddir:-.}/src/test/test-switch-id" nobody setuid-strict || exit 1 +"${builddir:-.}/src/test/test-switch-id" nobody built-with-caps || exit 0 +# ... Go beyond this point only if we were built with capability support. + +"${builddir:-.}/src/test/test-switch-id" nobody have-caps || exit 1 +"${builddir:-.}/src/test/test-switch-id" nobody setuid-keepcaps || exit 1 + + +echo "All okay" + +exit 0 diff --git a/src/test/test_threads.c b/src/test/test_threads.c index 35f5dc8ea3..fe88c9470f 100644 --- a/src/test/test_threads.c +++ b/src/test/test_threads.c @@ -73,6 +73,8 @@ thread_test_func_(void* _s) ++thread_fns_failed; tor_mutex_release(thread_test_mutex_); + tor_free(mycount); + tor_mutex_release(m); spawn_exit(); diff --git a/src/test/test_tortls.c b/src/test/test_tortls.c new file mode 100644 index 0000000000..add020e9f4 --- /dev/null +++ b/src/test/test_tortls.c @@ -0,0 +1,2845 @@ +/* Copyright (c) 2010-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define TORTLS_PRIVATE +#define LOG_PRIVATE +#include "orconfig.h" + +#ifdef _WIN32 +#include <winsock2.h> +#endif + +#ifdef __GNUC__ +#define GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#endif + +#if __GNUC__ && GCC_VERSION >= 402 +#if GCC_VERSION >= 406 +#pragma GCC diagnostic push +#endif +/* Some versions of OpenSSL declare SSL_get_selected_srtp_profile twice in + * srtp.h. Suppress the GCC warning so we can build with -Wredundant-decl. */ +#pragma GCC diagnostic ignored "-Wredundant-decls" +#endif + +#include <openssl/opensslv.h> + +#include <openssl/ssl.h> +#include <openssl/ssl3.h> +#include <openssl/err.h> +#include <openssl/asn1t.h> +#include <openssl/x509.h> +#include <openssl/rsa.h> +#include <openssl/evp.h> +#include <openssl/bn.h> + +#if __GNUC__ && GCC_VERSION >= 402 +#if GCC_VERSION >= 406 +#pragma GCC diagnostic pop +#else +#pragma GCC diagnostic warning "-Wredundant-decls" +#endif +#endif + +#include "or.h" +#include "torlog.h" +#include "config.h" +#include "tortls.h" + +#include "test.h" +#include "log_test_helpers.h" +#define NS_MODULE tortls + +extern tor_tls_context_t *server_tls_context; +extern tor_tls_context_t *client_tls_context; + +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) \ + && !defined(LIBRESSL_VERSION_NUMBER) +#define OPENSSL_OPAQUE +#define SSL_STATE_STR "before SSL initialization" +#else +#define SSL_STATE_STR "before/accept initialization" +#endif + +#ifndef OPENSSL_OPAQUE +static SSL_METHOD * +give_me_a_test_method(void) +{ + SSL_METHOD *method = tor_malloc_zero(sizeof(SSL_METHOD)); + memcpy(method, TLSv1_method(), sizeof(SSL_METHOD)); + return method; +} + +static int +fake_num_ciphers(void) +{ + return 0; +} +#endif + +static void +test_tortls_errno_to_tls_error(void *data) +{ + (void) data; + tt_int_op(tor_errno_to_tls_error(SOCK_ERRNO(ECONNRESET)),OP_EQ, + TOR_TLS_ERROR_CONNRESET); + tt_int_op(tor_errno_to_tls_error(SOCK_ERRNO(ETIMEDOUT)),OP_EQ, + TOR_TLS_ERROR_TIMEOUT); + tt_int_op(tor_errno_to_tls_error(SOCK_ERRNO(EHOSTUNREACH)),OP_EQ, + TOR_TLS_ERROR_NO_ROUTE); + tt_int_op(tor_errno_to_tls_error(SOCK_ERRNO(ENETUNREACH)),OP_EQ, + TOR_TLS_ERROR_NO_ROUTE); + tt_int_op(tor_errno_to_tls_error(SOCK_ERRNO(ECONNREFUSED)),OP_EQ, + TOR_TLS_ERROR_CONNREFUSED); + tt_int_op(tor_errno_to_tls_error(0),OP_EQ,TOR_TLS_ERROR_MISC); + done: + (void)1; +} + +static void +test_tortls_err_to_string(void *data) +{ + (void) data; + tt_str_op(tor_tls_err_to_string(1),OP_EQ,"[Not an error.]"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_ERROR_MISC),OP_EQ,"misc error"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_ERROR_IO),OP_EQ,"unexpected close"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_ERROR_CONNREFUSED),OP_EQ, + "connection refused"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_ERROR_CONNRESET),OP_EQ, + "connection reset"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_ERROR_NO_ROUTE),OP_EQ, + "host unreachable"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_ERROR_TIMEOUT),OP_EQ, + "connection timed out"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_CLOSE),OP_EQ,"closed"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_WANTREAD),OP_EQ,"want to read"); + tt_str_op(tor_tls_err_to_string(TOR_TLS_WANTWRITE),OP_EQ,"want to write"); + tt_str_op(tor_tls_err_to_string(-100),OP_EQ,"(unknown error code)"); + done: + (void)1; +} + +static int +mock_tls_cert_matches_key(const tor_tls_t *tls, const tor_x509_cert_t *cert) +{ + (void) tls; + (void) cert; // XXXX look at this. + return 1; +} + +static void +test_tortls_tor_tls_new(void *data) +{ + (void) data; + MOCK(tor_tls_cert_matches_key, mock_tls_cert_matches_key); + crypto_pk_t *key1 = NULL, *key2 = NULL; + SSL_METHOD *method = NULL; + + key1 = pk_generate(2); + key2 = pk_generate(3); + + tor_tls_t *tls = NULL; + tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER, + key1, key2, 86400), OP_EQ, 0); + tls = tor_tls_new(-1, 0); + tt_want(tls); + tor_tls_free(tls); tls = NULL; + + SSL_CTX_free(client_tls_context->ctx); + client_tls_context->ctx = NULL; + tls = tor_tls_new(-1, 0); + tt_assert(!tls); + +#ifndef OPENSSL_OPAQUE + method = give_me_a_test_method(); + SSL_CTX *ctx = SSL_CTX_new(method); + method->num_ciphers = fake_num_ciphers; + client_tls_context->ctx = ctx; + tls = tor_tls_new(-1, 0); + tt_assert(!tls); +#endif + + done: + UNMOCK(tor_tls_cert_matches_key); + crypto_pk_free(key1); + crypto_pk_free(key2); + tor_tls_free(tls); + tor_free(method); + tor_tls_free_all(); +} + +#define NS_MODULE tortls +NS_DECL(void, logv, (int severity, log_domain_mask_t domain, + const char *funcname, const char *suffix, + const char *format, va_list ap)); + +static void +NS(logv)(int severity, log_domain_mask_t domain, + const char *funcname, const char *suffix, const char *format, + va_list ap) +{ + (void) severity; + (void) domain; + (void) funcname; + (void) suffix; + (void) format; + (void) ap; // XXXX look at this. + CALLED(logv)++; +} + +static void +test_tortls_tor_tls_get_error(void *data) +{ + (void) data; + MOCK(tor_tls_cert_matches_key, mock_tls_cert_matches_key); + crypto_pk_t *key1 = NULL, *key2 = NULL; + key1 = pk_generate(2); + key2 = pk_generate(3); + + tor_tls_t *tls = NULL; + tt_int_op(tor_tls_context_init(TOR_TLS_CTX_IS_PUBLIC_SERVER, + key1, key2, 86400), OP_EQ, 0); + tls = tor_tls_new(-1, 0); + NS_MOCK(logv); + tt_int_op(CALLED(logv), OP_EQ, 0); + tor_tls_get_error(tls, 0, 0, + (const char *)"test", 0, 0); + tt_int_op(CALLED(logv), OP_EQ, 1); + + done: + UNMOCK(tor_tls_cert_matches_key); + NS_UNMOCK(logv); + crypto_pk_free(key1); + crypto_pk_free(key2); + tor_tls_free(tls); +} + +static void +test_tortls_get_state_description(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + char *buf; + SSL_CTX *ctx; + + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(SSLv23_method()); + + buf = tor_malloc_zero(1000); + tls = tor_malloc_zero(sizeof(tor_tls_t)); + + tor_tls_get_state_description(NULL, buf, 20); + tt_str_op(buf, OP_EQ, "(No SSL object)"); + + SSL_free(tls->ssl); + tls->ssl = NULL; + tor_tls_get_state_description(tls, buf, 20); + tt_str_op(buf, OP_EQ, "(No SSL object)"); + + tls->ssl = SSL_new(ctx); + tor_tls_get_state_description(tls, buf, 200); + tt_str_op(buf, OP_EQ, SSL_STATE_STR " in HANDSHAKE"); + + tls->state = TOR_TLS_ST_OPEN; + tor_tls_get_state_description(tls, buf, 200); + tt_str_op(buf, OP_EQ, SSL_STATE_STR " in OPEN"); + + tls->state = TOR_TLS_ST_GOTCLOSE; + tor_tls_get_state_description(tls, buf, 200); + tt_str_op(buf, OP_EQ, SSL_STATE_STR " in GOTCLOSE"); + + tls->state = TOR_TLS_ST_SENTCLOSE; + tor_tls_get_state_description(tls, buf, 200); + tt_str_op(buf, OP_EQ, SSL_STATE_STR " in SENTCLOSE"); + + tls->state = TOR_TLS_ST_CLOSED; + tor_tls_get_state_description(tls, buf, 200); + tt_str_op(buf, OP_EQ, SSL_STATE_STR " in CLOSED"); + + tls->state = TOR_TLS_ST_RENEGOTIATE; + tor_tls_get_state_description(tls, buf, 200); + tt_str_op(buf, OP_EQ, SSL_STATE_STR " in RENEGOTIATE"); + + tls->state = TOR_TLS_ST_BUFFEREVENT; + tor_tls_get_state_description(tls, buf, 200); + tt_str_op(buf, OP_EQ, SSL_STATE_STR); + + tls->state = 7; + tor_tls_get_state_description(tls, buf, 200); + tt_str_op(buf, OP_EQ, SSL_STATE_STR " in unknown TLS state"); + + done: + SSL_CTX_free(ctx); + SSL_free(tls->ssl); + tor_free(buf); + tor_free(tls); +} + +extern int tor_tls_object_ex_data_index; + +static void +test_tortls_get_by_ssl(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + tor_tls_t *res; + SSL_CTX *ctx; + SSL *ssl; + + SSL_library_init(); + SSL_load_error_strings(); + tor_tls_allocate_tor_tls_object_ex_data_index(); + + ctx = SSL_CTX_new(SSLv23_method()); + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->magic = TOR_TLS_MAGIC; + + ssl = SSL_new(ctx); + + res = tor_tls_get_by_ssl(ssl); + tt_assert(!res); + + SSL_set_ex_data(ssl, tor_tls_object_ex_data_index, tls); + + res = tor_tls_get_by_ssl(ssl); + tt_assert(res == tls); + + done: + SSL_free(ssl); + SSL_CTX_free(ctx); + tor_free(tls); +} + +static void +test_tortls_allocate_tor_tls_object_ex_data_index(void *ignored) +{ + (void)ignored; + int first; + + tor_tls_allocate_tor_tls_object_ex_data_index(); + + first = tor_tls_object_ex_data_index; + tor_tls_allocate_tor_tls_object_ex_data_index(); + tt_int_op(first, OP_EQ, tor_tls_object_ex_data_index); + + done: + (void)0; +} + +static void +test_tortls_log_one_error(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + SSL_CTX *ctx; + SSL *ssl = NULL; + + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(SSLv23_method()); + tls = tor_malloc_zero(sizeof(tor_tls_t)); + int previous_log = setup_capture_of_logs(LOG_INFO); + + tor_tls_log_one_error(NULL, 0, LOG_WARN, 0, "something"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error while something: " + "(null) (in (null):(null):---)\n"); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, 0, LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error: (null) " + "(in (null):(null):---)\n"); + + mock_clean_saved_logs(); + tls->address = tor_strdup("127.hello"); + tor_tls_log_one_error(tls, 0, LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error with 127.hello: (null) " + "(in (null):(null):---)\n"); + tor_free(tls->address); + + mock_clean_saved_logs(); + tls->address = tor_strdup("127.hello"); + tor_tls_log_one_error(tls, 0, LOG_WARN, 0, "blarg"); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error while blarg with " + "127.hello: (null) (in (null):(null):---)\n"); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, ERR_PACK(1, 2, 3), LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error with 127.hello: " + "BN lib (in unknown library:(null):---)\n"); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, ERR_PACK(1, 2, SSL_R_HTTP_REQUEST), + LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, ERR_PACK(1, 2, SSL_R_HTTPS_PROXY_REQUEST), + LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, ERR_PACK(1, 2, SSL_R_RECORD_LENGTH_MISMATCH), + LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, ERR_PACK(1, 2, SSL_R_RECORD_TOO_LARGE), + LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, ERR_PACK(1, 2, SSL_R_UNKNOWN_PROTOCOL), + LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, ERR_PACK(1, 2, SSL_R_UNSUPPORTED_PROTOCOL), + LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + + tls->ssl = SSL_new(ctx); + + mock_clean_saved_logs(); + tor_tls_log_one_error(tls, 0, LOG_WARN, 0, NULL); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error with 127.hello: (null)" + " (in (null):(null):" SSL_STATE_STR ")\n"); + + done: + teardown_capture_of_logs(previous_log); + SSL_free(ssl); + SSL_CTX_free(ctx); + if (tls && tls->ssl) + SSL_free(tls->ssl); + if (tls) + tor_free(tls->address); + tor_free(tls); +} + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_get_error(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + int ret; + SSL_CTX *ctx; + + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(SSLv23_method()); + int previous_log = setup_capture_of_logs(LOG_INFO); + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = SSL_new(ctx); + SSL_set_bio(tls->ssl, BIO_new(BIO_s_mem()), NULL); + + ret = tor_tls_get_error(tls, 0, 0, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, TOR_TLS_ERROR_IO); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error: unexpected close while" + " something (before/accept initialization)\n"); + + mock_clean_saved_logs(); + ret = tor_tls_get_error(tls, 2, 0, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + + mock_clean_saved_logs(); + ret = tor_tls_get_error(tls, 0, 1, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, -11); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + + mock_clean_saved_logs(); + ERR_clear_error(); + ERR_put_error(ERR_LIB_BN, 2, -1, "somewhere.c", 99); + ret = tor_tls_get_error(tls, 0, 0, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, TOR_TLS_ERROR_MISC); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error while something: (null)" + " (in bignum routines:(null):before/accept initialization)\n"); + + mock_clean_saved_logs(); + ERR_clear_error(); + tls->ssl->rwstate = SSL_READING; + SSL_get_rbio(tls->ssl)->flags = BIO_FLAGS_READ; + ret = tor_tls_get_error(tls, -1, 0, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, TOR_TLS_WANTREAD); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + + mock_clean_saved_logs(); + ERR_clear_error(); + tls->ssl->rwstate = SSL_READING; + SSL_get_rbio(tls->ssl)->flags = BIO_FLAGS_WRITE; + ret = tor_tls_get_error(tls, -1, 0, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, TOR_TLS_WANTWRITE); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + + mock_clean_saved_logs(); + ERR_clear_error(); + tls->ssl->rwstate = 0; + tls->ssl->shutdown = SSL_RECEIVED_SHUTDOWN; + tls->ssl->s3->warn_alert =SSL_AD_CLOSE_NOTIFY; + ret = tor_tls_get_error(tls, 0, 0, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, TOR_TLS_CLOSE); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + + mock_clean_saved_logs(); + ret = tor_tls_get_error(tls, 0, 2, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, -10); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + + mock_clean_saved_logs(); + ERR_put_error(ERR_LIB_SYS, 2, -1, "somewhere.c", 99); + ret = tor_tls_get_error(tls, -1, 0, "something", LOG_WARN, 0); + tt_int_op(ret, OP_EQ, -9); + tt_int_op(mock_saved_log_number(), OP_EQ, 2); + tt_str_op(mock_saved_log_at(1), OP_EQ, + "TLS error while something: (null) (in system library:" + "connect:before/accept initialization)\n"); + + done: + teardown_capture_of_logs(previous_log); + SSL_free(tls->ssl); + tor_free(tls); + SSL_CTX_free(ctx); +} +#endif + +static void +test_tortls_always_accept_verify_cb(void *ignored) +{ + (void)ignored; + int ret; + + ret = always_accept_verify_cb(0, NULL); + tt_int_op(ret, OP_EQ, 1); + + done: + (void)0; +} + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_x509_cert_free(void *ignored) +{ + (void)ignored; + tor_x509_cert_t *cert; + + cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + tor_x509_cert_free(cert); + + cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + cert->cert = tor_malloc_zero(sizeof(X509)); + cert->encoded = tor_malloc_zero(1); + tor_x509_cert_free(cert); +} +#endif + +static void +test_tortls_x509_cert_get_id_digests(void *ignored) +{ + (void)ignored; + tor_x509_cert_t *cert; + digests_t *d; + const digests_t *res; + cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + d = tor_malloc_zero(sizeof(digests_t)); + d->d[0][0] = 42; + + res = tor_x509_cert_get_id_digests(cert); + tt_assert(!res); + + cert->pkey_digests_set = 1; + cert->pkey_digests = *d; + res = tor_x509_cert_get_id_digests(cert); + tt_int_op(res->d[0][0], OP_EQ, 42); + + done: + tor_free(cert); + tor_free(d); +} + +#ifndef OPENSSL_OPAQUE +static int +fixed_pub_cmp(const EVP_PKEY *a, const EVP_PKEY *b) +{ + (void) a; (void) b; + return 1; +} + +static void +fake_x509_free(X509 *cert) +{ + if (cert) { + if (cert->cert_info) { + if (cert->cert_info->key) { + if (cert->cert_info->key->pkey) { + tor_free(cert->cert_info->key->pkey); + } + tor_free(cert->cert_info->key); + } + tor_free(cert->cert_info); + } + tor_free(cert); + } +} + +static void +test_tortls_cert_matches_key(void *ignored) +{ + (void)ignored; + int res; + tor_tls_t *tls; + tor_x509_cert_t *cert; + X509 *one = NULL, *two = NULL; + EVP_PKEY_ASN1_METHOD *meth = EVP_PKEY_asn1_new(999, 0, NULL, NULL); + EVP_PKEY_asn1_set_public(meth, NULL, NULL, fixed_pub_cmp, NULL, NULL, NULL); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + one = tor_malloc_zero(sizeof(X509)); + one->references = 1; + two = tor_malloc_zero(sizeof(X509)); + two->references = 1; + + res = tor_tls_cert_matches_key(tls, cert); + tt_int_op(res, OP_EQ, 0); + + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->session = tor_malloc_zero(sizeof(SSL_SESSION)); + tls->ssl->session->peer = one; + res = tor_tls_cert_matches_key(tls, cert); + tt_int_op(res, OP_EQ, 0); + + cert->cert = two; + res = tor_tls_cert_matches_key(tls, cert); + tt_int_op(res, OP_EQ, 0); + + one->cert_info = tor_malloc_zero(sizeof(X509_CINF)); + one->cert_info->key = tor_malloc_zero(sizeof(X509_PUBKEY)); + one->cert_info->key->pkey = tor_malloc_zero(sizeof(EVP_PKEY)); + one->cert_info->key->pkey->references = 1; + one->cert_info->key->pkey->ameth = meth; + one->cert_info->key->pkey->type = 1; + + two->cert_info = tor_malloc_zero(sizeof(X509_CINF)); + two->cert_info->key = tor_malloc_zero(sizeof(X509_PUBKEY)); + two->cert_info->key->pkey = tor_malloc_zero(sizeof(EVP_PKEY)); + two->cert_info->key->pkey->references = 1; + two->cert_info->key->pkey->ameth = meth; + two->cert_info->key->pkey->type = 2; + + res = tor_tls_cert_matches_key(tls, cert); + tt_int_op(res, OP_EQ, 0); + + one->cert_info->key->pkey->type = 1; + two->cert_info->key->pkey->type = 1; + res = tor_tls_cert_matches_key(tls, cert); + tt_int_op(res, OP_EQ, 1); + + done: + EVP_PKEY_asn1_free(meth); + tor_free(tls->ssl->session); + tor_free(tls->ssl); + tor_free(tls); + tor_free(cert); + fake_x509_free(one); + fake_x509_free(two); +} + +static void +test_tortls_cert_get_key(void *ignored) +{ + (void)ignored; + tor_x509_cert_t *cert = NULL; + crypto_pk_t *res = NULL; + cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + X509 *key = NULL; + key = tor_malloc_zero(sizeof(X509)); + key->references = 1; + + res = tor_tls_cert_get_key(cert); + tt_assert(!res); + + cert->cert = key; + key->cert_info = tor_malloc_zero(sizeof(X509_CINF)); + key->cert_info->key = tor_malloc_zero(sizeof(X509_PUBKEY)); + key->cert_info->key->pkey = tor_malloc_zero(sizeof(EVP_PKEY)); + key->cert_info->key->pkey->references = 1; + key->cert_info->key->pkey->type = 2; + res = tor_tls_cert_get_key(cert); + tt_assert(!res); + + done: + fake_x509_free(key); + tor_free(cert); + crypto_pk_free(res); +} +#endif + +static void +test_tortls_get_my_client_auth_key(void *ignored) +{ + (void)ignored; + crypto_pk_t *ret; + crypto_pk_t *expected; + tor_tls_context_t *ctx; + RSA *k = tor_malloc_zero(sizeof(RSA)); + + ctx = tor_malloc_zero(sizeof(tor_tls_context_t)); + expected = crypto_new_pk_from_rsa_(k); + ctx->auth_key = expected; + + client_tls_context = NULL; + ret = tor_tls_get_my_client_auth_key(); + tt_assert(!ret); + + client_tls_context = ctx; + ret = tor_tls_get_my_client_auth_key(); + tt_assert(ret == expected); + + done: + tor_free(expected); + tor_free(k); + tor_free(ctx); +} + +static void +test_tortls_get_my_certs(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_context_t *ctx; + const tor_x509_cert_t *link_cert_out = NULL; + const tor_x509_cert_t *id_cert_out = NULL; + + ctx = tor_malloc_zero(sizeof(tor_tls_context_t)); + + client_tls_context = NULL; + ret = tor_tls_get_my_certs(0, NULL, NULL); + tt_int_op(ret, OP_EQ, -1); + + server_tls_context = NULL; + ret = tor_tls_get_my_certs(1, NULL, NULL); + tt_int_op(ret, OP_EQ, -1); + + client_tls_context = ctx; + ret = tor_tls_get_my_certs(0, NULL, NULL); + tt_int_op(ret, OP_EQ, 0); + + client_tls_context = ctx; + ret = tor_tls_get_my_certs(0, &link_cert_out, &id_cert_out); + tt_int_op(ret, OP_EQ, 0); + + server_tls_context = ctx; + ret = tor_tls_get_my_certs(1, &link_cert_out, &id_cert_out); + tt_int_op(ret, OP_EQ, 0); + + done: + (void)1; +} + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_get_ciphersuite_name(void *ignored) +{ + (void)ignored; + const char *ret; + tor_tls_t *ctx; + ctx = tor_malloc_zero(sizeof(tor_tls_t)); + ctx->ssl = tor_malloc_zero(sizeof(SSL)); + + ret = tor_tls_get_ciphersuite_name(ctx); + tt_str_op(ret, OP_EQ, "(NONE)"); + + done: + tor_free(ctx->ssl); + tor_free(ctx); +} + +static SSL_CIPHER * +get_cipher_by_name(const char *name) +{ + int i; + const SSL_METHOD *method = SSLv23_method(); + int num = method->num_ciphers(); + for (i = 0; i < num; ++i) { + const SSL_CIPHER *cipher = method->get_cipher(i); + const char *ciphername = SSL_CIPHER_get_name(cipher); + if (!strcmp(ciphername, name)) { + return (SSL_CIPHER *)cipher; + } + } + + return NULL; +} + +static SSL_CIPHER * +get_cipher_by_id(uint16_t id) +{ + int i; + const SSL_METHOD *method = SSLv23_method(); + int num = method->num_ciphers(); + for (i = 0; i < num; ++i) { + const SSL_CIPHER *cipher = method->get_cipher(i); + if (id == (SSL_CIPHER_get_id(cipher) & 0xffff)) { + return (SSL_CIPHER *)cipher; + } + } + + return NULL; +} + +extern uint16_t v2_cipher_list[]; + +static void +test_tortls_classify_client_ciphers(void *ignored) +{ + (void)ignored; + int i; + int ret; + SSL_CTX *ctx; + SSL *ssl; + tor_tls_t *tls; + STACK_OF(SSL_CIPHER) *ciphers; + SSL_CIPHER *tmp_cipher; + + SSL_library_init(); + SSL_load_error_strings(); + tor_tls_allocate_tor_tls_object_ex_data_index(); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->magic = TOR_TLS_MAGIC; + + ctx = SSL_CTX_new(TLSv1_method()); + ssl = SSL_new(ctx); + tls->ssl = ssl; + + ciphers = sk_SSL_CIPHER_new_null(); + + ret = tor_tls_classify_client_ciphers(ssl, NULL); + tt_int_op(ret, OP_EQ, -1); + + SSL_set_ex_data(ssl, tor_tls_object_ex_data_index, tls); + tls->client_cipher_list_type = 42; + + ret = tor_tls_classify_client_ciphers(ssl, NULL); + tt_int_op(ret, OP_EQ, 42); + + tls->client_cipher_list_type = 0; + ret = tor_tls_classify_client_ciphers(ssl, ciphers); + tt_int_op(ret, OP_EQ, 1); + tt_int_op(tls->client_cipher_list_type, OP_EQ, 1); + + tls->client_cipher_list_type = 0; + ret = tor_tls_classify_client_ciphers(ssl, SSL_get_ciphers(ssl)); + tt_int_op(ret, OP_EQ, 3); + tt_int_op(tls->client_cipher_list_type, OP_EQ, 3); + + SSL_CIPHER *one = get_cipher_by_name(TLS1_TXT_DHE_RSA_WITH_AES_128_SHA), + *two = get_cipher_by_name(TLS1_TXT_DHE_RSA_WITH_AES_256_SHA), + *three = get_cipher_by_name(SSL3_TXT_EDH_RSA_DES_192_CBC3_SHA), + *four = NULL; + sk_SSL_CIPHER_push(ciphers, one); + sk_SSL_CIPHER_push(ciphers, two); + sk_SSL_CIPHER_push(ciphers, three); + sk_SSL_CIPHER_push(ciphers, four); + + tls->client_cipher_list_type = 0; + ret = tor_tls_classify_client_ciphers(ssl, ciphers); + tt_int_op(ret, OP_EQ, 1); + tt_int_op(tls->client_cipher_list_type, OP_EQ, 1); + + sk_SSL_CIPHER_zero(ciphers); + + one = get_cipher_by_name("ECDH-RSA-AES256-GCM-SHA384"); + one->id = 0x00ff; + two = get_cipher_by_name("ECDH-RSA-AES128-GCM-SHA256"); + two->id = 0x0000; + sk_SSL_CIPHER_push(ciphers, one); + tls->client_cipher_list_type = 0; + ret = tor_tls_classify_client_ciphers(ssl, ciphers); + tt_int_op(ret, OP_EQ, 3); + tt_int_op(tls->client_cipher_list_type, OP_EQ, 3); + + sk_SSL_CIPHER_push(ciphers, two); + tls->client_cipher_list_type = 0; + ret = tor_tls_classify_client_ciphers(ssl, ciphers); + tt_int_op(ret, OP_EQ, 3); + tt_int_op(tls->client_cipher_list_type, OP_EQ, 3); + + one->id = 0xC00A; + tls->client_cipher_list_type = 0; + ret = tor_tls_classify_client_ciphers(ssl, ciphers); + tt_int_op(ret, OP_EQ, 3); + tt_int_op(tls->client_cipher_list_type, OP_EQ, 3); + + sk_SSL_CIPHER_zero(ciphers); + for (i=0; v2_cipher_list[i]; i++) { + tmp_cipher = get_cipher_by_id(v2_cipher_list[i]); + tt_assert(tmp_cipher); + sk_SSL_CIPHER_push(ciphers, tmp_cipher); + } + tls->client_cipher_list_type = 0; + ret = tor_tls_classify_client_ciphers(ssl, ciphers); + tt_int_op(ret, OP_EQ, 2); + tt_int_op(tls->client_cipher_list_type, OP_EQ, 2); + + done: + sk_SSL_CIPHER_free(ciphers); + SSL_free(tls->ssl); + tor_free(tls); + SSL_CTX_free(ctx); +} +#endif + +static void +test_tortls_client_is_using_v2_ciphers(void *ignored) +{ + (void)ignored; + +#ifdef HAVE_SSL_GET_CLIENT_CIPHERS + tt_skip(); + done: + (void)1; +#else + int ret; + SSL_CTX *ctx; + SSL *ssl; + SSL_SESSION *sess; + STACK_OF(SSL_CIPHER) *ciphers; + + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(TLSv1_method()); + ssl = SSL_new(ctx); + sess = SSL_SESSION_new(); + + ret = tor_tls_client_is_using_v2_ciphers(ssl); + tt_int_op(ret, OP_EQ, -1); + + ssl->session = sess; + ret = tor_tls_client_is_using_v2_ciphers(ssl); + tt_int_op(ret, OP_EQ, 0); + + ciphers = sk_SSL_CIPHER_new_null(); + SSL_CIPHER *one = get_cipher_by_name("ECDH-RSA-AES256-GCM-SHA384"); + one->id = 0x00ff; + sk_SSL_CIPHER_push(ciphers, one); + sess->ciphers = ciphers; + ret = tor_tls_client_is_using_v2_ciphers(ssl); + tt_int_op(ret, OP_EQ, 1); + done: + SSL_free(ssl); + SSL_CTX_free(ctx); +#endif +} + +#ifndef OPENSSL_OPAQUE +static X509 *fixed_try_to_extract_certs_from_tls_cert_out_result = NULL; +static X509 *fixed_try_to_extract_certs_from_tls_id_cert_out_result = NULL; + +static void +fixed_try_to_extract_certs_from_tls(int severity, tor_tls_t *tls, + X509 **cert_out, X509 **id_cert_out) +{ + (void) severity; + (void) tls; + *cert_out = fixed_try_to_extract_certs_from_tls_cert_out_result; + *id_cert_out = fixed_try_to_extract_certs_from_tls_id_cert_out_result; +} +#endif + +#ifndef OPENSSL_OPAQUE +static const char* notCompletelyValidCertString = + "-----BEGIN CERTIFICATE-----\n" + "MIICVjCCAb8CAg37MA0GCSqGSIb3DQEBBQUAMIGbMQswCQYDVQQGEwJKUDEOMAwG\n" + "A1UECBMFVG9reW8xEDAOBgNVBAcTB0NodW8ta3UxETAPBgNVBAoTCEZyYW5rNERE\n" + "MRgwFgYDVQQLEw9XZWJDZXJ0IFN1cHBvcnQxGDAWBgNVBAMTD0ZyYW5rNEREIFdl\n" + "YiBDQTEjMCEGCSqGSIb3DQEJARYUc3VwcG9ydEBmcmFuazRkZC5jb20wHhcNMTIw\n" + "ODIyMDUyNzIzWhcNMTcwODIxMDUyNzIzWjBKMQswCQYDVQQGEwJKUDEOMAwGA1UE\n" + "CAwFVG9reW8xETAPBgNVBAoMCEZyYW5rNEREMRgwFgYDVQQDDA93d3cuZXhhbXBs\n" + "ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMYBBrx5PlP0WNI/ZdzD\n" + "+6Pktmurn+F2kQYbtc7XQh8/LTBvCo+P6iZoLEmUA9e7EXLRxgU1CVqeAi7QcAn9\n" + "MwBlc8ksFJHB0rtf9pmf8Oza9E0Bynlq/4/Kb1x+d+AyhL7oK9tQwB24uHOueHi1\n" + "C/iVv8CSWKiYe6hzN1txYe8rAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAASPdjigJ\n" + "kXCqKWpnZ/Oc75EUcMi6HztaW8abUMlYXPIgkV2F7YanHOB7K4f7OOLjiz8DTPFf\n" + "jC9UeuErhaA/zzWi8ewMTFZW/WshOrm3fNvcMrMLKtH534JKvcdMg6qIdjTFINIr\n" + "evnAhf0cwULaebn+lMs8Pdl7y37+sfluVok=\n" + "-----END CERTIFICATE-----\n"; +#endif + +static const char* validCertString = "-----BEGIN CERTIFICATE-----\n" + "MIIDpTCCAY0CAg3+MA0GCSqGSIb3DQEBBQUAMF4xCzAJBgNVBAYTAlVTMREwDwYD\n" + "VQQIDAhJbGxpbm9pczEQMA4GA1UEBwwHQ2hpY2FnbzEUMBIGA1UECgwLVG9yIFRl\n" + "c3RpbmcxFDASBgNVBAMMC1RvciBUZXN0aW5nMB4XDTE1MDkwNjEzMzk1OVoXDTQz\n" + "MDEyMjEzMzk1OVowVjELMAkGA1UEBhMCVVMxEDAOBgNVBAcMB0NoaWNhZ28xFDAS\n" + "BgNVBAoMC1RvciBUZXN0aW5nMR8wHQYDVQQDDBZ0ZXN0aW5nLnRvcnByb2plY3Qu\n" + "b3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoT6uyVVhWyOF3wkHjjYbd\n" + "nKaykyRv4JVtKQdZ4OpEErmX1zw4MmyzpQNV6iR4bQnWiyLfzyVJMZDIC/WILBfX\n" + "w2Pza/yuLgUvDc3twMuhOACzOQVO8PrEF/aVv2+hbCCy2udXvKhnYn+CCXl3ozc8\n" + "XcKYvujTXDyvGWY3xwAjlQIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQCUvnhzQWuQ\n" + "MrN+pERkE+zcTI/9dGS90rUMMLgu8VDNqTa0TUQh8uO0EQ6uDvI8Js6e8tgwS0BR\n" + "UBahqb7ZHv+rejGCBr5OudqD+x4STiiuPNJVs86JTLN8SpM9CHjIBH5WCCN2KOy3\n" + "mevNoRcRRyYJzSFULCunIK6FGulszigMYGscrO4oiTkZiHPh9KvWT40IMiHfL+Lw\n" + "EtEWiLex6064LcA2YQ1AMuSZyCexks63lcfaFmQbkYOKqXa1oLkIRuDsOaSVjTfe\n" + "vec+X6jvf12cFTKS5WIeqkKF2Irt+dJoiHEGTe5RscUMN/f+gqHPzfFz5dR23sxo\n" + "g+HC6MZHlFkLAOx3wW6epPS8A/m1mw3zMPoTnb2U2YYt8T0dJMMlUn/7Y1sEAa+a\n" + "dSTMaeUf6VnJ//11m454EZl1to9Z7oJOgqmFffSrdD4BGIWe8f7hhW6L1Enmqe/J\n" + "BKL3wbzZh80O1W0bndAwhnEEhlzneFY84cbBo9pmVxpODHkUcStpr5Z7pBDrcL21\n" + "Ss/aB/1YrsVXhdvJdOGxl3Mnl9dUY57CympLGlT8f0pPS6GAKOelECOhFMHmJd8L\n" + "dj3XQSmKtYHevZ6IvuMXSlB/fJvSjSlkCuLo5+kJoaqPuRu+i/S1qxeRy3CBwmnE\n" + "LdSNdcX4N79GQJ996PA8+mUCQG7YRtK+WA==\n" + "-----END CERTIFICATE-----\n"; + +static const char* caCertString = "-----BEGIN CERTIFICATE-----\n" + "MIIFjzCCA3egAwIBAgIJAKd5WgyfPMYRMA0GCSqGSIb3DQEBCwUAMF4xCzAJBgNV\n" + "BAYTAlVTMREwDwYDVQQIDAhJbGxpbm9pczEQMA4GA1UEBwwHQ2hpY2FnbzEUMBIG\n" + "A1UECgwLVG9yIFRlc3RpbmcxFDASBgNVBAMMC1RvciBUZXN0aW5nMB4XDTE1MDkw\n" + "NjEzMzc0MVoXDTQzMDEyMjEzMzc0MVowXjELMAkGA1UEBhMCVVMxETAPBgNVBAgM\n" + "CElsbGlub2lzMRAwDgYDVQQHDAdDaGljYWdvMRQwEgYDVQQKDAtUb3IgVGVzdGlu\n" + "ZzEUMBIGA1UEAwwLVG9yIFRlc3RpbmcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw\n" + "ggIKAoICAQCpLMUEiLW5leUgBZoEJms2V7lZRhIAjnJBhVMHD0e3UubNknmaQoxf\n" + "ARz3rvqOaRd0JlV+qM9qE0DjiYcCVP1cAfqAo9d83uS1vwY3YMVJzADlaIiHfyVW\n" + "uEgBy0vvkeUBqaua24dYlcwsemOiXYLu41yM1wkcGHW1AhBNHppY6cznb8TyLgNM\n" + "2x3SGUdzc5XMyAFx51faKGBA3wjs+Hg1PLY7d30nmCgEOBavpm5I1disM/0k+Mcy\n" + "YmAKEo/iHJX/rQzO4b9znP69juLlR8PDBUJEVIG/CYb6+uw8MjjUyiWXYoqfVmN2\n" + "hm/lH8b6rXw1a2Aa3VTeD0DxaWeacMYHY/i01fd5n7hCoDTRNdSw5KJ0L3Z0SKTu\n" + "0lzffKzDaIfyZGlpW5qdouACkWYzsaitQOePVE01PIdO30vUfzNTFDfy42ccx3Di\n" + "59UCu+IXB+eMtrBfsok0Qc63vtF1linJgjHW1z/8ujk8F7/qkOfODhk4l7wngc2A\n" + "EmwWFIFoGaiTEZHB9qteXr4unbXZ0AHpM02uGGwZEGohjFyebEb73M+J57WKKAFb\n" + "PqbLcGUksL1SHNBNAJcVLttX55sO4nbidOS/kA3m+F1R04MBTyQF9qA6YDDHqdI3\n" + "h/3pw0Z4fxVouTYT4/NfRnX4JTP4u+7Mpcoof28VME0qWqD1LnRhFQIDAQABo1Aw\n" + "TjAdBgNVHQ4EFgQUMoAgIXH7pZ3QMRwTjT+DM9Yo/v0wHwYDVR0jBBgwFoAUMoAg\n" + "IXH7pZ3QMRwTjT+DM9Yo/v0wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC\n" + "AgEAUJxacjXR9sT+Xs6ISFiUsyd0T6WVKMnV46xrYJHirGfx+krWHrjxMY+ZtxYD\n" + "DBDGlo11Qc4v6QrclNf5QUBfIiGQsP9Cm6hHcQ+Tpg9HHCgSqG1YNPwCPReCR4br\n" + "BLvLfrfkcBL2IWM0PdQdCze+59DBfipsULD2mEn9fjYRXQEwb2QWtQ9qRc20Yb/x\n" + "Q4b/+CvUodLkaq7B8MHz0BV8HHcBoph6DYaRmO/N+hPauIuSp6XyaGYcEefGKVKj\n" + "G2+fcsdyXsoijNdL8vNKwm4j2gVwCBnw16J00yfFoV46YcbfqEdJB2je0XSvwXqt\n" + "14AOTngxso2h9k9HLtrfpO1ZG/B5AcCMs1lzbZ2fp5DPHtjvvmvA2RJqgo3yjw4W\n" + "4DHAuTglYFlC3mDHNfNtcGP20JvepcQNzNP2UzwcpOc94hfKikOFw+gf9Vf1qd0y\n" + "h/Sk6OZHn2+JVUPiWHIQV98Vtoh4RmUZDJD+b55ia3fQGTGzt4z1XFzQYSva5sfs\n" + "wocS/papthqWldQU7x+3wofNd5CNU1x6WKXG/yw30IT/4F8ADJD6GeygNT8QJYvt\n" + "u/8lAkbOy6B9xGmSvr0Kk1oq9P2NshA6kalxp1Oz/DTNDdL4AeBXV3JmM6WWCjGn\n" + "Yy1RT69d0rwYc5u/vnqODz1IjvT90smsrkBumGt791FAFeg=\n" + "-----END CERTIFICATE-----\n"; + +static X509 * +read_cert_from(const char *str) +{ + BIO *bio = BIO_new(BIO_s_mem()); + BIO_write(bio, str, (int) strlen(str)); + X509 *res = PEM_read_bio_X509(bio, NULL, NULL, NULL); + BIO_free(bio); + return res; +} + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_verify(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + crypto_pk_t *k = NULL; + X509 *cert1 = NULL, *cert2 = NULL, *invalidCert = NULL, + *validCert = NULL, *caCert = NULL; + + cert1 = tor_malloc_zero(sizeof(X509)); + cert1->references = 10; + + cert2 = tor_malloc_zero(sizeof(X509)); + cert2->references = 10; + + validCert = read_cert_from(validCertString); + caCert = read_cert_from(caCertString); + invalidCert = read_cert_from(notCompletelyValidCertString); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + ret = tor_tls_verify(LOG_WARN, tls, &k); + tt_int_op(ret, OP_EQ, -1); + + MOCK(try_to_extract_certs_from_tls, fixed_try_to_extract_certs_from_tls); + + fixed_try_to_extract_certs_from_tls_cert_out_result = cert1; + ret = tor_tls_verify(LOG_WARN, tls, &k); + tt_int_op(ret, OP_EQ, -1); + + fixed_try_to_extract_certs_from_tls_id_cert_out_result = cert2; + ret = tor_tls_verify(LOG_WARN, tls, &k); + tt_int_op(ret, OP_EQ, -1); + + fixed_try_to_extract_certs_from_tls_cert_out_result = invalidCert; + fixed_try_to_extract_certs_from_tls_id_cert_out_result = invalidCert; + + ret = tor_tls_verify(LOG_WARN, tls, &k); + tt_int_op(ret, OP_EQ, -1); + + fixed_try_to_extract_certs_from_tls_cert_out_result = validCert; + fixed_try_to_extract_certs_from_tls_id_cert_out_result = caCert; + + ret = tor_tls_verify(LOG_WARN, tls, &k); + tt_int_op(ret, OP_EQ, 0); + tt_assert(k); + + done: + UNMOCK(try_to_extract_certs_from_tls); + tor_free(cert1); + tor_free(cert2); + tor_free(tls); + tor_free(k); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_check_lifetime(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + X509 *validCert = read_cert_from(validCertString); + time_t now = time(NULL); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + ret = tor_tls_check_lifetime(LOG_WARN, tls, 0, 0); + tt_int_op(ret, OP_EQ, -1); + + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->session = tor_malloc_zero(sizeof(SSL_SESSION)); + tls->ssl->session->peer = validCert; + ret = tor_tls_check_lifetime(LOG_WARN, tls, 0, 0); + tt_int_op(ret, OP_EQ, 0); + + ASN1_STRING_free(validCert->cert_info->validity->notBefore); + validCert->cert_info->validity->notBefore = ASN1_TIME_set(NULL, now-10); + ASN1_STRING_free(validCert->cert_info->validity->notAfter); + validCert->cert_info->validity->notAfter = ASN1_TIME_set(NULL, now+60); + + ret = tor_tls_check_lifetime(LOG_WARN, tls, 0, -1000); + tt_int_op(ret, OP_EQ, -1); + + ret = tor_tls_check_lifetime(LOG_WARN, tls, -1000, 0); + tt_int_op(ret, OP_EQ, -1); + + done: + tor_free(tls->ssl->session); + tor_free(tls->ssl); + tor_free(tls); + X509_free(validCert); +} +#endif + +#ifndef OPENSSL_OPAQUE +static int fixed_ssl_pending_result = 0; + +static int +fixed_ssl_pending(const SSL *ignored) +{ + (void)ignored; + return fixed_ssl_pending_result; +} + +static void +test_tortls_get_pending_bytes(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + SSL_METHOD *method; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + method = tor_malloc_zero(sizeof(SSL_METHOD)); + method->ssl_pending = fixed_ssl_pending; + tls->ssl->method = method; + + fixed_ssl_pending_result = 42; + ret = tor_tls_get_pending_bytes(tls); + tt_int_op(ret, OP_EQ, 42); + + done: + tor_free(method); + tor_free(tls->ssl); + tor_free(tls); +} +#endif + +static void +test_tortls_get_forced_write_size(void *ignored) +{ + (void)ignored; + long ret; + tor_tls_t *tls; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + + tls->wantwrite_n = 43; + ret = tor_tls_get_forced_write_size(tls); + tt_int_op(ret, OP_EQ, 43); + + done: + tor_free(tls); +} + +extern uint64_t total_bytes_written_over_tls; +extern uint64_t total_bytes_written_by_tls; + +static void +test_tortls_get_write_overhead_ratio(void *ignored) +{ + (void)ignored; + double ret; + + total_bytes_written_over_tls = 0; + ret = tls_get_write_overhead_ratio(); + tt_int_op(ret, OP_EQ, 1.0); + + total_bytes_written_by_tls = 10; + total_bytes_written_over_tls = 1; + ret = tls_get_write_overhead_ratio(); + tt_int_op(ret, OP_EQ, 10.0); + + total_bytes_written_by_tls = 10; + total_bytes_written_over_tls = 2; + ret = tls_get_write_overhead_ratio(); + tt_int_op(ret, OP_EQ, 5.0); + + done: + (void)0; +} + +static void +test_tortls_used_v1_handshake(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + tls = tor_malloc_zero(sizeof(tor_tls_t)); + + // These tests assume both V2 handshake server and client are enabled + tls->wasV2Handshake = 0; + ret = tor_tls_used_v1_handshake(tls); + tt_int_op(ret, OP_EQ, 1); + + tls->wasV2Handshake = 1; + ret = tor_tls_used_v1_handshake(tls); + tt_int_op(ret, OP_EQ, 0); + + done: + tor_free(tls); +} + +static void +test_tortls_get_num_server_handshakes(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + + tls->server_handshake_count = 3; + ret = tor_tls_get_num_server_handshakes(tls); + tt_int_op(ret, OP_EQ, 3); + + done: + tor_free(tls); +} + +static void +test_tortls_server_got_renegotiate(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + + tls->got_renegotiate = 1; + ret = tor_tls_server_got_renegotiate(tls); + tt_int_op(ret, OP_EQ, 1); + + done: + tor_free(tls); +} + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_SSL_SESSION_get_master_key(void *ignored) +{ + (void)ignored; + size_t ret; + tor_tls_t *tls; + uint8_t *out; + out = tor_malloc_zero(1); + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->session = tor_malloc_zero(sizeof(SSL_SESSION)); + tls->ssl->session->master_key_length = 1; + +#ifndef HAVE_SSL_SESSION_GET_MASTER_KEY + tls->ssl->session->master_key[0] = 43; + ret = SSL_SESSION_get_master_key(tls->ssl->session, out, 0); + tt_int_op(ret, OP_EQ, 1); + tt_int_op(out[0], OP_EQ, 0); + + ret = SSL_SESSION_get_master_key(tls->ssl->session, out, 1); + tt_int_op(ret, OP_EQ, 1); + tt_int_op(out[0], OP_EQ, 43); + + done: +#endif + tor_free(tls->ssl->session); + tor_free(tls->ssl); + tor_free(tls); + tor_free(out); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_get_tlssecrets(void *ignored) +{ + (void)ignored; + int ret; + uint8_t *secret_out = tor_malloc_zero(DIGEST256_LEN);; + tor_tls_t *tls; + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->session = tor_malloc_zero(sizeof(SSL_SESSION)); + tls->ssl->session->master_key_length = 1; + tls->ssl->s3 = tor_malloc_zero(sizeof(SSL3_STATE)); + + ret = tor_tls_get_tlssecrets(tls, secret_out); + tt_int_op(ret, OP_EQ, 0); + + done: + tor_free(secret_out); + tor_free(tls->ssl->s3); + tor_free(tls->ssl->session); + tor_free(tls->ssl); + tor_free(tls); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_get_buffer_sizes(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + size_t rbuf_c=-1, rbuf_b=-1, wbuf_c=-1, wbuf_b=-1; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->s3 = tor_malloc_zero(sizeof(SSL3_STATE)); + + tls->ssl->s3->rbuf.buf = NULL; + tls->ssl->s3->rbuf.len = 1; + tls->ssl->s3->rbuf.offset = 0; + tls->ssl->s3->rbuf.left = 42; + + tls->ssl->s3->wbuf.buf = NULL; + tls->ssl->s3->wbuf.len = 2; + tls->ssl->s3->wbuf.offset = 0; + tls->ssl->s3->wbuf.left = 43; + +#if OPENSSL_VERSION_NUMBER >= OPENSSL_V_SERIES(1,1,0) + ret = tor_tls_get_buffer_sizes(NULL, NULL, NULL, NULL, NULL); + tt_int_op(ret, OP_EQ, -1); +#else + ret = tor_tls_get_buffer_sizes(tls, &rbuf_c, &rbuf_b, &wbuf_c, &wbuf_b); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(rbuf_c, OP_EQ, 0); + tt_int_op(wbuf_c, OP_EQ, 0); + tt_int_op(rbuf_b, OP_EQ, 42); + tt_int_op(wbuf_b, OP_EQ, 43); + + tls->ssl->s3->rbuf.buf = tor_malloc_zero(1); + tls->ssl->s3->wbuf.buf = tor_malloc_zero(1); + ret = tor_tls_get_buffer_sizes(tls, &rbuf_c, &rbuf_b, &wbuf_c, &wbuf_b); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(rbuf_c, OP_EQ, 1); + tt_int_op(wbuf_c, OP_EQ, 2); + +#endif + + done: + tor_free(tls->ssl->s3->rbuf.buf); + tor_free(tls->ssl->s3->wbuf.buf); + tor_free(tls->ssl->s3); + tor_free(tls->ssl); + tor_free(tls); +} +#endif + +static void +test_tortls_evaluate_ecgroup_for_tls(void *ignored) +{ + (void)ignored; + int ret; + + ret = evaluate_ecgroup_for_tls(NULL); + tt_int_op(ret, OP_EQ, 1); + + ret = evaluate_ecgroup_for_tls("foobar"); + tt_int_op(ret, OP_EQ, 0); + + ret = evaluate_ecgroup_for_tls("P256"); + tt_int_op(ret, OP_EQ, 1); + + ret = evaluate_ecgroup_for_tls("P224"); + // tt_int_op(ret, OP_EQ, 1); This varies between machines + + done: + (void)0; +} + +#ifndef OPENSSL_OPAQUE +typedef struct cert_pkey_st_local +{ + X509 *x509; + EVP_PKEY *privatekey; + const EVP_MD *digest; +} CERT_PKEY_local; + +typedef struct sess_cert_st_local +{ + STACK_OF(X509) *cert_chain; + int peer_cert_type; + CERT_PKEY_local *peer_key; + CERT_PKEY_local peer_pkeys[8]; + int references; +} SESS_CERT_local; + +static void +test_tortls_try_to_extract_certs_from_tls(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + X509 *cert = NULL, *id_cert = NULL, *c1 = NULL, *c2 = NULL; + SESS_CERT_local *sess = NULL; + + c1 = read_cert_from(validCertString); + c2 = read_cert_from(caCertString); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->session = tor_malloc_zero(sizeof(SSL_SESSION)); + sess = tor_malloc_zero(sizeof(SESS_CERT_local)); + tls->ssl->session->sess_cert = (void *)sess; + + try_to_extract_certs_from_tls(LOG_WARN, tls, &cert, &id_cert); + tt_assert(!cert); + tt_assert(!id_cert); + + tls->ssl->session->peer = c1; + try_to_extract_certs_from_tls(LOG_WARN, tls, &cert, &id_cert); + tt_assert(cert == c1); + tt_assert(!id_cert); + X509_free(cert); /* decrease refcnt */ + + sess->cert_chain = sk_X509_new_null(); + try_to_extract_certs_from_tls(LOG_WARN, tls, &cert, &id_cert); + tt_assert(cert == c1); + tt_assert(!id_cert); + X509_free(cert); /* decrease refcnt */ + + sk_X509_push(sess->cert_chain, c1); + sk_X509_push(sess->cert_chain, c2); + + try_to_extract_certs_from_tls(LOG_WARN, tls, &cert, &id_cert); + tt_assert(cert == c1); + tt_assert(id_cert); + X509_free(cert); /* decrease refcnt */ + + done: + sk_X509_free(sess->cert_chain); + tor_free(sess); + tor_free(tls->ssl->session); + tor_free(tls->ssl); + tor_free(tls); + X509_free(c1); + X509_free(c2); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_get_peer_cert(void *ignored) +{ + (void)ignored; + tor_x509_cert_t *ret; + tor_tls_t *tls; + X509 *cert = NULL; + + cert = read_cert_from(validCertString); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->session = tor_malloc_zero(sizeof(SSL_SESSION)); + + ret = tor_tls_get_peer_cert(tls); + tt_assert(!ret); + + tls->ssl->session->peer = cert; + ret = tor_tls_get_peer_cert(tls); + tt_assert(ret); + tt_assert(ret->cert == cert); + + done: + tor_x509_cert_free(ret); + tor_free(tls->ssl->session); + tor_free(tls->ssl); + tor_free(tls); + X509_free(cert); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_peer_has_cert(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + X509 *cert = NULL; + + cert = read_cert_from(validCertString); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->session = tor_malloc_zero(sizeof(SSL_SESSION)); + + ret = tor_tls_peer_has_cert(tls); + tt_assert(!ret); + + tls->ssl->session->peer = cert; + ret = tor_tls_peer_has_cert(tls); + tt_assert(ret); + + done: + tor_free(tls->ssl->session); + tor_free(tls->ssl); + tor_free(tls); + X509_free(cert); +} +#endif + +static void +test_tortls_is_server(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + int ret; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->isServer = 1; + ret = tor_tls_is_server(tls); + tt_int_op(ret, OP_EQ, 1); + + done: + tor_free(tls); +} + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_session_secret_cb(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + SSL_CTX *ctx; + STACK_OF(SSL_CIPHER) *ciphers = NULL; + SSL_CIPHER *one; + + SSL_library_init(); + SSL_load_error_strings(); + tor_tls_allocate_tor_tls_object_ex_data_index(); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + + tls->magic = TOR_TLS_MAGIC; + + ctx = SSL_CTX_new(TLSv1_method()); + tls->ssl = SSL_new(ctx); + SSL_set_ex_data(tls->ssl, tor_tls_object_ex_data_index, tls); + + SSL_set_session_secret_cb(tls->ssl, tor_tls_session_secret_cb, NULL); + + tor_tls_session_secret_cb(tls->ssl, NULL, NULL, NULL, NULL, NULL); + tt_assert(!tls->ssl->tls_session_secret_cb); + + one = get_cipher_by_name("ECDH-RSA-AES256-GCM-SHA384"); + one->id = 0x00ff; + ciphers = sk_SSL_CIPHER_new_null(); + sk_SSL_CIPHER_push(ciphers, one); + + tls->client_cipher_list_type = 0; + tor_tls_session_secret_cb(tls->ssl, NULL, NULL, ciphers, NULL, NULL); + tt_assert(!tls->ssl->tls_session_secret_cb); + + done: + sk_SSL_CIPHER_free(ciphers); + SSL_free(tls->ssl); + SSL_CTX_free(ctx); + tor_free(tls); +} +#endif + +#ifndef OPENSSL_OPAQUE +/* TODO: It seems block_renegotiation and unblock_renegotiation and + * using different blags. This might not be correct */ +static void +test_tortls_block_renegotiation(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->s3 = tor_malloc_zero(sizeof(SSL3_STATE)); + tls->ssl->s3->flags = 0x0010; + + tor_tls_block_renegotiation(tls); + + tt_assert(!(SSL_get_options(tls->ssl) & 0x0010)); + + done: + tor_free(tls->ssl->s3); + tor_free(tls->ssl); + tor_free(tls); +} + +static void +test_tortls_unblock_renegotiation(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tor_tls_unblock_renegotiation(tls); + + tt_assert(SSL_get_options(tls->ssl) & 0x00040000L); + + done: + tor_free(tls->ssl); + tor_free(tls); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_assert_renegotiation_unblocked(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tor_tls_unblock_renegotiation(tls); + tor_tls_assert_renegotiation_unblocked(tls); + /* No assertion here - this test will fail if tor_assert is turned on + * and things are bad. */ + + tor_free(tls->ssl); + tor_free(tls); +} +#endif + +static void +test_tortls_set_logged_address(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + + tor_tls_set_logged_address(tls, "foo bar"); + + tt_str_op(tls->address, OP_EQ, "foo bar"); + + tor_tls_set_logged_address(tls, "foo bar 2"); + tt_str_op(tls->address, OP_EQ, "foo bar 2"); + + done: + tor_free(tls->address); + tor_free(tls); +} + +#ifndef OPENSSL_OPAQUE +static void +example_cb(tor_tls_t *t, void *arg) +{ + (void)t; + (void)arg; +} + +static void +test_tortls_set_renegotiate_callback(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + const char *arg = "hello"; + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + + tor_tls_set_renegotiate_callback(tls, example_cb, (void*)arg); + tt_assert(tls->negotiated_callback == example_cb); + tt_assert(tls->callback_arg == arg); + tt_assert(!tls->got_renegotiate); + + /* Assumes V2_HANDSHAKE_SERVER */ + tt_assert(tls->ssl->info_callback == tor_tls_server_info_callback); + + tor_tls_set_renegotiate_callback(tls, NULL, (void*)arg); + tt_assert(tls->ssl->info_callback == tor_tls_debug_state_callback); + + done: + tor_free(tls->ssl); + tor_free(tls); +} +#endif + +#ifndef OPENSSL_OPAQUE +static SSL_CIPHER *fixed_cipher1 = NULL; +static SSL_CIPHER *fixed_cipher2 = NULL; +static const SSL_CIPHER * +fake_get_cipher(unsigned ncipher) +{ + + switch (ncipher) { + case 1: + return fixed_cipher1; + case 2: + return fixed_cipher2; + default: + return NULL; + } +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_find_cipher_by_id(void *ignored) +{ + (void)ignored; + int ret; + SSL *ssl; + SSL_CTX *ctx; + const SSL_METHOD *m = TLSv1_method(); + SSL_METHOD *empty_method = tor_malloc_zero(sizeof(SSL_METHOD)); + + fixed_cipher1 = tor_malloc_zero(sizeof(SSL_CIPHER)); + fixed_cipher2 = tor_malloc_zero(sizeof(SSL_CIPHER)); + fixed_cipher2->id = 0xC00A; + + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(m); + ssl = SSL_new(ctx); + + ret = find_cipher_by_id(ssl, NULL, 0xC00A); + tt_int_op(ret, OP_EQ, 1); + + ret = find_cipher_by_id(ssl, m, 0xC00A); + tt_int_op(ret, OP_EQ, 1); + + ret = find_cipher_by_id(ssl, m, 0xFFFF); + tt_int_op(ret, OP_EQ, 0); + + ret = find_cipher_by_id(ssl, empty_method, 0xC00A); + tt_int_op(ret, OP_EQ, 1); + + ret = find_cipher_by_id(ssl, empty_method, 0xFFFF); +#ifdef HAVE_SSL_CIPHER_FIND + tt_int_op(ret, OP_EQ, 0); +#else + tt_int_op(ret, OP_EQ, 1); +#endif + + empty_method->get_cipher = fake_get_cipher; + ret = find_cipher_by_id(ssl, empty_method, 0xC00A); + tt_int_op(ret, OP_EQ, 1); + + empty_method->get_cipher = m->get_cipher; + empty_method->num_ciphers = m->num_ciphers; + ret = find_cipher_by_id(ssl, empty_method, 0xC00A); + tt_int_op(ret, OP_EQ, 1); + + empty_method->get_cipher = fake_get_cipher; + empty_method->num_ciphers = m->num_ciphers; + ret = find_cipher_by_id(ssl, empty_method, 0xC00A); + tt_int_op(ret, OP_EQ, 1); + + empty_method->num_ciphers = fake_num_ciphers; + ret = find_cipher_by_id(ssl, empty_method, 0xC00A); +#ifdef HAVE_SSL_CIPHER_FIND + tt_int_op(ret, OP_EQ, 1); +#else + tt_int_op(ret, OP_EQ, 0); +#endif + + done: + tor_free(empty_method); + SSL_free(ssl); + SSL_CTX_free(ctx); + tor_free(fixed_cipher1); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_debug_state_callback(void *ignored) +{ + (void)ignored; + SSL *ssl; + char *buf = tor_malloc_zero(1000); + int n; + + int previous_log = setup_capture_of_logs(LOG_DEBUG); + + ssl = tor_malloc_zero(sizeof(SSL)); + + tor_tls_debug_state_callback(ssl, 32, 45); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + n = snprintf(buf, 1000, "SSL %p is now in state unknown" + " state [type=32,val=45].\n", ssl); + buf[n]='\0'; + if (strcasecmp(mock_saved_log_at(0), buf)) + tt_str_op(mock_saved_log_at(0), OP_EQ, buf); + + done: + teardown_capture_of_logs(previous_log); + tor_free(buf); + tor_free(ssl); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_server_info_callback(void *ignored) +{ + (void)ignored; + tor_tls_t *tls; + SSL_CTX *ctx; + SSL *ssl; + int previous_log = setup_capture_of_logs(LOG_WARN); + + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(TLSv1_method()); + ssl = SSL_new(ctx); + + tor_tls_allocate_tor_tls_object_ex_data_index(); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->magic = TOR_TLS_MAGIC; + tls->ssl = ssl; + + tor_tls_server_info_callback(NULL, 0, 0); + + SSL_set_state(ssl, SSL3_ST_SW_SRVR_HELLO_A); + mock_clean_saved_logs(); + tor_tls_server_info_callback(ssl, SSL_CB_ACCEPT_LOOP, 0); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Couldn't look up the tls for an SSL*. How odd!\n"); + + SSL_set_state(ssl, SSL3_ST_SW_SRVR_HELLO_B); + mock_clean_saved_logs(); + tor_tls_server_info_callback(ssl, SSL_CB_ACCEPT_LOOP, 0); + tt_int_op(mock_saved_log_number(), OP_EQ, 1); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Couldn't look up the tls for an SSL*. How odd!\n"); + + SSL_set_state(ssl, 99); + mock_clean_saved_logs(); + tor_tls_server_info_callback(ssl, SSL_CB_ACCEPT_LOOP, 0); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + + SSL_set_ex_data(tls->ssl, tor_tls_object_ex_data_index, tls); + SSL_set_state(ssl, SSL3_ST_SW_SRVR_HELLO_B); + tls->negotiated_callback = 0; + tls->server_handshake_count = 120; + tor_tls_server_info_callback(ssl, SSL_CB_ACCEPT_LOOP, 0); + tt_int_op(tls->server_handshake_count, OP_EQ, 121); + + tls->server_handshake_count = 127; + tls->negotiated_callback = (void *)1; + tor_tls_server_info_callback(ssl, SSL_CB_ACCEPT_LOOP, 0); + tt_int_op(tls->server_handshake_count, OP_EQ, 127); + tt_int_op(tls->got_renegotiate, OP_EQ, 1); + + tls->ssl->session = SSL_SESSION_new(); + tls->wasV2Handshake = 0; + tor_tls_server_info_callback(ssl, SSL_CB_ACCEPT_LOOP, 0); + tt_int_op(tls->wasV2Handshake, OP_EQ, 0); + + done: + teardown_capture_of_logs(previous_log); + SSL_free(ssl); + SSL_CTX_free(ctx); + tor_free(tls); +} +#endif + +#ifndef OPENSSL_OPAQUE +static int fixed_ssl_read_result_index; +static int fixed_ssl_read_result[5]; +static int fixed_ssl_shutdown_result; + +static int +fixed_ssl_read(SSL *s, void *buf, int len) +{ + (void)s; + (void)buf; + (void)len; + return fixed_ssl_read_result[fixed_ssl_read_result_index++]; +} + +static int +fixed_ssl_shutdown(SSL *s) +{ + (void)s; + return fixed_ssl_shutdown_result; +} + +static int fixed_ssl_state_to_set; +static tor_tls_t *fixed_tls; + +static int +setting_version_ssl_shutdown(SSL *s) +{ + s->version = SSL2_VERSION; + return fixed_ssl_shutdown_result; +} + +static int +setting_version_and_state_ssl_shutdown(SSL *s) +{ + fixed_tls->state = fixed_ssl_state_to_set; + s->version = SSL2_VERSION; + return fixed_ssl_shutdown_result; +} + +static int +dummy_handshake_func(SSL *s) +{ + (void)s; + return 1; +} + +static void +test_tortls_shutdown(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + SSL_METHOD *method = give_me_a_test_method(); + int previous_log = setup_capture_of_logs(LOG_WARN); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->ssl->method = method; + method->ssl_read = fixed_ssl_read; + method->ssl_shutdown = fixed_ssl_shutdown; + + ret = tor_tls_shutdown(tls); + tt_int_op(ret, OP_EQ, -9); + + tls->state = TOR_TLS_ST_SENTCLOSE; + fixed_ssl_read_result_index = 0; + fixed_ssl_read_result[0] = 10; + fixed_ssl_read_result[1] = -1; + ret = tor_tls_shutdown(tls); + tt_int_op(ret, OP_EQ, -9); + + tls->ssl->handshake_func = dummy_handshake_func; + + fixed_ssl_read_result_index = 0; + fixed_ssl_read_result[0] = 10; + fixed_ssl_read_result[1] = 42; + fixed_ssl_read_result[2] = 0; + fixed_ssl_shutdown_result = 1; + ERR_clear_error(); + tls->ssl->version = SSL2_VERSION; + ret = tor_tls_shutdown(tls); + tt_int_op(ret, OP_EQ, TOR_TLS_DONE); + tt_int_op(tls->state, OP_EQ, TOR_TLS_ST_CLOSED); + + fixed_ssl_read_result_index = 0; + fixed_ssl_read_result[0] = 10; + fixed_ssl_read_result[1] = 42; + fixed_ssl_read_result[2] = 0; + fixed_ssl_shutdown_result = 0; + ERR_clear_error(); + tls->ssl->version = 0; + ret = tor_tls_shutdown(tls); + tt_int_op(ret, OP_EQ, TOR_TLS_DONE); + tt_int_op(tls->state, OP_EQ, TOR_TLS_ST_CLOSED); + + fixed_ssl_read_result_index = 0; + fixed_ssl_read_result[0] = 10; + fixed_ssl_read_result[1] = 42; + fixed_ssl_read_result[2] = 0; + fixed_ssl_shutdown_result = 0; + ERR_clear_error(); + tls->ssl->version = 0; + method->ssl_shutdown = setting_version_ssl_shutdown; + ret = tor_tls_shutdown(tls); + tt_int_op(ret, OP_EQ, TOR_TLS_ERROR_MISC); + + fixed_ssl_read_result_index = 0; + fixed_ssl_read_result[0] = 10; + fixed_ssl_read_result[1] = 42; + fixed_ssl_read_result[2] = 0; + fixed_ssl_shutdown_result = 0; + fixed_tls = tls; + fixed_ssl_state_to_set = TOR_TLS_ST_GOTCLOSE; + ERR_clear_error(); + tls->ssl->version = 0; + method->ssl_shutdown = setting_version_and_state_ssl_shutdown; + ret = tor_tls_shutdown(tls); + tt_int_op(ret, OP_EQ, TOR_TLS_ERROR_MISC); + + fixed_ssl_read_result_index = 0; + fixed_ssl_read_result[0] = 10; + fixed_ssl_read_result[1] = 42; + fixed_ssl_read_result[2] = 0; + fixed_ssl_read_result[3] = -1; + fixed_ssl_shutdown_result = 0; + fixed_tls = tls; + fixed_ssl_state_to_set = 0; + ERR_clear_error(); + tls->ssl->version = 0; + method->ssl_shutdown = setting_version_and_state_ssl_shutdown; + ret = tor_tls_shutdown(tls); + tt_int_op(ret, OP_EQ, TOR_TLS_ERROR_MISC); + + done: + teardown_capture_of_logs(previous_log); + tor_free(method); + tor_free(tls->ssl); + tor_free(tls); +} + +static int negotiated_callback_called; + +static void +negotiated_callback_setter(tor_tls_t *t, void *arg) +{ + (void)t; + (void)arg; + negotiated_callback_called++; +} + +static void +test_tortls_read(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + char buf[100]; + SSL_METHOD *method = give_me_a_test_method(); + int previous_log = setup_capture_of_logs(LOG_WARN); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->state = TOR_TLS_ST_OPEN; + + ret = tor_tls_read(tls, buf, 10); + tt_int_op(ret, OP_EQ, -9); + + /* These tests assume that V2_HANDSHAKE_SERVER is set */ + tls->ssl->handshake_func = dummy_handshake_func; + tls->ssl->method = method; + method->ssl_read = fixed_ssl_read; + fixed_ssl_read_result_index = 0; + fixed_ssl_read_result[0] = 42; + tls->state = TOR_TLS_ST_OPEN; + ERR_clear_error(); + ret = tor_tls_read(tls, buf, 10); + tt_int_op(ret, OP_EQ, 42); + + tls->state = TOR_TLS_ST_OPEN; + tls->got_renegotiate = 1; + fixed_ssl_read_result_index = 0; + ERR_clear_error(); + ret = tor_tls_read(tls, buf, 10); + tt_int_op(tls->got_renegotiate, OP_EQ, 0); + + tls->state = TOR_TLS_ST_OPEN; + tls->got_renegotiate = 1; + negotiated_callback_called = 0; + tls->negotiated_callback = negotiated_callback_setter; + fixed_ssl_read_result_index = 0; + ERR_clear_error(); + ret = tor_tls_read(tls, buf, 10); + tt_int_op(negotiated_callback_called, OP_EQ, 1); + + fixed_ssl_read_result_index = 0; + fixed_ssl_read_result[0] = 0; + tls->ssl->version = SSL2_VERSION; + ERR_clear_error(); + ret = tor_tls_read(tls, buf, 10); + tt_int_op(ret, OP_EQ, TOR_TLS_CLOSE); + tt_int_op(tls->state, OP_EQ, TOR_TLS_ST_CLOSED); + + // TODO: fill up + + done: + teardown_capture_of_logs(previous_log); + tor_free(tls->ssl); + tor_free(tls); + tor_free(method); +} + +static int fixed_ssl_write_result; + +static int +fixed_ssl_write(SSL *s, const void *buf, int len) +{ + (void)s; + (void)buf; + (void)len; + return fixed_ssl_write_result; +} + +static void +test_tortls_write(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + SSL_METHOD *method = give_me_a_test_method(); + char buf[100]; + int previous_log = setup_capture_of_logs(LOG_WARN); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = tor_malloc_zero(sizeof(SSL)); + tls->state = TOR_TLS_ST_OPEN; + + ret = tor_tls_write(tls, buf, 0); + tt_int_op(ret, OP_EQ, 0); + + ret = tor_tls_write(tls, buf, 10); + tt_int_op(ret, OP_EQ, -9); + + tls->ssl->method = method; + tls->wantwrite_n = 1; + ret = tor_tls_write(tls, buf, 10); + tt_int_op(tls->wantwrite_n, OP_EQ, 0); + + method->ssl_write = fixed_ssl_write; + tls->ssl->handshake_func = dummy_handshake_func; + fixed_ssl_write_result = 1; + ERR_clear_error(); + ret = tor_tls_write(tls, buf, 10); + tt_int_op(ret, OP_EQ, 1); + + fixed_ssl_write_result = -1; + ERR_clear_error(); + tls->ssl->rwstate = SSL_READING; + SSL_set_bio(tls->ssl, BIO_new(BIO_s_mem()), NULL); + SSL_get_rbio(tls->ssl)->flags = BIO_FLAGS_READ; + ret = tor_tls_write(tls, buf, 10); + tt_int_op(ret, OP_EQ, TOR_TLS_WANTREAD); + + ERR_clear_error(); + tls->ssl->rwstate = SSL_READING; + SSL_set_bio(tls->ssl, BIO_new(BIO_s_mem()), NULL); + SSL_get_rbio(tls->ssl)->flags = BIO_FLAGS_WRITE; + ret = tor_tls_write(tls, buf, 10); + tt_int_op(ret, OP_EQ, TOR_TLS_WANTWRITE); + + done: + teardown_capture_of_logs(previous_log); + BIO_free(tls->ssl->rbio); + tor_free(tls->ssl); + tor_free(tls); + tor_free(method); +} +#endif + +#ifndef OPENSSL_OPAQUE +static int fixed_ssl_accept_result; +static int fixed_ssl_connect_result; + +static int +setting_error_ssl_accept(SSL *ssl) +{ + (void)ssl; + ERR_put_error(ERR_LIB_BN, 2, -1, "somewhere.c", 99); + ERR_put_error(ERR_LIB_SYS, 2, -1, "somewhere.c", 99); + return fixed_ssl_accept_result; +} + +static int +setting_error_ssl_connect(SSL *ssl) +{ + (void)ssl; + ERR_put_error(ERR_LIB_BN, 2, -1, "somewhere.c", 99); + ERR_put_error(ERR_LIB_SYS, 2, -1, "somewhere.c", 99); + return fixed_ssl_connect_result; +} + +static int +fixed_ssl_accept(SSL *ssl) +{ + (void) ssl; + return fixed_ssl_accept_result; +} + +static void +test_tortls_handshake(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + SSL_CTX *ctx; + SSL_METHOD *method = give_me_a_test_method(); + int previous_log = setup_capture_of_logs(LOG_INFO); + + SSL_library_init(); + SSL_load_error_strings(); + + ctx = SSL_CTX_new(TLSv1_method()); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = SSL_new(ctx); + tls->state = TOR_TLS_ST_HANDSHAKE; + + ret = tor_tls_handshake(tls); + tt_int_op(ret, OP_EQ, -9); + + tls->isServer = 1; + tls->state = TOR_TLS_ST_HANDSHAKE; + ret = tor_tls_handshake(tls); + tt_int_op(ret, OP_EQ, -9); + + tls->ssl->method = method; + method->ssl_accept = fixed_ssl_accept; + fixed_ssl_accept_result = 2; + ERR_clear_error(); + tls->state = TOR_TLS_ST_HANDSHAKE; + ret = tor_tls_handshake(tls); + tt_int_op(tls->state, OP_EQ, TOR_TLS_ST_OPEN); + + method->ssl_accept = setting_error_ssl_accept; + fixed_ssl_accept_result = 1; + ERR_clear_error(); + mock_clean_saved_logs(); + tls->state = TOR_TLS_ST_HANDSHAKE; + ret = tor_tls_handshake(tls); + tt_int_op(ret, OP_EQ, TOR_TLS_ERROR_MISC); + tt_int_op(mock_saved_log_number(), OP_EQ, 2); + /* This fails on jessie. Investigate why! */ +#if 0 + tt_str_op(mock_saved_log_at(0), OP_EQ, + "TLS error while handshaking: (null) (in bignum routines:" + "(null):SSLv3 write client hello B)\n"); + tt_str_op(mock_saved_log_at(1), OP_EQ, + "TLS error while handshaking: (null) (in system library:" + "connect:SSLv3 write client hello B)\n"); +#endif + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_INFO); + tt_int_op(mock_saved_severity_at(1), OP_EQ, LOG_INFO); + + tls->isServer = 0; + method->ssl_connect = setting_error_ssl_connect; + fixed_ssl_connect_result = 1; + ERR_clear_error(); + mock_clean_saved_logs(); + tls->state = TOR_TLS_ST_HANDSHAKE; + ret = tor_tls_handshake(tls); + tt_int_op(ret, OP_EQ, TOR_TLS_ERROR_MISC); + tt_int_op(mock_saved_log_number(), OP_EQ, 2); +#if 0 + /* See above */ + tt_str_op(mock_saved_log_at(0), OP_EQ, "TLS error while handshaking: " + "(null) (in bignum routines:(null):SSLv3 write client hello B)\n"); + tt_str_op(mock_saved_log_at(1), OP_EQ, "TLS error while handshaking: " + "(null) (in system library:connect:SSLv3 write client hello B)\n"); +#endif + tt_int_op(mock_saved_severity_at(0), OP_EQ, LOG_WARN); + tt_int_op(mock_saved_severity_at(1), OP_EQ, LOG_WARN); + + done: + teardown_capture_of_logs(previous_log); + SSL_free(tls->ssl); + SSL_CTX_free(ctx); + tor_free(tls); + tor_free(method); +} +#endif + +#ifndef OPENSSL_OPAQUE +static void +test_tortls_finish_handshake(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_t *tls; + SSL_CTX *ctx; + SSL_METHOD *method = give_me_a_test_method(); + SSL_library_init(); + SSL_load_error_strings(); + + X509 *c1 = read_cert_from(validCertString); + SESS_CERT_local *sess = NULL; + + ctx = SSL_CTX_new(method); + + tls = tor_malloc_zero(sizeof(tor_tls_t)); + tls->ssl = SSL_new(ctx); + tls->state = TOR_TLS_ST_OPEN; + + ret = tor_tls_finish_handshake(tls); + tt_int_op(ret, OP_EQ, 0); + + tls->isServer = 1; + tls->wasV2Handshake = 0; + ret = tor_tls_finish_handshake(tls); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tls->wasV2Handshake, OP_EQ, 1); + + tls->wasV2Handshake = 1; + ret = tor_tls_finish_handshake(tls); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tls->wasV2Handshake, OP_EQ, 1); + + tls->wasV2Handshake = 1; + tls->ssl->session = SSL_SESSION_new(); + ret = tor_tls_finish_handshake(tls); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tls->wasV2Handshake, OP_EQ, 0); + + tls->isServer = 0; + + sess = tor_malloc_zero(sizeof(SESS_CERT_local)); + tls->ssl->session->sess_cert = (void *)sess; + sess->cert_chain = sk_X509_new_null(); + sk_X509_push(sess->cert_chain, c1); + tls->ssl->session->peer = c1; + tls->wasV2Handshake = 0; + ret = tor_tls_finish_handshake(tls); + tt_int_op(ret, OP_EQ, 0); + tt_int_op(tls->wasV2Handshake, OP_EQ, 1); + + method->num_ciphers = fake_num_ciphers; + ret = tor_tls_finish_handshake(tls); + tt_int_op(ret, OP_EQ, -9); + + done: + if (sess) + sk_X509_free(sess->cert_chain); + if (tls->ssl && tls->ssl->session) { + tor_free(tls->ssl->session->sess_cert); + } + SSL_free(tls->ssl); + tor_free(tls); + SSL_CTX_free(ctx); + tor_free(method); +} +#endif + +static int fixed_crypto_pk_new_result_index; +static crypto_pk_t *fixed_crypto_pk_new_result[5]; + +static crypto_pk_t * +fixed_crypto_pk_new(void) +{ + return fixed_crypto_pk_new_result[fixed_crypto_pk_new_result_index++]; +} + +#ifndef OPENSSL_OPAQUE +static int fixed_crypto_pk_generate_key_with_bits_result_index; +static int fixed_crypto_pk_generate_key_with_bits_result[5]; +static int fixed_tor_tls_create_certificate_result_index; +static X509 *fixed_tor_tls_create_certificate_result[5]; +static int fixed_tor_x509_cert_new_result_index; +static tor_x509_cert_t *fixed_tor_x509_cert_new_result[5]; + +static int +fixed_crypto_pk_generate_key_with_bits(crypto_pk_t *env, int bits) +{ + (void)env; + (void)bits; + return fixed_crypto_pk_generate_key_with_bits_result[ + fixed_crypto_pk_generate_key_with_bits_result_index++]; +} + +static X509 * +fixed_tor_tls_create_certificate(crypto_pk_t *rsa, + crypto_pk_t *rsa_sign, + const char *cname, + const char *cname_sign, + unsigned int cert_lifetime) +{ + (void)rsa; + (void)rsa_sign; + (void)cname; + (void)cname_sign; + (void)cert_lifetime; + return fixed_tor_tls_create_certificate_result[ + fixed_tor_tls_create_certificate_result_index++]; +} + +static tor_x509_cert_t * +fixed_tor_x509_cert_new(X509 *x509_cert) +{ + (void) x509_cert; + return fixed_tor_x509_cert_new_result[ + fixed_tor_x509_cert_new_result_index++]; +} + +static void +test_tortls_context_new(void *ignored) +{ + (void)ignored; + tor_tls_context_t *ret; + crypto_pk_t *pk1, *pk2, *pk3, *pk4, *pk5, *pk6, *pk7, *pk8, *pk9, *pk10, + *pk11, *pk12, *pk13, *pk14, *pk15, *pk16, *pk17, *pk18; + + pk1 = crypto_pk_new(); + pk2 = crypto_pk_new(); + pk3 = crypto_pk_new(); + pk4 = crypto_pk_new(); + pk5 = crypto_pk_new(); + pk6 = crypto_pk_new(); + pk7 = crypto_pk_new(); + pk8 = crypto_pk_new(); + pk9 = crypto_pk_new(); + pk10 = crypto_pk_new(); + pk11 = crypto_pk_new(); + pk12 = crypto_pk_new(); + pk13 = crypto_pk_new(); + pk14 = crypto_pk_new(); + pk15 = crypto_pk_new(); + pk16 = crypto_pk_new(); + pk17 = crypto_pk_new(); + pk18 = crypto_pk_new(); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = NULL; + MOCK(crypto_pk_new, fixed_crypto_pk_new); + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + MOCK(crypto_pk_generate_key_with_bits, + fixed_crypto_pk_generate_key_with_bits); + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk1; + fixed_crypto_pk_new_result[1] = NULL; + fixed_crypto_pk_generate_key_with_bits_result[0] = -1; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk2; + fixed_crypto_pk_new_result[1] = NULL; + fixed_crypto_pk_generate_key_with_bits_result[0] = 0; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk3; + fixed_crypto_pk_new_result[1] = pk4; + fixed_crypto_pk_new_result[2] = NULL; + fixed_crypto_pk_generate_key_with_bits_result[0] = 0; + fixed_crypto_pk_generate_key_with_bits_result[1] = -1; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + MOCK(tor_tls_create_certificate, fixed_tor_tls_create_certificate); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk5; + fixed_crypto_pk_new_result[1] = pk6; + fixed_crypto_pk_new_result[2] = NULL; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + fixed_crypto_pk_generate_key_with_bits_result[1] = 0; + fixed_tor_tls_create_certificate_result_index = 0; + fixed_tor_tls_create_certificate_result[0] = NULL; + fixed_tor_tls_create_certificate_result[1] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[2] = tor_malloc_zero(sizeof(X509)); + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk7; + fixed_crypto_pk_new_result[1] = pk8; + fixed_crypto_pk_new_result[2] = NULL; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + fixed_tor_tls_create_certificate_result_index = 0; + fixed_tor_tls_create_certificate_result[0] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[1] = NULL; + fixed_tor_tls_create_certificate_result[2] = tor_malloc_zero(sizeof(X509)); + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk9; + fixed_crypto_pk_new_result[1] = pk10; + fixed_crypto_pk_new_result[2] = NULL; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + fixed_tor_tls_create_certificate_result_index = 0; + fixed_tor_tls_create_certificate_result[0] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[1] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[2] = NULL; + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + MOCK(tor_x509_cert_new, fixed_tor_x509_cert_new); + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk11; + fixed_crypto_pk_new_result[1] = pk12; + fixed_crypto_pk_new_result[2] = NULL; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + fixed_tor_tls_create_certificate_result_index = 0; + fixed_tor_tls_create_certificate_result[0] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[1] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[2] = tor_malloc_zero(sizeof(X509)); + fixed_tor_x509_cert_new_result_index = 0; + fixed_tor_x509_cert_new_result[0] = NULL; + fixed_tor_x509_cert_new_result[1] = NULL; + fixed_tor_x509_cert_new_result[2] = NULL; + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk13; + fixed_crypto_pk_new_result[1] = pk14; + fixed_crypto_pk_new_result[2] = NULL; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + fixed_tor_tls_create_certificate_result_index = 0; + fixed_tor_tls_create_certificate_result[0] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[1] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[2] = tor_malloc_zero(sizeof(X509)); + fixed_tor_x509_cert_new_result_index = 0; + fixed_tor_x509_cert_new_result[0] = tor_malloc_zero(sizeof(tor_x509_cert_t)); + fixed_tor_x509_cert_new_result[1] = NULL; + fixed_tor_x509_cert_new_result[2] = NULL; + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk15; + fixed_crypto_pk_new_result[1] = pk16; + fixed_crypto_pk_new_result[2] = NULL; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + fixed_tor_tls_create_certificate_result_index = 0; + fixed_tor_tls_create_certificate_result[0] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[1] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[2] = tor_malloc_zero(sizeof(X509)); + fixed_tor_x509_cert_new_result_index = 0; + fixed_tor_x509_cert_new_result[0] = tor_malloc_zero(sizeof(tor_x509_cert_t)); + fixed_tor_x509_cert_new_result[1] = tor_malloc_zero(sizeof(tor_x509_cert_t)); + fixed_tor_x509_cert_new_result[2] = NULL; + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = pk17; + fixed_crypto_pk_new_result[1] = pk18; + fixed_crypto_pk_new_result[2] = NULL; + fixed_crypto_pk_generate_key_with_bits_result_index = 0; + fixed_tor_tls_create_certificate_result_index = 0; + fixed_tor_tls_create_certificate_result[0] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[1] = tor_malloc_zero(sizeof(X509)); + fixed_tor_tls_create_certificate_result[2] = tor_malloc_zero(sizeof(X509)); + fixed_tor_x509_cert_new_result_index = 0; + fixed_tor_x509_cert_new_result[0] = tor_malloc_zero(sizeof(tor_x509_cert_t)); + fixed_tor_x509_cert_new_result[1] = tor_malloc_zero(sizeof(tor_x509_cert_t)); + fixed_tor_x509_cert_new_result[2] = tor_malloc_zero(sizeof(tor_x509_cert_t)); + ret = tor_tls_context_new(NULL, 0, 0, 0); + tt_assert(!ret); + + done: + UNMOCK(tor_x509_cert_new); + UNMOCK(tor_tls_create_certificate); + UNMOCK(crypto_pk_generate_key_with_bits); + UNMOCK(crypto_pk_new); +} +#endif + +static int fixed_crypto_pk_get_evp_pkey_result_index = 0; +static EVP_PKEY *fixed_crypto_pk_get_evp_pkey_result[5]; + +static EVP_PKEY * +fixed_crypto_pk_get_evp_pkey_(crypto_pk_t *env, int private) +{ + (void) env; + (void) private; + return fixed_crypto_pk_get_evp_pkey_result[ + fixed_crypto_pk_get_evp_pkey_result_index++]; +} + +static void +test_tortls_create_certificate(void *ignored) +{ + (void)ignored; + X509 *ret; + crypto_pk_t *pk1, *pk2; + + pk1 = crypto_pk_new(); + pk2 = crypto_pk_new(); + + MOCK(crypto_pk_get_evp_pkey_, fixed_crypto_pk_get_evp_pkey_); + fixed_crypto_pk_get_evp_pkey_result_index = 0; + fixed_crypto_pk_get_evp_pkey_result[0] = NULL; + ret = tor_tls_create_certificate(pk1, pk2, "hello", "hello2", 1); + tt_assert(!ret); + + fixed_crypto_pk_get_evp_pkey_result_index = 0; + fixed_crypto_pk_get_evp_pkey_result[0] = tor_malloc_zero(sizeof(EVP_PKEY)); + fixed_crypto_pk_get_evp_pkey_result[1] = NULL; + ret = tor_tls_create_certificate(pk1, pk2, "hello", "hello2", 1); + tt_assert(!ret); + + fixed_crypto_pk_get_evp_pkey_result_index = 0; + fixed_crypto_pk_get_evp_pkey_result[0] = tor_malloc_zero(sizeof(EVP_PKEY)); + fixed_crypto_pk_get_evp_pkey_result[1] = tor_malloc_zero(sizeof(EVP_PKEY)); + ret = tor_tls_create_certificate(pk1, pk2, "hello", "hello2", 1); + tt_assert(!ret); + + done: + UNMOCK(crypto_pk_get_evp_pkey_); + crypto_pk_free(pk1); + crypto_pk_free(pk2); +} + +static void +test_tortls_cert_new(void *ignored) +{ + (void)ignored; + tor_x509_cert_t *ret; + X509 *cert = read_cert_from(validCertString); + + ret = tor_x509_cert_new(NULL); + tt_assert(!ret); + + ret = tor_x509_cert_new(cert); + tt_assert(ret); + tor_x509_cert_free(ret); + ret = NULL; + +#if 0 + cert = read_cert_from(validCertString); + /* XXX this doesn't do what you think: it alters a copy of the pubkey. */ + X509_get_pubkey(cert)->type = EVP_PKEY_DSA; + ret = tor_x509_cert_new(cert); + tt_assert(ret); +#endif + +#ifndef OPENSSL_OPAQUE + cert = read_cert_from(validCertString); + X509_CINF_free(cert->cert_info); + cert->cert_info = NULL; + ret = tor_x509_cert_new(cert); + tt_assert(ret); +#endif + + done: + tor_x509_cert_free(ret); +} + +static void +test_tortls_cert_is_valid(void *ignored) +{ + (void)ignored; + int ret; + tor_x509_cert_t *cert = NULL, *scert = NULL; + + scert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0); + tt_int_op(ret, OP_EQ, 0); + + cert = tor_malloc_zero(sizeof(tor_x509_cert_t)); + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0); + tt_int_op(ret, OP_EQ, 0); + tor_free(scert); + tor_free(cert); + + cert = tor_x509_cert_new(read_cert_from(validCertString)); + scert = tor_x509_cert_new(read_cert_from(caCertString)); + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0); + tt_int_op(ret, OP_EQ, 1); + +#ifndef OPENSSL_OPAQUE + tor_x509_cert_free(cert); + tor_x509_cert_free(scert); + cert = tor_x509_cert_new(read_cert_from(validCertString)); + scert = tor_x509_cert_new(read_cert_from(caCertString)); + ASN1_TIME_free(cert->cert->cert_info->validity->notAfter); + cert->cert->cert_info->validity->notAfter = + ASN1_TIME_set(NULL, time(NULL)-1000000); + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0); + tt_int_op(ret, OP_EQ, 0); + + tor_x509_cert_free(cert); + tor_x509_cert_free(scert); + cert = tor_x509_cert_new(read_cert_from(validCertString)); + scert = tor_x509_cert_new(read_cert_from(caCertString)); + X509_PUBKEY_free(cert->cert->cert_info->key); + cert->cert->cert_info->key = NULL; + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 1); + tt_int_op(ret, OP_EQ, 0); +#endif + +#if 0 + tor_x509_cert_free(cert); + tor_x509_cert_free(scert); + cert = tor_x509_cert_new(read_cert_from(validCertString)); + scert = tor_x509_cert_new(read_cert_from(caCertString)); + /* This doesn't actually change the key in the cert. XXXXXX */ + BN_one(EVP_PKEY_get1_RSA(X509_get_pubkey(cert->cert))->n); + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 1); + tt_int_op(ret, OP_EQ, 0); + + tor_x509_cert_free(cert); + tor_x509_cert_free(scert); + cert = tor_x509_cert_new(read_cert_from(validCertString)); + scert = tor_x509_cert_new(read_cert_from(caCertString)); + /* This doesn't actually change the key in the cert. XXXXXX */ + X509_get_pubkey(cert->cert)->type = EVP_PKEY_EC; + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 1); + tt_int_op(ret, OP_EQ, 0); + + tor_x509_cert_free(cert); + tor_x509_cert_free(scert); + cert = tor_x509_cert_new(read_cert_from(validCertString)); + scert = tor_x509_cert_new(read_cert_from(caCertString)); + /* This doesn't actually change the key in the cert. XXXXXX */ + X509_get_pubkey(cert->cert)->type = EVP_PKEY_EC; + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0); + tt_int_op(ret, OP_EQ, 1); + + tor_x509_cert_free(cert); + tor_x509_cert_free(scert); + cert = tor_x509_cert_new(read_cert_from(validCertString)); + scert = tor_x509_cert_new(read_cert_from(caCertString)); + /* This doesn't actually change the key in the cert. XXXXXX */ + X509_get_pubkey(cert->cert)->type = EVP_PKEY_EC; + X509_get_pubkey(cert->cert)->ameth = NULL; + ret = tor_tls_cert_is_valid(LOG_WARN, cert, scert, 0); + tt_int_op(ret, OP_EQ, 0); +#endif + + done: + tor_x509_cert_free(cert); + tor_x509_cert_free(scert); +} + +static void +test_tortls_context_init_one(void *ignored) +{ + (void)ignored; + int ret; + tor_tls_context_t *old = NULL; + + MOCK(crypto_pk_new, fixed_crypto_pk_new); + + fixed_crypto_pk_new_result_index = 0; + fixed_crypto_pk_new_result[0] = NULL; + ret = tor_tls_context_init_one(&old, NULL, 0, 0, 0); + tt_int_op(ret, OP_EQ, -1); + + done: + UNMOCK(crypto_pk_new); +} + +#define LOCAL_TEST_CASE(name, flags) \ + { #name, test_tortls_##name, (flags|TT_FORK), NULL, NULL } + +#ifdef OPENSSL_OPAQUE +#define INTRUSIVE_TEST_CASE(name, flags) \ + { #name, NULL, TT_SKIP, NULL, NULL } +#else +#define INTRUSIVE_TEST_CASE(name, flags) LOCAL_TEST_CASE(name, flags) +#endif + +struct testcase_t tortls_tests[] = { + LOCAL_TEST_CASE(errno_to_tls_error, 0), + LOCAL_TEST_CASE(err_to_string, 0), + LOCAL_TEST_CASE(tor_tls_new, TT_FORK), + LOCAL_TEST_CASE(tor_tls_get_error, 0), + LOCAL_TEST_CASE(get_state_description, TT_FORK), + LOCAL_TEST_CASE(get_by_ssl, TT_FORK), + LOCAL_TEST_CASE(allocate_tor_tls_object_ex_data_index, TT_FORK), + LOCAL_TEST_CASE(log_one_error, TT_FORK), + INTRUSIVE_TEST_CASE(get_error, TT_FORK), + LOCAL_TEST_CASE(always_accept_verify_cb, 0), + INTRUSIVE_TEST_CASE(x509_cert_free, 0), + LOCAL_TEST_CASE(x509_cert_get_id_digests, 0), + INTRUSIVE_TEST_CASE(cert_matches_key, 0), + INTRUSIVE_TEST_CASE(cert_get_key, 0), + LOCAL_TEST_CASE(get_my_client_auth_key, TT_FORK), + LOCAL_TEST_CASE(get_my_certs, TT_FORK), + INTRUSIVE_TEST_CASE(get_ciphersuite_name, 0), + INTRUSIVE_TEST_CASE(classify_client_ciphers, 0), + LOCAL_TEST_CASE(client_is_using_v2_ciphers, 0), + INTRUSIVE_TEST_CASE(verify, 0), + INTRUSIVE_TEST_CASE(check_lifetime, 0), + INTRUSIVE_TEST_CASE(get_pending_bytes, 0), + LOCAL_TEST_CASE(get_forced_write_size, 0), + LOCAL_TEST_CASE(get_write_overhead_ratio, TT_FORK), + LOCAL_TEST_CASE(used_v1_handshake, TT_FORK), + LOCAL_TEST_CASE(get_num_server_handshakes, 0), + LOCAL_TEST_CASE(server_got_renegotiate, 0), + INTRUSIVE_TEST_CASE(SSL_SESSION_get_master_key, 0), + INTRUSIVE_TEST_CASE(get_tlssecrets, 0), + INTRUSIVE_TEST_CASE(get_buffer_sizes, 0), + LOCAL_TEST_CASE(evaluate_ecgroup_for_tls, 0), + INTRUSIVE_TEST_CASE(try_to_extract_certs_from_tls, 0), + INTRUSIVE_TEST_CASE(get_peer_cert, 0), + INTRUSIVE_TEST_CASE(peer_has_cert, 0), + INTRUSIVE_TEST_CASE(shutdown, 0), + INTRUSIVE_TEST_CASE(finish_handshake, 0), + INTRUSIVE_TEST_CASE(handshake, 0), + INTRUSIVE_TEST_CASE(write, 0), + INTRUSIVE_TEST_CASE(read, 0), + INTRUSIVE_TEST_CASE(server_info_callback, 0), + LOCAL_TEST_CASE(is_server, 0), + INTRUSIVE_TEST_CASE(assert_renegotiation_unblocked, 0), + INTRUSIVE_TEST_CASE(block_renegotiation, 0), + INTRUSIVE_TEST_CASE(unblock_renegotiation, 0), + INTRUSIVE_TEST_CASE(set_renegotiate_callback, 0), + LOCAL_TEST_CASE(set_logged_address, 0), + INTRUSIVE_TEST_CASE(find_cipher_by_id, 0), + INTRUSIVE_TEST_CASE(session_secret_cb, 0), + INTRUSIVE_TEST_CASE(debug_state_callback, 0), + INTRUSIVE_TEST_CASE(context_new, 0), + LOCAL_TEST_CASE(create_certificate, 0), + LOCAL_TEST_CASE(cert_new, 0), + LOCAL_TEST_CASE(cert_is_valid, 0), + LOCAL_TEST_CASE(context_init_one, 0), + END_OF_TESTCASES +}; + diff --git a/src/test/test_util.c b/src/test/test_util.c index 0a5783e9f5..37f7d938ea 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -14,11 +14,21 @@ #include "memarea.h" #include "util_process.h" +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_SYS_UTIME_H +#include <sys/utime.h> +#endif +#ifdef HAVE_UTIME_H +#include <utime.h> +#endif #ifdef _WIN32 #include <tchar.h> #endif #include <math.h> #include <ctype.h> +#include <float.h> /* XXXX this is a minimal wrapper to make the unit tests compile with the * changed tor_timegm interface. */ @@ -4097,6 +4107,9 @@ test_util_round_to_next_multiple_of(void *arg) tt_u64_op(round_uint64_to_next_multiple_of(99,7), ==, 105); tt_u64_op(round_uint64_to_next_multiple_of(99,9), ==, 99); + tt_u64_op(round_uint64_to_next_multiple_of(UINT64_MAX,2), ==, + UINT64_MAX); + tt_i64_op(round_int64_to_next_multiple_of(0,1), ==, 0); tt_i64_op(round_int64_to_next_multiple_of(0,7), ==, 0); @@ -4110,7 +4123,27 @@ test_util_round_to_next_multiple_of(void *arg) tt_i64_op(round_int64_to_next_multiple_of(INT64_MIN,2), ==, INT64_MIN); tt_i64_op(round_int64_to_next_multiple_of(INT64_MAX,2), ==, - INT64_MAX-INT64_MAX%2); + INT64_MAX); + + tt_int_op(round_uint32_to_next_multiple_of(0,1), ==, 0); + tt_int_op(round_uint32_to_next_multiple_of(0,7), ==, 0); + + tt_int_op(round_uint32_to_next_multiple_of(99,1), ==, 99); + tt_int_op(round_uint32_to_next_multiple_of(99,7), ==, 105); + tt_int_op(round_uint32_to_next_multiple_of(99,9), ==, 99); + + tt_int_op(round_uint32_to_next_multiple_of(UINT32_MAX,2), ==, + UINT32_MAX); + + tt_uint_op(round_to_next_multiple_of(0,1), ==, 0); + tt_uint_op(round_to_next_multiple_of(0,7), ==, 0); + + tt_uint_op(round_to_next_multiple_of(99,1), ==, 99); + tt_uint_op(round_to_next_multiple_of(99,7), ==, 105); + tt_uint_op(round_to_next_multiple_of(99,9), ==, 99); + + tt_uint_op(round_to_next_multiple_of(UINT_MAX,2), ==, + UINT_MAX); done: ; } @@ -4143,6 +4176,7 @@ test_util_laplace(void *arg) */ tt_i64_op(INT64_MIN + 20, ==, add_laplace_noise(20, 0.0, delta_f, epsilon)); + tt_i64_op(-60, ==, add_laplace_noise(20, 0.1, delta_f, epsilon)); tt_i64_op(-14, ==, add_laplace_noise(20, 0.25, delta_f, epsilon)); tt_i64_op(20, ==, add_laplace_noise(20, 0.5, delta_f, epsilon)); @@ -4150,15 +4184,146 @@ test_util_laplace(void *arg) tt_i64_op(100, ==, add_laplace_noise(20, 0.9, delta_f, epsilon)); tt_i64_op(215, ==, add_laplace_noise(20, 0.99, delta_f, epsilon)); + /* Test extreme values of signal with maximally negative values of noise + * 1.0000000000000002 is the smallest number > 1 + * 0.0000000000000002 is the double epsilon (error when calculating near 1) + * this is approximately 1/(2^52) + * per https://en.wikipedia.org/wiki/Double_precision + * (let's not descend into the world of subnormals) + * >>> laplace.ppf([0, 0.0000000000000002], loc = 0, scale = 1) + * array([ -inf, -35.45506713]) + */ + const double noscale_df = 1.0, noscale_eps = 1.0; + + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(0, 0.0, noscale_df, noscale_eps)); + + /* is it clipped to INT64_MIN? */ + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(-1, 0.0, noscale_df, noscale_eps)); + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(INT64_MIN, 0.0, + noscale_df, noscale_eps)); + /* ... even when scaled? */ + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(0, 0.0, delta_f, epsilon)); + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(0, 0.0, + DBL_MAX, 1)); + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(INT64_MIN, 0.0, + DBL_MAX, 1)); + + /* does it play nice with INT64_MAX? */ + tt_i64_op((INT64_MIN + INT64_MAX), ==, + add_laplace_noise(INT64_MAX, 0.0, + noscale_df, noscale_eps)); + + /* do near-zero fractional values work? */ + const double min_dbl_error = 0.0000000000000002; + + tt_i64_op(-35, ==, + add_laplace_noise(0, min_dbl_error, + noscale_df, noscale_eps)); + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(INT64_MIN, min_dbl_error, + noscale_df, noscale_eps)); + tt_i64_op((-35 + INT64_MAX), ==, + add_laplace_noise(INT64_MAX, min_dbl_error, + noscale_df, noscale_eps)); + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(0, min_dbl_error, + DBL_MAX, 1)); + tt_i64_op((INT64_MAX + INT64_MIN), ==, + add_laplace_noise(INT64_MAX, min_dbl_error, + DBL_MAX, 1)); + tt_i64_op(INT64_MIN, ==, + add_laplace_noise(INT64_MIN, min_dbl_error, + DBL_MAX, 1)); + + /* does it play nice with INT64_MAX? */ + tt_i64_op((INT64_MAX - 35), ==, + add_laplace_noise(INT64_MAX, min_dbl_error, + noscale_df, noscale_eps)); + + /* Test extreme values of signal with maximally positive values of noise + * 1.0000000000000002 is the smallest number > 1 + * 0.9999999999999998 is the greatest number < 1 by calculation + * per https://en.wikipedia.org/wiki/Double_precision + * >>> laplace.ppf([1.0, 0.9999999999999998], loc = 0, scale = 1) + * array([inf, 35.35050621]) + * but the function rejects p == 1.0, so we just use max_dbl_lt_one + */ + const double max_dbl_lt_one = 0.9999999999999998; + + /* do near-one fractional values work? */ + tt_i64_op(35, ==, + add_laplace_noise(0, max_dbl_lt_one, noscale_df, noscale_eps)); + + /* is it clipped to INT64_MAX? */ + tt_i64_op(INT64_MAX, ==, + add_laplace_noise(INT64_MAX - 35, max_dbl_lt_one, + noscale_df, noscale_eps)); + tt_i64_op(INT64_MAX, ==, + add_laplace_noise(INT64_MAX - 34, max_dbl_lt_one, + noscale_df, noscale_eps)); + tt_i64_op(INT64_MAX, ==, + add_laplace_noise(INT64_MAX, max_dbl_lt_one, + noscale_df, noscale_eps)); + /* ... even when scaled? */ + tt_i64_op(INT64_MAX, ==, + add_laplace_noise(INT64_MAX, max_dbl_lt_one, + delta_f, epsilon)); + tt_i64_op((INT64_MIN + INT64_MAX), ==, + add_laplace_noise(INT64_MIN, max_dbl_lt_one, + DBL_MAX, 1)); + tt_i64_op(INT64_MAX, ==, + add_laplace_noise(INT64_MAX, max_dbl_lt_one, + DBL_MAX, 1)); + /* does it play nice with INT64_MIN? */ + tt_i64_op((INT64_MIN + 35), ==, + add_laplace_noise(INT64_MIN, max_dbl_lt_one, + noscale_df, noscale_eps)); + done: ; } -#define UTIL_LEGACY(name) \ - { #name, test_util_ ## name , 0, NULL, NULL } +static void +test_util_clamp_double_to_int64(void *arg) +{ + (void)arg; -#define UTIL_TEST(name, flags) \ - { #name, test_util_ ## name, flags, NULL, NULL } + tt_i64_op(INT64_MIN, ==, clamp_double_to_int64(-INFINITY)); + tt_i64_op(INT64_MIN, ==, + clamp_double_to_int64(-1.0 * pow(2.0, 64.0) - 1.0)); + tt_i64_op(INT64_MIN, ==, + clamp_double_to_int64(-1.0 * pow(2.0, 63.0) - 1.0)); + tt_i64_op(((uint64_t) -1) << 53, ==, + clamp_double_to_int64(-1.0 * pow(2.0, 53.0))); + tt_i64_op((((uint64_t) -1) << 53) + 1, ==, + clamp_double_to_int64(-1.0 * pow(2.0, 53.0) + 1.0)); + tt_i64_op(-1, ==, clamp_double_to_int64(-1.0)); + tt_i64_op(0, ==, clamp_double_to_int64(-0.9)); + tt_i64_op(0, ==, clamp_double_to_int64(-0.1)); + tt_i64_op(0, ==, clamp_double_to_int64(0.0)); + tt_i64_op(0, ==, clamp_double_to_int64(NAN)); + tt_i64_op(0, ==, clamp_double_to_int64(0.1)); + tt_i64_op(0, ==, clamp_double_to_int64(0.9)); + tt_i64_op(1, ==, clamp_double_to_int64(1.0)); + tt_i64_op((((int64_t) 1) << 53) - 1, ==, + clamp_double_to_int64(pow(2.0, 53.0) - 1.0)); + tt_i64_op(((int64_t) 1) << 53, ==, + clamp_double_to_int64(pow(2.0, 53.0))); + tt_i64_op(INT64_MAX, ==, + clamp_double_to_int64(pow(2.0, 63.0))); + tt_i64_op(INT64_MAX, ==, + clamp_double_to_int64(pow(2.0, 64.0))); + tt_i64_op(INT64_MAX, ==, clamp_double_to_int64(INFINITY)); + + done: + ; +} #ifdef FD_CLOEXEC #define CAN_CHECK_CLOEXEC @@ -4180,9 +4345,14 @@ fd_is_nonblocking(tor_socket_t fd) } #endif +#define ERRNO_IS_EPROTO(e) (e == SOCK_ERRNO(EPROTONOSUPPORT)) +#define SOCK_ERR_IS_EPROTO(s) ERRNO_IS_EPROTO(tor_socket_errno(s)) + +/* Test for tor_open_socket*, using IPv4 or IPv6 depending on arg. */ static void test_util_socket(void *arg) { + const int domain = !strcmp(arg, "4") ? AF_INET : AF_INET6; tor_socket_t fd1 = TOR_INVALID_SOCKET; tor_socket_t fd2 = TOR_INVALID_SOCKET; tor_socket_t fd3 = TOR_INVALID_SOCKET; @@ -4193,15 +4363,19 @@ test_util_socket(void *arg) (void)arg; - fd1 = tor_open_socket_with_extensions(AF_INET, SOCK_STREAM, 0, 0, 0); - fd2 = tor_open_socket_with_extensions(AF_INET, SOCK_STREAM, 0, 0, 1); + fd1 = tor_open_socket_with_extensions(domain, SOCK_STREAM, 0, 0, 0); + if (SOCK_ERR_IS_EPROTO(fd1)) { + /* Assume we're on an IPv4-only or IPv6-only system, and give up now. */ + goto done; + } + fd2 = tor_open_socket_with_extensions(domain, SOCK_STREAM, 0, 0, 1); tt_assert(SOCKET_OK(fd1)); tt_assert(SOCKET_OK(fd2)); tt_int_op(get_n_open_sockets(), OP_EQ, n + 2); - //fd3 = tor_open_socket_with_extensions(AF_INET, SOCK_STREAM, 0, 1, 0); - //fd4 = tor_open_socket_with_extensions(AF_INET, SOCK_STREAM, 0, 1, 1); - fd3 = tor_open_socket(AF_INET, SOCK_STREAM, 0); - fd4 = tor_open_socket_nonblocking(AF_INET, SOCK_STREAM, 0); + //fd3 = tor_open_socket_with_extensions(domain, SOCK_STREAM, 0, 1, 0); + //fd4 = tor_open_socket_with_extensions(domain, SOCK_STREAM, 0, 1, 1); + fd3 = tor_open_socket(domain, SOCK_STREAM, 0); + fd4 = tor_open_socket_nonblocking(domain, SOCK_STREAM, 0); tt_assert(SOCKET_OK(fd3)); tt_assert(SOCKET_OK(fd4)); tt_int_op(get_n_open_sockets(), OP_EQ, n + 4); @@ -4250,8 +4424,20 @@ test_util_socketpair(void *arg) int n = get_n_open_sockets(); tor_socket_t fds[2] = {TOR_INVALID_SOCKET, TOR_INVALID_SOCKET}; const int family = AF_UNIX; + int socketpair_result = 0; + + socketpair_result = tor_socketpair_fn(family, SOCK_STREAM, 0, fds); + /* If there is no 127.0.0.1 or ::1, tor_ersatz_socketpair will and must fail. + * Otherwise, we risk exposing a socketpair on a routable IP address. (Some + * BSD jails use a routable address for localhost. Fortunately, they have + * the real AF_UNIX socketpair.) */ + if (ersatz && ERRNO_IS_EPROTO(-socketpair_result)) { + /* In my testing, an IPv6-only FreeBSD jail without ::1 returned EINVAL. + * Assume we're on a machine without 127.0.0.1 or ::1 and give up now. */ + goto done; + } + tt_int_op(0, OP_EQ, socketpair_result); - tt_int_op(0, OP_EQ, tor_socketpair_fn(family, SOCK_STREAM, 0, fds)); tt_assert(SOCKET_OK(fds[0])); tt_assert(SOCKET_OK(fds[1])); tt_int_op(get_n_open_sockets(), OP_EQ, n + 2); @@ -4271,6 +4457,8 @@ test_util_socketpair(void *arg) tor_close_socket(fds[1]); } +#undef SOCKET_EPROTO + static void test_util_max_mem(void *arg) { @@ -4414,6 +4602,92 @@ test_util_get_avail_disk_space(void *arg) ; } +static void +test_util_touch_file(void *arg) +{ + (void) arg; + const char *fname = get_fname("touch"); + + const time_t now = time(NULL); + struct stat st; + write_bytes_to_file(fname, "abc", 3, 1); + tt_int_op(0, OP_EQ, stat(fname, &st)); + /* A subtle point: the filesystem time is not necessarily equal to the + * system clock time, since one can be using a monotonic clock, or coarse + * monotonic clock, or whatever. So we might wind up with an mtime a few + * microseconds ago. Let's just give it a lot of wiggle room. */ + tt_i64_op(st.st_mtime, OP_GE, now - 1); + + const time_t five_sec_ago = now - 5; + struct utimbuf u = { five_sec_ago, five_sec_ago }; + tt_int_op(0, OP_EQ, utime(fname, &u)); + tt_int_op(0, OP_EQ, stat(fname, &st)); + /* Let's hope that utime/stat give the same second as a round-trip? */ + tt_i64_op(st.st_mtime, OP_EQ, five_sec_ago); + + /* Finally we can touch the file */ + tt_int_op(0, OP_EQ, touch_file(fname)); + tt_int_op(0, OP_EQ, stat(fname, &st)); + tt_i64_op(st.st_mtime, OP_GE, now-1); + + done: + ; +} + +#ifndef _WIN32 +static void +test_util_pwdb(void *arg) +{ + (void) arg; + const struct passwd *me = NULL, *me2, *me3; + char *name = NULL; + char *dir = NULL; + + /* Uncached case. */ + /* Let's assume that we exist. */ + me = tor_getpwuid(getuid()); + tt_assert(me != NULL); + name = tor_strdup(me->pw_name); + + /* Uncached case */ + me2 = tor_getpwnam(name); + tt_assert(me2 != NULL); + tt_int_op(me2->pw_uid, OP_EQ, getuid()); + + /* Cached case */ + me3 = tor_getpwuid(getuid()); + tt_assert(me3 != NULL); + tt_str_op(me3->pw_name, OP_EQ, name); + + me3 = tor_getpwnam(name); + tt_assert(me3 != NULL); + tt_int_op(me3->pw_uid, OP_EQ, getuid()); + + dir = get_user_homedir(name); + tt_assert(dir != NULL); + + done: + tor_free(name); + tor_free(dir); +} +#endif + +#define UTIL_LEGACY(name) \ + { #name, test_util_ ## name , 0, NULL, NULL } + +#define UTIL_TEST(name, flags) \ + { #name, test_util_ ## name, flags, NULL, NULL } + +#ifdef _WIN32 +#define UTIL_TEST_NO_WIN(n, f) { #n, NULL, TT_SKIP, NULL, NULL } +#define UTIL_TEST_WIN_ONLY(n, f) UTIL_TEST(n, (f)) +#define UTIL_LEGACY_NO_WIN(n) UTIL_TEST_NO_WIN(n, 0) +#else +#define UTIL_TEST_NO_WIN(n, f) UTIL_TEST(n, (f)) +#define UTIL_TEST_WIN_ONLY(n, f) { #n, NULL, TT_SKIP, NULL, NULL } +#define UTIL_LEGACY_NO_WIN(n) UTIL_LEGACY(n) +#endif + struct testcase_t util_tests[] = { UTIL_LEGACY(time), UTIL_TEST(parse_http_time, 0), @@ -4421,9 +4695,7 @@ struct testcase_t util_tests[] = { UTIL_LEGACY(config_line_quotes), UTIL_LEGACY(config_line_comment_character), UTIL_LEGACY(config_line_escaped_content), -#ifndef _WIN32 - UTIL_LEGACY(expand_filename), -#endif + UTIL_LEGACY_NO_WIN(expand_filename), UTIL_LEGACY(escape_string_socks), UTIL_LEGACY(string_is_key_value), UTIL_LEGACY(strmisc), @@ -4441,19 +4713,16 @@ struct testcase_t util_tests[] = { UTIL_TEST(di_map, 0), UTIL_TEST(round_to_next_multiple_of, 0), UTIL_TEST(laplace, 0), + UTIL_TEST(clamp_double_to_int64, 0), UTIL_TEST(find_str_at_start_of_line, 0), UTIL_TEST(string_is_C_identifier, 0), UTIL_TEST(asprintf, 0), UTIL_TEST(listdir, 0), UTIL_TEST(parent_dir, 0), UTIL_TEST(ftruncate, 0), -#ifdef _WIN32 - UTIL_TEST(load_win_lib, 0), -#endif -#ifndef _WIN32 - UTIL_TEST(exit_status, 0), - UTIL_TEST(fgets_eagain, 0), -#endif + UTIL_TEST_WIN_ONLY(load_win_lib, 0), + UTIL_TEST_NO_WIN(exit_status, 0), + UTIL_TEST_NO_WIN(fgets_eagain, 0), UTIL_TEST(format_hex_number, 0), UTIL_TEST(format_dec_number, 0), UTIL_TEST(join_win_cmdline, 0), @@ -4473,7 +4742,10 @@ struct testcase_t util_tests[] = { UTIL_TEST(write_chunks_to_file, 0), UTIL_TEST(mathlog, 0), UTIL_TEST(weak_random, 0), - UTIL_TEST(socket, TT_FORK), + { "socket_ipv4", test_util_socket, TT_FORK, &passthrough_setup, + (void*)"4" }, + { "socket_ipv6", test_util_socket, TT_FORK, + &passthrough_setup, (void*)"6" }, { "socketpair", test_util_socketpair, TT_FORK, &passthrough_setup, (void*)"0" }, { "socketpair_ersatz", test_util_socketpair, TT_FORK, @@ -4483,6 +4755,8 @@ struct testcase_t util_tests[] = { UTIL_TEST(ipv4_validation, 0), UTIL_TEST(writepid, 0), UTIL_TEST(get_avail_disk_space, 0), + UTIL_TEST(touch_file, 0), + UTIL_TEST_NO_WIN(pwdb, TT_FORK), END_OF_TESTCASES }; diff --git a/src/test/test_util_format.c b/src/test/test_util_format.c new file mode 100644 index 0000000000..45a28ccaac --- /dev/null +++ b/src/test/test_util_format.c @@ -0,0 +1,302 @@ +/* Copyright (c) 2010-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "or.h" + +#include "test.h" + +#define UTIL_FORMAT_PRIVATE +#include "util_format.h" + +#define NS_MODULE util_format + +#if !defined(HAVE_HTONLL) && !defined(htonll) +#ifdef WORDS_BIGENDIAN +#define htonll(x) (x) +#else +static uint64_t +htonll(uint64_t a) +{ + return htonl((uint32_t)(a>>32)) | (((uint64_t)htonl((uint32_t)a))<<32); +} +#endif +#endif + +static void +test_util_format_unaligned_accessors(void *ignored) +{ + (void)ignored; + char buf[9] = "onionsoup"; // 6f6e696f6e736f7570 + + tt_u64_op(get_uint64(buf+1), OP_EQ, htonll(U64_LITERAL(0x6e696f6e736f7570))); + tt_uint_op(get_uint32(buf+1), OP_EQ, htonl(0x6e696f6e)); + tt_uint_op(get_uint16(buf+1), OP_EQ, htons(0x6e69)); + tt_uint_op(get_uint8(buf+1), OP_EQ, 0x6e); + + set_uint8(buf+7, 0x61); + tt_mem_op(buf, OP_EQ, "onionsoap", 9); + + set_uint16(buf+6, htons(0x746f)); + tt_mem_op(buf, OP_EQ, "onionstop", 9); + + set_uint32(buf+1, htonl(0x78696465)); + tt_mem_op(buf, OP_EQ, "oxidestop", 9); + + set_uint64(buf+1, htonll(U64_LITERAL(0x6266757363617465))); + tt_mem_op(buf, OP_EQ, "obfuscate", 9); + done: + ; +} + +static void +test_util_format_base64_encode(void *ignored) +{ + (void)ignored; + int res; + int i; + char *src; + char *dst; + + src = tor_malloc_zero(256); + dst = tor_malloc_zero(1000); + + for (i=0;i<256;i++) { + src[i] = (char)i; + } + + res = base64_encode(NULL, 1, src, 1, 0); + tt_int_op(res, OP_EQ, -1); + + res = base64_encode(dst, 1, NULL, 1, 0); + tt_int_op(res, OP_EQ, -1); + + res = base64_encode(dst, 1, src, 10, 0); + tt_int_op(res, OP_EQ, -1); + + res = base64_encode(dst, SSIZE_MAX-1, src, 1, 0); + tt_int_op(res, OP_EQ, -1); + + res = base64_encode(dst, SSIZE_MAX-1, src, 10, 0); + tt_int_op(res, OP_EQ, -1); + + res = base64_encode(dst, 1000, src, 256, 0); + tt_int_op(res, OP_EQ, 344); + tt_str_op(dst, OP_EQ, "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh" + "8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZH" + "SElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiY2RlZmdoaWprbG1ub3" + "BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeY" + "mZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/wM" + "HCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp" + "6uvs7e7v8PHy8/T19vf4+fr7/P3+/w=="); + + res = base64_encode(dst, 1000, src, 256, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 350); + tt_str_op(dst, OP_EQ, + "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4v\n" + "MDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5f\n" + "YGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMjY6P\n" + "kJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8vb6/\n" + "wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uvs7e7v\n" + "8PHy8/T19vf4+fr7/P3+/w==\n"); + + res = base64_encode(dst, 1000, src+1, 255, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 346); + + for (i = 0;i<50;i++) { + src[i] = 0; + } + src[50] = 255; + src[51] = 255; + src[52] = 255; + src[53] = 255; + + res = base64_encode(dst, 1000, src, 54, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 74); + + res = base64_encode(dst, 1000, src+1, 53, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 74); + + res = base64_encode(dst, 1000, src+2, 52, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 74); + + res = base64_encode(dst, 1000, src+3, 51, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 70); + + res = base64_encode(dst, 1000, src+4, 50, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 70); + + res = base64_encode(dst, 1000, src+5, 49, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 70); + + res = base64_encode(dst, 1000, src+6, 48, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 65); + + res = base64_encode(dst, 1000, src+7, 47, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 65); + + res = base64_encode(dst, 1000, src+8, 46, BASE64_ENCODE_MULTILINE); + tt_int_op(res, OP_EQ, 65); + + done: + tor_free(src); + tor_free(dst); +} + +static void +test_util_format_base64_decode_nopad(void *ignored) +{ + (void)ignored; + int res; + int i; + char *src; + uint8_t *dst, *real_dst; + uint8_t expected[] = {0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65}; + char real_src[] = "ZXhhbXBsZQ"; + + src = tor_malloc_zero(256); + dst = tor_malloc_zero(1000); + real_dst = tor_malloc_zero(10); + + for (i=0;i<256;i++) { + src[i] = (char)i; + } + + res = base64_decode_nopad(dst, 1, src, SIZE_T_CEILING); + tt_int_op(res, OP_EQ, -1); + + res = base64_decode_nopad(dst, 1, src, 5); + tt_int_op(res, OP_EQ, -1); + + const char *s = "SGVsbG8gd29ybGQ"; + res = base64_decode_nopad(dst, 1000, s, strlen(s)); + tt_int_op(res, OP_EQ, 11); + tt_mem_op(dst, OP_EQ, "Hello world", 11); + + s = "T3BhIG11bmRv"; + res = base64_decode_nopad(dst, 9, s, strlen(s)); + tt_int_op(res, OP_EQ, 9); + tt_mem_op(dst, OP_EQ, "Opa mundo", 9); + + res = base64_decode_nopad(real_dst, 10, real_src, 10); + tt_int_op(res, OP_EQ, 7); + tt_mem_op(real_dst, OP_EQ, expected, 7); + + done: + tor_free(src); + tor_free(dst); + tor_free(real_dst); +} + +static void +test_util_format_base64_decode(void *ignored) +{ + (void)ignored; + int res; + int i; + char *src; + char *dst, *real_dst; + uint8_t expected[] = {0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65}; + char real_src[] = "ZXhhbXBsZQ=="; + + src = tor_malloc_zero(256); + dst = tor_malloc_zero(1000); + real_dst = tor_malloc_zero(10); + + for (i=0;i<256;i++) { + src[i] = (char)i; + } + + res = base64_decode(dst, 1, src, SIZE_T_CEILING); + tt_int_op(res, OP_EQ, -1); + + res = base64_decode(dst, SIZE_T_CEILING+1, src, 10); + tt_int_op(res, OP_EQ, -1); + + const char *s = "T3BhIG11bmRv"; + res = base64_decode(dst, 9, s, strlen(s)); + tt_int_op(res, OP_EQ, 9); + tt_mem_op(dst, OP_EQ, "Opa mundo", 9); + + memset(dst, 0, 1000); + res = base64_decode(dst, 100, s, strlen(s)); + tt_int_op(res, OP_EQ, 9); + tt_mem_op(dst, OP_EQ, "Opa mundo", 9); + + s = "SGVsbG8gd29ybGQ="; + res = base64_decode(dst, 100, s, strlen(s)); + tt_int_op(res, OP_EQ, 11); + tt_mem_op(dst, OP_EQ, "Hello world", 11); + + res = base64_decode(real_dst, 10, real_src, 10); + tt_int_op(res, OP_EQ, 7); + tt_mem_op(real_dst, OP_EQ, expected, 7); + + done: + tor_free(src); + tor_free(dst); + tor_free(real_dst); +} + +static void +test_util_format_base16_decode(void *ignored) +{ + (void)ignored; + int res; + int i; + char *src; + char *dst, *real_dst; + char expected[] = {0x65, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65}; + char real_src[] = "6578616D706C65"; + + src = tor_malloc_zero(256); + dst = tor_malloc_zero(1000); + real_dst = tor_malloc_zero(10); + + for (i=0;i<256;i++) { + src[i] = (char)i; + } + + res = base16_decode(dst, 3, src, 3); + tt_int_op(res, OP_EQ, -1); + + res = base16_decode(dst, 1, src, 10); + tt_int_op(res, OP_EQ, -1); + + res = base16_decode(dst, SIZE_T_CEILING+2, src, 10); + tt_int_op(res, OP_EQ, -1); + + res = base16_decode(dst, 1000, "", 0); + tt_int_op(res, OP_EQ, 0); + + res = base16_decode(dst, 1000, "aabc", 4); + tt_int_op(res, OP_EQ, 0); + tt_mem_op(dst, OP_EQ, "\xaa\xbc", 2); + + res = base16_decode(dst, 1000, "aabcd", 6); + tt_int_op(res, OP_EQ, -1); + + res = base16_decode(dst, 1000, "axxx", 4); + tt_int_op(res, OP_EQ, -1); + + res = base16_decode(real_dst, 10, real_src, 14); + tt_int_op(res, OP_EQ, 0); + tt_mem_op(real_dst, OP_EQ, expected, 7); + + done: + tor_free(src); + tor_free(dst); + tor_free(real_dst); +} + +struct testcase_t util_format_tests[] = { + { "unaligned_accessors", test_util_format_unaligned_accessors, 0, + NULL, NULL }, + { "base64_encode", test_util_format_base64_encode, 0, NULL, NULL }, + { "base64_decode_nopad", test_util_format_base64_decode_nopad, 0, + NULL, NULL }, + { "base64_decode", test_util_format_base64_decode, 0, NULL, NULL }, + { "base16_decode", test_util_format_base16_decode, 0, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_util_process.c b/src/test/test_util_process.c new file mode 100644 index 0000000000..cb1d5b2ebb --- /dev/null +++ b/src/test/test_util_process.c @@ -0,0 +1,84 @@ +/* Copyright (c) 2010-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#define UTIL_PROCESS_PRIVATE +#include "orconfig.h" +#include "or.h" + +#include "test.h" + +#include "util_process.h" + +#include "log_test_helpers.h" + +#ifndef _WIN32 +#define NS_MODULE util_process + +static void +temp_callback(int r, void *s) +{ + (void)r; + (void)s; +} + +static void +test_util_process_set_waitpid_callback(void *ignored) +{ + (void)ignored; + waitpid_callback_t *res1 = NULL, *res2 = NULL; + int previous_log = setup_capture_of_logs(LOG_WARN); + pid_t pid = (pid_t)42; + + res1 = set_waitpid_callback(pid, temp_callback, NULL); + tt_assert(res1); + + res2 = set_waitpid_callback(pid, temp_callback, NULL); + tt_assert(res2); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Replaced a waitpid monitor on pid 42. That should be " + "impossible.\n"); + + done: + teardown_capture_of_logs(previous_log); + clear_waitpid_callback(res1); + clear_waitpid_callback(res2); +} + +static void +test_util_process_clear_waitpid_callback(void *ignored) +{ + (void)ignored; + waitpid_callback_t *res; + int previous_log = setup_capture_of_logs(LOG_WARN); + pid_t pid = (pid_t)43; + + clear_waitpid_callback(NULL); + + res = set_waitpid_callback(pid, temp_callback, NULL); + clear_waitpid_callback(res); + tt_int_op(mock_saved_log_number(), OP_EQ, 0); + +#if 0 + /* No. This is use-after-free. We don't _do_ that. XXXX */ + clear_waitpid_callback(res); + tt_str_op(mock_saved_log_at(0), OP_EQ, + "Couldn't remove waitpid monitor for pid 43.\n"); +#endif + + done: + teardown_capture_of_logs(previous_log); +} +#endif /* _WIN32 */ + +#ifndef _WIN32 +#define TEST(name) { #name, test_util_process_##name, 0, NULL, NULL } +#else +#define TEST(name) { #name, NULL, TT_SKIP, NULL, NULL } +#endif + +struct testcase_t util_process_tests[] = { + TEST(set_waitpid_callback), + TEST(clear_waitpid_callback), + END_OF_TESTCASES +}; + diff --git a/src/test/test_workqueue.c b/src/test/test_workqueue.c index 0d79733cf0..1202f80fa3 100644 --- a/src/test/test_workqueue.c +++ b/src/test/test_workqueue.c @@ -390,8 +390,14 @@ main(int argc, char **argv) init_logging(1); network_init(); - crypto_global_init(1, NULL, NULL); - crypto_seed_rng(); + if (crypto_global_init(1, NULL, NULL) < 0) { + printf("Couldn't initialize crypto subsystem; exiting.\n"); + return 1; + } + if (crypto_seed_rng() < 0) { + printf("Couldn't seed RNG; exiting.\n"); + return 1; + } rq = replyqueue_new(as_flags); tor_assert(rq); diff --git a/src/test/testing_common.c b/src/test/testing_common.c index 441024bd7d..e20e9e6095 100644 --- a/src/test/testing_common.c +++ b/src/test/testing_common.c @@ -272,7 +272,10 @@ main(int c, const char **v) return 1; } crypto_set_tls_dh_prime(); - crypto_seed_rng(); + if (crypto_seed_rng() < 0) { + printf("Couldn't seed RNG; exiting.\n"); + return 1; + } rep_hist_init(); network_init(); setup_directory(); @@ -294,6 +297,7 @@ main(int c, const char **v) tor_free_all(0); dmalloc_log_unfreed(); #endif + crypto_global_cleanup(); if (have_failed) return 1; diff --git a/src/test/vote_descriptors.inc b/src/test/vote_descriptors.inc new file mode 100644 index 0000000000..c5ce21f744 --- /dev/null +++ b/src/test/vote_descriptors.inc @@ -0,0 +1,94 @@ +const char* VOTE_BODY_V3 = +"network-status-version 3\n" +"vote-status vote\n" +"consensus-methods 13 14 15 16 17 18 19 20 21\n" +"published 2015-09-02 19:34:15\n" +"valid-after 2015-09-02 19:50:55\n" +"fresh-until 2015-09-02 20:07:38\n" +"valid-until 2015-09-02 20:24:15\n" +"voting-delay 100 250\n" +"client-versions 0.1.2.14,0.1.2.17\n" +"server-versions 0.1.2.10,0.1.2.15,0.1.2.16\n" +"known-flags Authority Exit Fast Guard MadeOfCheese MadeOfTin Running Stable V2Dir Valid\n" +"flag-thresholds stable-uptime=0 stable-mtbf=0 fast-speed=0 guard-wfu=0.000% guard-tk=0 guard-bw-inc-exits=0 guard-bw-exc-exits=0 enough-mtbf=0 ignoring-advertised-bws=0\n" +"params circuitwindow=80 foo=660\n" +"dir-source Voter3 D867ACF56A9D229B35C25F0090BC9867E906BE69 3.4.5.6 3.4.5.6 80 9000\n" +"contact voter@example.com\n" +"legacy-dir-key 4141414141414141414141414141414141414141\n" +"dir-key-certificate-version 3\n" +"fingerprint D867ACF56A9D229B35C25F0090BC9867E906BE69\n" +"dir-key-published 2008-12-12 18:07:24\n" +"dir-key-expires 2009-12-12 18:07:24\n" +"dir-identity-key\n" +"-----BEGIN RSA PUBLIC KEY-----\n" +"MIIBigKCAYEAveMpKlw8oD1YqFqpJchuwSR82BDhutbqgHiez3QO9FmzOctJpV+Y\n" +"mpTYIJLS/qC+4GBKFF1VK0C4SoBrS3zri0qdXdE+vBGcyrxrjMklpxoqSKRY2011\n" +"4eqYPghKlo5RzuqteBclGCHyNxWjUJeRKDWgvh+U/gr2uYM6fRm5q0fCzg4aECE7\n" +"VP6fDGZrMbQI8jHpiMSoC9gkUASNEa6chLInlnP8/H5qUEW4TB9CN/q095pefuwL\n" +"P+F+1Nz5hnM7fa5XmeMB8iM4RriUmOQlLBZgpQBMpEfWMIPcR9F1Gh3MxERqqUcH\n" +"tmij+IZdeXg9OkCXykcabaYIhZD3meErn9Tax4oA/THduLfgli9zM0ExwzH1OooN\n" +"L8rIcJ+2eBo3bQiQUbdYW71sl9w7nSPtircbJUa1mUvWYLPWQxFliPiQSetgJLMj\n" +"VQqtPmV2hvN2Xk3lLfJO50qMTK7w7Gsaw8UtV4YDM1Hcjp/hQaIB1xfwhXgl+eUU\n" +"btUa4c+cUTjHAgMBAAE=\n" +"-----END RSA PUBLIC KEY-----\n" +"dir-signing-key\n" +"-----BEGIN RSA PUBLIC KEY-----\n" +"MIGJAoGBALPSUInyuEu6NV3NjozplaniIEBzQXEjv1x9/+mqnwZABpYVmuy9A8nx\n" +"eoyY3sZFsnYwNW/IZjAgG23pEmevu3F+L4myMjjaa6ORl3MgRYQ4gmuFqpefrGdm\n" +"ywRCleh2JerkQ4VxOuq10dn/abITzLyaZzMw30KXWp5pxKXOLtxFAgMBAAE=\n" +"-----END RSA PUBLIC KEY-----\n" +"dir-key-crosscert\n" +"-----BEGIN ID SIGNATURE-----\n" +"FTBJNR/Hlt4T53yUMp1r/QCSMCpkHJCbYBT0R0pvYqhqFfYN5qHRSICRXaFFImIF\n" +"0DGWmwRza6DxPKNzkm5/b7I0de9zJW1jNNdQAQK5xppAtQcAafRdu8cBonnmh9KX\n" +"k1NrAK/X00FYywju3yl/SxCn1GddVNkHYexEudmJMPM=\n" +"-----END ID SIGNATURE-----\n" +"dir-key-certification\n" +"-----BEGIN SIGNATURE-----\n" +"pjWguLFBfELZDc6DywL6Do21SCl7LcutfpM92MEn4WYeSNcTXNR6lRX7reOEJk4e\n" +"NwEaMt+Hl7slgeR5wjnW3OmMmRPZK9bquNWbfD+sAOV9bRFZTpXIdleAQFPlwvMF\n" +"z/Gzwspzn4i2Yh6hySShrctMmW8YL3OM8LsBXzBhp/rG2uHlsxmIsc13DA6HWt61\n" +"ffY72uNE6KckDGsQ4wPGP9q69y6g+X+TNio1KPbsILbePv6EjbO+rS8FiS4njPlg\n" +"SPYry1RaUvxzxTkswIzdE1tjJrUiqpbWlTGxrH9N4OszoLm45Pc784KLULrjKIoi\n" +"Q+vRsGrcMBAa+kDowWU6H1ryKR7KOhzRTcf2uqLE/W3ezaRwmOG+ETmoVFwbhk2X\n" +"OlbXEM9fWP+INvFkr6Z93VYL2jGkCjV7e3xXmre/Lb92fUcYi6t5dwzfV8gJnIoG\n" +"eCHd0K8NrQK0ipVk/7zcPDKOPeo9Y5aj/f6X/pDHtb+Dd5sT+l82G/Tqy4DIYUYR\n" +"-----END SIGNATURE-----\n" +"r router2 AwMDAwMDAwMDAwMDAwMDAwMDAwM Tk5OTk5OTk5OTk5OTk5OTk5OTk4 2015-09-02 19:09:15 153.0.136.1 443 8000\n" +"s Running V2Dir\n" +"v 0.1.2.14\n" +"w Bandwidth=30 Measured=30\n" +"p reject 1-65535\n" +"id ed25519 none\n" +"m 9,10,11,12,13,14,15,16,17 sha256=xyzajkldsdsajdadlsdjaslsdksdjlsdjsdaskdaaa0\n" +"r router1 BQUFBQUFBQUFBQUFBQUFBQUFBQU TU1NTU1NTU1NTU1NTU1NTU1NTU0 2015-09-02 19:17:35 153.0.153.1 443 0\n" +"a [1:2:3::4]:4711\n" +"s Exit Fast Guard Running Stable Valid\n" +"v 0.2.0.5\n" +"w Bandwidth=120 Measured=120\n" +"p reject 1-65535\n" +"id ed25519 none\n" +"m 9,10,11,12,13,14,15,16,17 sha256=xyzajkldsdsajdadlsdjaslsdksdjlsdjsdaskdaaa1\n" +"r router3 MzMzMzMzMzMzMzMzMzMzMzMzMzM T09PT09PT09PT09PT09PT09PT08 2015-09-02 19:17:35 170.0.153.1 400 9999\n" +"s Authority Exit Fast Guard Running Stable V2Dir Valid\n" +"v 0.1.0.3\n" +"w Bandwidth=120\n" +"p reject 1-65535\n" +"id ed25519 none\n" +"m 9,10,11,12,13,14,15,16,17 " +"sha256=xyzajkldsdsajdadlsdjaslsdksdjlsdjsdaskdaaa2\n" +"r router4 NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ Ly8vLy8vLy8vLy8vLy8vLy8vLy8 2015-09-02 19:17:35 192.0.2.3 500 1999\n" +"s Running V2Dir\n" +"v 0.1.6.3\n" +"w Bandwidth=30\n" +"p reject 1-65535\n" +"id ed25519 none\n" +"m 9,10,11,12,13,14,15,16,17 sha256=xyzajkldsdsajdadlsdjaslsdksdjlsdjsdaskdaaa3\n" +"directory-footer\n" +"directory-signature D867ACF56A9D229B35C25F0090BC9867E906BE69 CBF56A83368A5150F1A9AAADAFB4D77F8C4170E2\n" +"-----BEGIN SIGNATURE-----\n" +"AHiWcHe+T3XbnlQqvqSAk6RY3XmEy1+hM2u9Xk6BNi7BpQkEQM1f0vzRpgn5Dnf2\n" +"TXQWGUq9Z7jdSVnzWT3xqPA4zjw6eZkj+DKUtwq+oEDZGlf8eHTFmr0NAWfwZbk9\n" +"NAjbMTUXUP37N2XAZwkoCWwFCrrfMwXrL7OhZbj7ifo=\n" +"-----END SIGNATURE-----\n"; + diff --git a/src/tools/include.am b/src/tools/include.am index ebdd349cb1..38ed57546f 100644 --- a/src/tools/include.am +++ b/src/tools/include.am @@ -20,6 +20,7 @@ endif src_tools_tor_gencert_SOURCES = src/tools/tor-gencert.c src_tools_tor_gencert_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ src_tools_tor_gencert_LDADD = src/common/libor.a src/common/libor-crypto.a \ + $(LIBKECCAK_TINY) \ $(LIBDONNA) \ @TOR_LIB_MATH@ @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ \ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @@ -31,6 +32,7 @@ src_tools_tor_cov_gencert_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) src_tools_tor_cov_gencert_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ src_tools_tor_cov_gencert_LDADD = src/common/libor-testing.a \ src/common/libor-crypto-testing.a \ + $(LIBKECCAK_TINY) \ $(LIBDONNA) \ @TOR_LIB_MATH@ @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ \ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ @@ -39,6 +41,7 @@ endif src_tools_tor_checkkey_SOURCES = src/tools/tor-checkkey.c src_tools_tor_checkkey_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ src_tools_tor_checkkey_LDADD = src/common/libor.a src/common/libor-crypto.a \ + $(LIBKECCAK_TINY) \ $(LIBDONNA) \ @TOR_LIB_MATH@ @TOR_ZLIB_LIBS@ @TOR_OPENSSL_LIBS@ \ @TOR_LIB_WS32@ @TOR_LIB_GDI@ @CURVE25519_LIBS@ diff --git a/src/trunnel/README b/src/trunnel/README new file mode 100644 index 0000000000..e24aea0764 --- /dev/null +++ b/src/trunnel/README @@ -0,0 +1,21 @@ +This directory contains code for use with, and code made by, the +automatic code generation tool "Trunnel". + +Trunnel generates binary parsers and formatters for simple data +structures. It aims for human-readable, obviously-correct outputs over +maximum efficiency or flexibility. + +The .trunnel files are the inputs here; the .c and .h files are the outputs. + +To add a new structure: + - Add a new .trunnel file or expand an existing one to describe the format + of the structure. + - Regenerate the .c and .h files. To do this, you run + "scripts/codegen/run_trunnel.sh". You'll need trunnel installed. + - Add the .trunnel, .c, and .h files to include.am + +For the Trunnel source code, and more documentation about using Trunnel, +see https://gitweb.torproject.org/trunnel.git , especially + https://gitweb.torproject.org/trunnel.git/tree/README +and https://gitweb.torproject.org/trunnel.git/tree/doc/trunnel.md + diff --git a/src/trunnel/include.am b/src/trunnel/include.am index 9bf37fe58b..b1448b7cb2 100644 --- a/src/trunnel/include.am +++ b/src/trunnel/include.am @@ -36,3 +36,7 @@ src_trunnel_libor_trunnel_testing_a_CPPFLAGS = -DTRUNNEL_LOCAL_H $(AM_CPPFLAGS) src_trunnel_libor_trunnel_testing_a_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS) noinst_HEADERS+= $(TRUNNELHEADERS) + +EXTRA_DIST += \ + src/trunnel/README + diff --git a/src/win32/orconfig.h b/src/win32/orconfig.h index 8b687c8234..c0b14e5304 100644 --- a/src/win32/orconfig.h +++ b/src/win32/orconfig.h @@ -232,7 +232,7 @@ #define USING_TWOS_COMPLEMENT /* Version number of package */ -#define VERSION "0.2.7.6-dev" +#define VERSION "0.2.8.0-alpha-dev" |