aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/netbase.cpp222
-rwxr-xr-xtest/functional/rpc_users.py182
-rw-r--r--test/functional/test_framework/util.py23
3 files changed, 256 insertions, 171 deletions
diff --git a/src/netbase.cpp b/src/netbase.cpp
index 78b3b6ae3a..53e5985a0f 100644
--- a/src/netbase.cpp
+++ b/src/netbase.cpp
@@ -65,6 +65,12 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
{
CNetAddr addr;
+ // From our perspective, onion addresses are not hostnames but rather
+ // direct encodings of CNetAddr much like IPv4 dotted-decimal notation
+ // or IPv6 colon-separated hextet notation. Since we can't use
+ // getaddrinfo to decode them and it wouldn't make sense to resolve
+ // them, we return a network address representing it instead. See
+ // CNetAddr::SetSpecial(const std::string&) for more details.
if (addr.SetSpecial(std::string(pszName))) {
vIP.push_back(addr);
return true;
@@ -74,15 +80,25 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
struct addrinfo aiHint;
memset(&aiHint, 0, sizeof(struct addrinfo));
+ // We want a TCP port, which is a streaming socket type
aiHint.ai_socktype = SOCK_STREAM;
aiHint.ai_protocol = IPPROTO_TCP;
+ // We don't care which address family (IPv4 or IPv6) is returned
aiHint.ai_family = AF_UNSPEC;
+ // If we allow lookups of hostnames, use the AI_ADDRCONFIG flag to only
+ // return addresses whose family we have an address configured for.
+ //
+ // If we don't allow lookups, then use the AI_NUMERICHOST flag for
+ // getaddrinfo to only decode numerical network addresses and suppress
+ // hostname lookups.
aiHint.ai_flags = fAllowLookup ? AI_ADDRCONFIG : AI_NUMERICHOST;
struct addrinfo *aiRes = nullptr;
int nErr = getaddrinfo(pszName, nullptr, &aiHint, &aiRes);
if (nErr)
return false;
+ // Traverse the linked list starting with aiTrav, add all non-internal
+ // IPv4,v6 addresses to vIP while respecting nMaxSolutions.
struct addrinfo *aiTrav = aiRes;
while (aiTrav != nullptr && (nMaxSolutions == 0 || vIP.size() < nMaxSolutions))
{
@@ -112,6 +128,21 @@ bool static LookupIntern(const char *pszName, std::vector<CNetAddr>& vIP, unsign
return (vIP.size() > 0);
}
+/**
+ * Resolve a host string to its corresponding network addresses.
+ *
+ * @param pszName The string representing a host. Could be a name or a numerical
+ * IP address (IPv6 addresses in their bracketed form are
+ * allowed).
+ * @param[out] vIP The resulting network addresses to which the specified host
+ * string resolved.
+ *
+ * @returns Whether or not the specified host string successfully resolved to
+ * any resulting network addresses.
+ *
+ * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int)
+ * for additional parameter descriptions.
+ */
bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nMaxSolutions, bool fAllowLookup)
{
std::string strHost(pszName);
@@ -124,6 +155,12 @@ bool LookupHost(const char *pszName, std::vector<CNetAddr>& vIP, unsigned int nM
return LookupIntern(strHost.c_str(), vIP, nMaxSolutions, fAllowLookup);
}
+ /**
+ * Resolve a host string to its first corresponding network address.
+ *
+ * @see LookupHost(const char *, std::vector<CNetAddr>&, unsigned int, bool) for
+ * additional parameter descriptions.
+ */
bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup)
{
std::vector<CNetAddr> vIP;
@@ -134,6 +171,26 @@ bool LookupHost(const char *pszName, CNetAddr& addr, bool fAllowLookup)
return true;
}
+/**
+ * Resolve a service string to its corresponding service.
+ *
+ * @param pszName The string representing a service. Could be a name or a
+ * numerical IP address (IPv6 addresses should be in their
+ * disambiguated bracketed form), optionally followed by a port
+ * number. (e.g. example.com:8333 or
+ * [2001:db8:85a3:8d3:1319:8a2e:370:7348]:420)
+ * @param[out] vAddr The resulting services to which the specified service string
+ * resolved.
+ * @param portDefault The default port for resulting services if not specified
+ * by the service string.
+ * @param fAllowLookup Whether or not hostname lookups are permitted. If yes,
+ * external queries may be performed.
+ * @param nMaxSolutions The maximum number of results we want, specifying 0
+ * means "as many solutions as we get."
+ *
+ * @returns Whether or not the service string successfully resolved to any
+ * resulting services.
+ */
bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault, bool fAllowLookup, unsigned int nMaxSolutions)
{
if (pszName[0] == 0)
@@ -152,6 +209,12 @@ bool Lookup(const char *pszName, std::vector<CService>& vAddr, int portDefault,
return true;
}
+/**
+ * Resolve a service string to its first corresponding service.
+ *
+ * @see Lookup(const char *, std::vector<CService>&, int, bool, unsigned int)
+ * for additional parameter descriptions.
+ */
bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLookup)
{
std::vector<CService> vService;
@@ -162,6 +225,16 @@ bool Lookup(const char *pszName, CService& addr, int portDefault, bool fAllowLoo
return true;
}
+/**
+ * Resolve a service string with a numeric IP to its first corresponding
+ * service.
+ *
+ * @returns The resulting CService if the resolution was successful, [::]:0
+ * otherwise.
+ *
+ * @see Lookup(const char *, CService&, int, bool) for additional parameter
+ * descriptions.
+ */
CService LookupNumeric(const char *pszName, int portDefault)
{
CService addr;
@@ -231,22 +304,29 @@ enum class IntrRecvError {
};
/**
- * Read bytes from socket. This will either read the full number of bytes requested
- * or return False on error or timeout.
- * This function can be interrupted by calling InterruptSocks5()
+ * Try to read a specified number of bytes from a socket. Please read the "see
+ * also" section for more detail.
*
- * @param data Buffer to receive into
- * @param len Length of data to receive
- * @param timeout Timeout in milliseconds for receive operation
+ * @param data The buffer where the read bytes should be stored.
+ * @param len The number of bytes to read into the specified buffer.
+ * @param timeout The total timeout in milliseconds for this read.
+ * @param hSocket The socket (has to be in non-blocking mode) from which to read
+ * bytes.
*
- * @note This function requires that hSocket is in non-blocking mode.
+ * @returns An IntrRecvError indicating the resulting status of this read.
+ * IntrRecvError::OK only if all of the specified number of bytes were
+ * read.
+ *
+ * @see This function can be interrupted by calling InterruptSocks5(bool).
+ * Sockets can be made non-blocking with SetSocketNonBlocking(const
+ * SOCKET&, bool).
*/
static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, const SOCKET& hSocket)
{
int64_t curTime = GetTimeMillis();
int64_t endTime = curTime + timeout;
- // Maximum time to wait in one select call. It will take up until this time (in millis)
- // to break off in case of an interruption.
+ // Maximum time to wait for I/O readiness. It will take up until this time
+ // (in millis) to break off in case of an interruption.
const int64_t maxWait = 1000;
while (len > 0 && curTime < endTime) {
ssize_t ret = recv(hSocket, (char*)data, len, 0); // Optimistically try the recv first
@@ -261,6 +341,8 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c
if (!IsSelectableSocket(hSocket)) {
return IntrRecvError::NetworkError;
}
+ // Only wait at most maxWait milliseconds at a time, unless
+ // we're approaching the end of the specified total timeout
int timeout_ms = std::min(endTime - curTime, maxWait);
#ifdef USE_POLL
struct pollfd pollfd = {};
@@ -320,7 +402,24 @@ static std::string Socks5ErrorString(uint8_t err)
}
}
-/** Connect using SOCKS5 (as described in RFC1928) */
+/**
+ * Connect to a specified destination service through an already connected
+ * SOCKS5 proxy.
+ *
+ * @param strDest The destination fully-qualified domain name.
+ * @param port The destination port.
+ * @param auth The credentials with which to authenticate with the specified
+ * SOCKS5 proxy.
+ * @param hSocket The SOCKS5 proxy socket.
+ *
+ * @returns Whether or not the operation succeeded.
+ *
+ * @note The specified SOCKS5 proxy socket must already be connected to the
+ * SOCKS5 proxy.
+ *
+ * @see <a href="https://www.ietf.org/rfc/rfc1928.txt">RFC1928: SOCKS Protocol
+ * Version 5</a>
+ */
static bool Socks5(const std::string& strDest, int port, const ProxyCredentials *auth, const SOCKET& hSocket)
{
IntrRecvError recvr;
@@ -328,15 +427,15 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
if (strDest.size() > 255) {
return error("Hostname too long");
}
- // Accepted authentication methods
+ // Construct the version identifier/method selection message
std::vector<uint8_t> vSocks5Init;
- vSocks5Init.push_back(SOCKSVersion::SOCKS5);
+ vSocks5Init.push_back(SOCKSVersion::SOCKS5); // We want the SOCK5 protocol
if (auth) {
- vSocks5Init.push_back(0x02); // Number of methods
+ vSocks5Init.push_back(0x02); // 2 method identifiers follow...
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
vSocks5Init.push_back(SOCKS5Method::USER_PASS);
} else {
- vSocks5Init.push_back(0x01); // Number of methods
+ vSocks5Init.push_back(0x01); // 1 method identifier follows...
vSocks5Init.push_back(SOCKS5Method::NOAUTH);
}
ssize_t ret = send(hSocket, (const char*)vSocks5Init.data(), vSocks5Init.size(), MSG_NOSIGNAL);
@@ -440,8 +539,16 @@ static bool Socks5(const std::string& strDest, int port, const ProxyCredentials
return true;
}
+/**
+ * Try to create a socket file descriptor with specific properties in the
+ * communications domain (address family) of the specified service.
+ *
+ * For details on the desired properties, see the inline comments in the source
+ * code.
+ */
SOCKET CreateSocket(const CService &addrConnect)
{
+ // Create a sockaddr from the specified service.
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
if (!addrConnect.GetSockAddr((struct sockaddr*)&sockaddr, &len)) {
@@ -449,10 +556,13 @@ SOCKET CreateSocket(const CService &addrConnect)
return INVALID_SOCKET;
}
+ // Create a TCP socket in the address family of the specified service.
SOCKET hSocket = socket(((struct sockaddr*)&sockaddr)->sa_family, SOCK_STREAM, IPPROTO_TCP);
if (hSocket == INVALID_SOCKET)
return INVALID_SOCKET;
+ // Ensure that waiting for I/O on this socket won't result in undefined
+ // behavior.
if (!IsSelectableSocket(hSocket)) {
CloseSocket(hSocket);
LogPrintf("Cannot create connection: non-selectable socket created (fd >= FD_SETSIZE ?)\n");
@@ -461,17 +571,18 @@ SOCKET CreateSocket(const CService &addrConnect)
#ifdef SO_NOSIGPIPE
int set = 1;
- // Different way of disabling SIGPIPE on BSD
+ // Set the no-sigpipe option on the socket for BSD systems, other UNIXes
+ // should use the MSG_NOSIGNAL flag for every send.
setsockopt(hSocket, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int));
#endif
- //Disable Nagle's algorithm
+ // Set the no-delay option (disable Nagle's algorithm) on the TCP socket.
SetSocketNoDelay(hSocket);
- // Set to non-blocking
+ // Set the non-blocking option on the socket.
if (!SetSocketNonBlocking(hSocket, true)) {
CloseSocket(hSocket);
- LogPrintf("ConnectSocketDirectly: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
+ LogPrintf("CreateSocket: Setting socket to non-blocking failed, error %s\n", NetworkErrorString(WSAGetLastError()));
}
return hSocket;
}
@@ -486,8 +597,21 @@ static void LogConnectFailure(bool manual_connection, const char* fmt, const Arg
}
}
+/**
+ * Try to connect to the specified service on the specified socket.
+ *
+ * @param addrConnect The service to which to connect.
+ * @param hSocket The socket on which to connect.
+ * @param nTimeout Wait this many milliseconds for the connection to be
+ * established.
+ * @param manual_connection Whether or not the connection was manually requested
+ * (e.g. thru the addnode RPC)
+ *
+ * @returns Whether or not a connection was successfully made.
+ */
bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, int nTimeout, bool manual_connection)
{
+ // Create a sockaddr from the specified service.
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
if (hSocket == INVALID_SOCKET) {
@@ -498,12 +622,17 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i
LogPrintf("Cannot connect to %s: unsupported network\n", addrConnect.ToString());
return false;
}
+
+ // Connect to the addrConnect service on the hSocket socket.
if (connect(hSocket, (struct sockaddr*)&sockaddr, len) == SOCKET_ERROR)
{
int nErr = WSAGetLastError();
// WSAEINVAL is here because some legacy version of winsock uses it
if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL)
{
+ // Connection didn't actually fail, but is being established
+ // asynchronously. Thus, use async I/O api (select/poll)
+ // synchronously to check for successful connection with a timeout.
#ifdef USE_POLL
struct pollfd pollfd = {};
pollfd.fd = hSocket;
@@ -516,6 +645,10 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i
FD_SET(hSocket, &fdset);
int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout);
#endif
+ // Upon successful completion, both select and poll return the total
+ // number of file descriptors that have been selected. A value of 0
+ // indicates that the call timed out and no file descriptors have
+ // been selected.
if (nRet == 0)
{
LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString());
@@ -526,6 +659,11 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i
LogPrintf("select() for %s failed: %s\n", addrConnect.ToString(), NetworkErrorString(WSAGetLastError()));
return false;
}
+
+ // Even if the select/poll was successful, the connect might not
+ // have been successful. The reason for this failure is hidden away
+ // in the SO_ERROR for the socket in modern systems. We read it into
+ // nRet here.
socklen_t nRetSize = sizeof(nRet);
if (getsockopt(hSocket, SOL_SOCKET, SO_ERROR, (sockopt_arg_type)&nRet, &nRetSize) == SOCKET_ERROR)
{
@@ -569,6 +707,22 @@ bool GetProxy(enum Network net, proxyType &proxyInfoOut) {
return true;
}
+/**
+ * Set the name proxy to use for all connections to nodes specified by a
+ * hostname. After setting this proxy, connecting to a node sepcified by a
+ * hostname won't result in a local lookup of said hostname, rather, connect to
+ * the node by asking the name proxy for a proxy connection to the hostname,
+ * effectively delegating the hostname lookup to the specified proxy.
+ *
+ * This delegation increases privacy for those who set the name proxy as they no
+ * longer leak their external hostname queries to their DNS servers.
+ *
+ * @returns Whether or not the operation succeeded.
+ *
+ * @note SOCKS5's support for UDP-over-SOCKS5 has been considered, but no SOCK5
+ * server in common use (most notably Tor) actually implements UDP
+ * support, and a DNS resolver is beyond the scope of this project.
+ */
bool SetNameProxy(const proxyType &addrProxy) {
if (!addrProxy.IsValid())
return false;
@@ -599,6 +753,21 @@ bool IsProxy(const CNetAddr &addr) {
return false;
}
+/**
+ * Connect to a specified destination service through a SOCKS5 proxy by first
+ * connecting to the SOCKS5 proxy.
+ *
+ * @param proxy The SOCKS5 proxy.
+ * @param strDest The destination service to which to connect.
+ * @param port The destination port.
+ * @param hSocket The socket on which to connect to the SOCKS5 proxy.
+ * @param nTimeout Wait this many milliseconds for the connection to the SOCKS5
+ * proxy to be established.
+ * @param outProxyConnectionFailed[out] Whether or not the connection to the
+ * SOCKS5 proxy failed.
+ *
+ * @returns Whether or not the operation succeeded.
+ */
bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int port, const SOCKET& hSocket, int nTimeout, bool *outProxyConnectionFailed)
{
// first connect to proxy server
@@ -623,6 +792,17 @@ bool ConnectThroughProxy(const proxyType &proxy, const std::string& strDest, int
return true;
}
+/**
+ * Parse and resolve a specified subnet string into the appropriate internal
+ * representation.
+ *
+ * @param pszName A string representation of a subnet of the form `network
+ * address [ "/", ( CIDR-style suffix | netmask ) ]`(e.g.
+ * `2001:db8::/32`, `192.0.2.0/255.255.255.0`, or `8.8.8.8`).
+ * @param ret The resulting internal representation of a subnet.
+ *
+ * @returns Whether the operation succeeded or not.
+ */
bool LookupSubNet(const char* pszName, CSubNet& ret)
{
std::string strSubnet(pszName);
@@ -630,6 +810,8 @@ bool LookupSubNet(const char* pszName, CSubNet& ret)
std::vector<CNetAddr> vIP;
std::string strAddress = strSubnet.substr(0, slash);
+ // TODO: Use LookupHost(const char *, CNetAddr&, bool) instead to just get
+ // one CNetAddr.
if (LookupHost(strAddress.c_str(), vIP, 1, false))
{
CNetAddr network = vIP[0];
@@ -637,8 +819,8 @@ bool LookupSubNet(const char* pszName, CSubNet& ret)
{
std::string strNetmask = strSubnet.substr(slash + 1);
int32_t n;
- // IPv4 addresses start at offset 12, and first 12 bytes must match, so just offset n
- if (ParseInt32(strNetmask, &n)) { // If valid number, assume /24 syntax
+ if (ParseInt32(strNetmask, &n)) {
+ // If valid number, assume CIDR variable-length subnet masking
ret = CSubNet(network, n);
return ret.IsValid();
}
diff --git a/test/functional/rpc_users.py b/test/functional/rpc_users.py
index 102dd22594..8bbb3c04fa 100755
--- a/test/functional/rpc_users.py
+++ b/test/functional/rpc_users.py
@@ -20,6 +20,17 @@ import string
import configparser
import sys
+def call_with_auth(node, user, password):
+ url = urllib.parse.urlparse(node.url)
+ headers = {"Authorization": "Basic " + str_to_b64str('{}:{}'.format(user, password))}
+
+ conn = http.client.HTTPConnection(url.hostname, url.port)
+ conn.connect()
+ conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
+ resp = conn.getresponse()
+ conn.close()
+ return resp
+
class HTTPBasicsTest(BitcoinTestFramework):
def set_test_params(self):
@@ -28,15 +39,24 @@ class HTTPBasicsTest(BitcoinTestFramework):
def setup_chain(self):
super().setup_chain()
#Append rpcauth to bitcoin.conf before initialization
+ self.rtpassword = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM="
rpcauth = "rpcauth=rt:93648e835a54c573682c2eb19f882535$7681e9c5b74bdd85e78166031d2058e1069b3ed7ed967c93fc63abba06f31144"
- rpcauth2 = "rpcauth=rt2:f8607b1a88861fac29dfccf9b52ff9f$ff36a0c23c8c62b4846112e50fa888416e94c17bfd4c42f88fd8f55ec6a3137e"
- rpcuser = "rpcuser=rpcuser💻"
- rpcpassword = "rpcpassword=rpcpassword🔑"
- self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
+ self.rpcuser = "rpcuser💻"
+ self.rpcpassword = "rpcpassword🔑"
+
config = configparser.ConfigParser()
config.read_file(open(self.options.configfile))
gen_rpcauth = config['environment']['RPCAUTH']
+
+ # Generate RPCAUTH with specified password
+ self.rt2password = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI="
+ p = subprocess.Popen([sys.executable, gen_rpcauth, 'rt2', self.rt2password], stdout=subprocess.PIPE, universal_newlines=True)
+ lines = p.stdout.read().splitlines()
+ rpcauth2 = lines[1]
+
+ # Generate RPCAUTH without specifying password
+ self.user = ''.join(SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(10))
p = subprocess.Popen([sys.executable, gen_rpcauth, self.user], stdout=subprocess.PIPE, universal_newlines=True)
lines = p.stdout.read().splitlines()
rpcauth3 = lines[1]
@@ -47,160 +67,40 @@ class HTTPBasicsTest(BitcoinTestFramework):
f.write(rpcauth2+"\n")
f.write(rpcauth3+"\n")
with open(os.path.join(get_datadir_path(self.options.tmpdir, 1), "bitcoin.conf"), 'a', encoding='utf8') as f:
- f.write(rpcuser+"\n")
- f.write(rpcpassword+"\n")
-
- def run_test(self):
-
- ##################################################
- # Check correctness of the rpcauth config option #
- ##################################################
- url = urllib.parse.urlparse(self.nodes[0].url)
-
- #Old authpair
- authpair = url.username + ':' + url.password
-
- #New authpair generated via share/rpcauth tool
- password = "cA773lm788buwYe4g4WT+05pKyNruVKjQ25x3n0DQcM="
-
- #Second authpair with different username
- password2 = "8/F3uMDw4KSEbw96U3CA1C4X05dkHDN2BPFjTgZW4KI="
- authpairnew = "rt:"+password
+ f.write("rpcuser={}\n".format(self.rpcuser))
+ f.write("rpcpassword={}\n".format(self.rpcpassword))
+ def test_auth(self, node, user, password):
self.log.info('Correct...')
- headers = {"Authorization": "Basic " + str_to_b64str(authpair)}
+ assert_equal(200, call_with_auth(node, user, password).status)
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
-
- #Use new authpair to confirm both work
- self.log.info('Correct...')
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
-
- #Wrong login name with rt's password
self.log.info('Wrong...')
- authpairnew = "rtwrong:"+password
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
+ assert_equal(401, call_with_auth(node, user, password+'wrong').status)
- #Wrong password for rt
self.log.info('Wrong...')
- authpairnew = "rt:"+password+"wrong"
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+ assert_equal(401, call_with_auth(node, user+'wrong', password).status)
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
-
- #Correct for rt2
- self.log.info('Correct...')
- authpairnew = "rt2:"+password2
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
-
- #Wrong password for rt2
self.log.info('Wrong...')
- authpairnew = "rt2:"+password2+"wrong"
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
+ assert_equal(401, call_with_auth(node, user+'wrong', password+'wrong').status)
- #Correct for randomly generated user
- self.log.info('Correct...')
- authpairnew = self.user+":"+self.password
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
+ def run_test(self):
- #Wrong password for randomly generated user
- self.log.info('Wrong...')
- authpairnew = self.user+":"+self.password+"Wrong"
- headers = {"Authorization": "Basic " + str_to_b64str(authpairnew)}
+ ##################################################
+ # Check correctness of the rpcauth config option #
+ ##################################################
+ url = urllib.parse.urlparse(self.nodes[0].url)
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
+ self.test_auth(self.nodes[0], url.username, url.password)
+ self.test_auth(self.nodes[0], 'rt', self.rtpassword)
+ self.test_auth(self.nodes[0], 'rt2', self.rt2password)
+ self.test_auth(self.nodes[0], self.user, self.password)
###############################################################
# Check correctness of the rpcuser/rpcpassword config options #
###############################################################
url = urllib.parse.urlparse(self.nodes[1].url)
- # rpcuser and rpcpassword authpair
- self.log.info('Correct...')
- rpcuserauthpair = "rpcuser💻:rpcpassword🔑"
-
- headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 200)
- conn.close()
-
- #Wrong login name with rpcuser's password
- rpcuserauthpair = "rpcuserwrong:rpcpassword"
- headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
-
- #Wrong password for rpcuser
- self.log.info('Wrong...')
- rpcuserauthpair = "rpcuser:rpcpasswordwrong"
- headers = {"Authorization": "Basic " + str_to_b64str(rpcuserauthpair)}
-
- conn = http.client.HTTPConnection(url.hostname, url.port)
- conn.connect()
- conn.request('POST', '/', '{"method": "getbestblockhash"}', headers)
- resp = conn.getresponse()
- assert_equal(resp.status, 401)
- conn.close()
-
+ self.test_auth(self.nodes[1], self.rpcuser, self.rpcpassword)
if __name__ == '__main__':
HTTPBasicsTest ().main ()
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 26215083fb..efd962ea93 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -19,6 +19,7 @@ import time
from . import coverage
from .authproxy import AuthServiceProxy, JSONRPCException
+from io import BytesIO
logger = logging.getLogger("TestFramework.utils")
@@ -515,14 +516,13 @@ def gen_return_txouts():
for i in range(512):
script_pubkey = script_pubkey + "01"
# concatenate 128 txouts of above script_pubkey which we'll insert before the txout for change
- txouts = "81"
+ txouts = []
+ from .messages import CTxOut
+ txout = CTxOut()
+ txout.nValue = 0
+ txout.scriptPubKey = hex_str_to_bytes(script_pubkey)
for k in range(128):
- # add txout value
- txouts = txouts + "0000000000000000"
- # add length of script_pubkey
- txouts = txouts + "fd0402"
- # add script_pubkey
- txouts = txouts + script_pubkey
+ txouts.append(txout)
return txouts
# Create a spend of each passed-in utxo, splicing in "txouts" to each raw
@@ -530,6 +530,7 @@ def gen_return_txouts():
def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
addr = node.getnewaddress()
txids = []
+ from .messages import CTransaction
for _ in range(num):
t = utxos.pop()
inputs = [{"txid": t["txid"], "vout": t["vout"]}]
@@ -537,9 +538,11 @@ def create_lots_of_big_transactions(node, txouts, utxos, num, fee):
change = t['amount'] - fee
outputs[addr] = satoshi_round(change)
rawtx = node.createrawtransaction(inputs, outputs)
- newtx = rawtx[0:92]
- newtx = newtx + txouts
- newtx = newtx + rawtx[94:]
+ tx = CTransaction()
+ tx.deserialize(BytesIO(hex_str_to_bytes(rawtx)))
+ for txout in txouts:
+ tx.vout.append(txout)
+ newtx = tx.serialize().hex()
signresult = node.signrawtransactionwithwallet(newtx, None, "NONE")
txid = node.sendrawtransaction(signresult["hex"], 0)
txids.append(txid)