diff options
author | Wladimir J. van der Laan <laanwj@gmail.com> | 2015-09-04 11:48:54 +0200 |
---|---|---|
committer | Wladimir J. van der Laan <laanwj@gmail.com> | 2015-09-04 13:06:32 +0200 |
commit | 9aa90994ee85e549ddbe23a6f03e33d0edcd57b2 (patch) | |
tree | 1f5eb525df417378a8677084a5a01731bca76960 | |
parent | 4b437b227c596f23ab8983559552b8c4910d2b17 (diff) | |
parent | d52802551752140cf41f0d9a225a43e84404d3e9 (diff) |
Merge pull request #5677
d528025 Revert "rpc-tests: re-enable rpc-tests for Windows" (Wladimir J. van der Laan)
1e700c9 doc: update deps in build-unix.md after libevent (Wladimir J. van der Laan)
26c9b83 Move windows socket init to utility function (Wladimir J. van der Laan)
4be0b08 libevent: Windows reuseaddr workaround in depends (Cory Fields)
3a174cd Fix race condition between starting HTTP server thread and setting EventBase() (Wladimir J. van der Laan)
6d2bc22 Document options for new HTTP/RPC server in --help (Wladimir J. van der Laan)
be33f3f Implement RPCTimerHandler for Qt RPC console (Wladimir J. van der Laan)
57d85d9 doc: mention SSL support dropped for RPC in release notes (Wladimir J. van der Laan)
40b556d evhttpd implementation (Wladimir J. van der Laan)
ee2a42b tests: GET requests cannot have request body, use POST in rest.py (Wladimir J. van der Laan)
6e996d3 tests: fix qt payment test (Cory Fields)
3140ef9 build: build-system changes for libevent (Wladimir J. van der Laan)
a9af234 libevent: add depends (Cory Fields)
6a21dd5 Remove rpc_boostasiotocnetaddr test (Wladimir J. van der Laan)
8f9301c qa: Remove -rpckeepalive tests from httpbasics (Wladimir J. van der Laan)
51fcfc0 doc: remove documentation for rpcssl (Wladimir J. van der Laan)
38 files changed, 1502 insertions, 1153 deletions
diff --git a/configure.ac b/configure.ac index a524bded6f..07ee28f84e 100644 --- a/configure.ac +++ b/configure.ac @@ -664,6 +664,12 @@ if test x$use_pkgconfig = xyes; then if test x$use_qr != xno; then BITCOIN_QT_CHECK([PKG_CHECK_MODULES([QR], [libqrencode], [have_qrencode=yes], [have_qrencode=no])]) fi + if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$use_tests != xnononono; then + PKG_CHECK_MODULES([EVENT], [libevent],, [AC_MSG_ERROR(libevent not found.)]) + if test x$TARGET_OS != xwindows; then + PKG_CHECK_MODULES([EVENT_PTHREADS], [libevent_pthreads],, [AC_MSG_ERROR(libevent_pthreads not found.)]) + fi + fi ] ) else @@ -673,6 +679,14 @@ else AC_CHECK_HEADER([openssl/ssl.h],, AC_MSG_ERROR(libssl headers missing),) AC_CHECK_LIB([ssl], [main],SSL_LIBS=-lssl, AC_MSG_ERROR(libssl missing)) + if test x$build_bitcoin_utils$build_bitcoind$bitcoin_enable_qt$use_tests != xnononono; then + AC_CHECK_HEADER([event2/event.h],, AC_MSG_ERROR(libevent headers missing),) + AC_CHECK_LIB([event],[main],EVENT_LIBS=-levent,AC_MSG_ERROR(libevent missing)) + if test x$TARGET_OS != xwindows; then + AC_CHECK_LIB([event_pthreads],[main],EVENT_PTHREADS_LIBS=-levent_pthreads,AC_MSG_ERROR(libevent_pthreads missing)) + fi + fi + BITCOIN_QT_CHECK(AC_CHECK_LIB([protobuf] ,[main],[PROTOBUF_LIBS=-lprotobuf], BITCOIN_QT_FAIL(libprotobuf not found))) if test x$use_qr != xno; then BITCOIN_QT_CHECK([AC_CHECK_LIB([qrencode], [main],[QR_LIBS=-lqrencode], [have_qrencode=no])]) diff --git a/contrib/bitcoind.bash-completion b/contrib/bitcoind.bash-completion index 3cc959c0a6..1338d2f2b5 100644 --- a/contrib/bitcoind.bash-completion +++ b/contrib/bitcoind.bash-completion @@ -96,7 +96,7 @@ _bitcoind() { esac case "$cur" in - -conf=*|-pid=*|-loadblock=*|-wallet=*|-rpcsslcertificatechainfile=*|-rpcsslprivatekeyfile=*) + -conf=*|-pid=*|-loadblock=*|-wallet=*) cur="${cur#*=}" _filedir return 0 diff --git a/contrib/debian/examples/bitcoin.conf b/contrib/debian/examples/bitcoin.conf index ade80d60ea..62ffd7123a 100644 --- a/contrib/debian/examples/bitcoin.conf +++ b/contrib/debian/examples/bitcoin.conf @@ -95,15 +95,6 @@ # running on another host using this option: #rpcconnect=127.0.0.1 -# Use Secure Sockets Layer (also known as TLS or HTTPS) to communicate -# with Bitcoin -server or bitcoind -#rpcssl=1 - -# OpenSSL settings used when rpcssl=1 -#rpcsslciphers=TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH -#rpcsslcertificatechainfile=server.cert -#rpcsslprivatekeyfile=server.pem - # Transaction Fee Changes in 0.10.0 # Send transactions as zero-fee transactions if possible (default: 0) diff --git a/contrib/debian/manpages/bitcoin-cli.1 b/contrib/debian/manpages/bitcoin-cli.1 index f953ae9db7..154b458739 100644 --- a/contrib/debian/manpages/bitcoin-cli.1 +++ b/contrib/debian/manpages/bitcoin-cli.1 @@ -36,9 +36,6 @@ Listen for JSON\-RPC connections on <port> (default: 8332 or testnet: 18332). .TP \fB\-rpcconnect=\fR<ip> Send commands to node running on <ip> (default: 127.0.0.1). -.TP -\fB\-rpcssl\fR=\fI1\fR -Use OpenSSL (https) for JSON\-RPC connections (see the Bitcoin Wiki for SSL setup instructions). .SH "SEE ALSO" \fBbitcoind\fP, \fBbitcoin.conf\fP diff --git a/contrib/debian/manpages/bitcoin-qt.1 b/contrib/debian/manpages/bitcoin-qt.1 index a023582bc0..05eadc94cd 100644 --- a/contrib/debian/manpages/bitcoin-qt.1 +++ b/contrib/debian/manpages/bitcoin-qt.1 @@ -178,18 +178,6 @@ Set maximum block size in bytes (default: 250000) .HP \fB\-blockprioritysize=\fR<n> Set maximum size of high\-priority/low\-fee transactions in bytes (default: 27000) .PP -SSL options: (see the Bitcoin Wiki for SSL setup instructions) -.TP -\fB\-rpcssl\fR -Use OpenSSL (https) for JSON\-RPC connections -.TP -\fB\-rpcsslcertificatechainfile=\fR<file.cert> -Server certificate file (default: server.cert) -.TP -\fB\-rpcsslprivatekeyfile=\fR<file.pem> -Server private key (default: server.pem) -.TP -\fB\-rpcsslciphers=\fR<ciphers> Acceptable ciphers (default: TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!AH:!3DES:@STRENGTH) .SS "UI options:" .TP diff --git a/contrib/debian/manpages/bitcoin.conf.5 b/contrib/debian/manpages/bitcoin.conf.5 index 8a0078d5d5..0cf4d991e3 100644 --- a/contrib/debian/manpages/bitcoin.conf.5 +++ b/contrib/debian/manpages/bitcoin.conf.5 @@ -46,16 +46,6 @@ Listen for RPC connections on this TCP port. \fBrpcconnect=\fR\fI'127.0.0.1'\fR You can use *bitcoin* or *bitcoind(1)* to send commands to *bitcoin*/*bitcoind(1)* running on another host using this option. .TP -\fBrpcssl=\fR\fI'1'\fR -Use Secure Sockets Layer (also known as TLS or HTTPS) to communicate with *bitcoin* '\-server' or *bitcoind(1)*. Example of OpenSSL settings used when *rpcssl*='1': -.TP -\fB\-rpcsslciphers=\fR<ciphers> -Acceptable ciphers (default: TLSv1+HIGH:\:!SSLv2:\:!aNULL:\:!eNULL:\:!AH:\:!3DES:\:@STRENGTH) -.TP -\fBrpcsslcertificatechainfile=\fR\fI'server.cert'\fR -.TP -\fBrpcsslprivatekeyfile=\fR\fI'server.pem'\fR -.TP .SH MISCELLANEOUS OPTIONS .TP \fBgen=\fR[\fI'0'\fR|\fI'1'\fR] diff --git a/contrib/debian/manpages/bitcoind.1 b/contrib/debian/manpages/bitcoind.1 index c225b9f3e9..5b0f2921aa 100644 --- a/contrib/debian/manpages/bitcoind.1 +++ b/contrib/debian/manpages/bitcoind.1 @@ -62,20 +62,6 @@ Allow JSON\-RPC connections from specified IP address .TP \fB\-rpcconnect=\fR<ip> Send commands to node running on <ip> -.PP -SSL options: (see the Bitcoin Wiki for SSL setup instructions) -.TP -\fB\-rpcssl\fR=\fI1\fR -Use OpenSSL (https) for JSON\-RPC connections -.TP -\fB\-rpcsslcertificatchainfile=\fR<file.cert> -Server certificate file (default: server.cert) -.TP -\fB\-rpcsslprivatekeyfile=\fR<file.pem> -Server private key (default: server.pem) -.TP -\fB\-rpcsslciphers=\fR<ciphers> -Acceptable ciphers (default: TLSv1+HIGH:\:!SSLv2:\:!aNULL:\:!eNULL:\:!AH:\:!3DES:\:@STRENGTH) .TP \-? This help message diff --git a/depends/packages/libevent.mk b/depends/packages/libevent.mk new file mode 100644 index 0000000000..2e9be1e98c --- /dev/null +++ b/depends/packages/libevent.mk @@ -0,0 +1,31 @@ +package=libevent +$(package)_version=2.0.22 +$(package)_download_path=https://github.com/libevent/libevent/releases/download/release-2.0.22-stable +$(package)_file_name=$(package)-$($(package)_version)-stable.tar.gz +$(package)_sha256_hash=71c2c49f0adadacfdbe6332a372c38cf9c8b7895bb73dabeaa53cdcc1d4e1fa3 +$(package)_patches=reuseaddr.patch + +define $(package)_preprocess_cmds + patch -p1 < $($(package)_patch_dir)/reuseaddr.patch +endef + +define $(package)_set_vars + $(package)_config_opts=--disable-shared --disable-openssl --disable-libevent-regress + $(package)_config_opts_release=--disable-debug-mode + $(package)_config_opts_linux=--with-pic +endef + +define $(package)_config_cmds + $($(package)_autoconf) +endef + +define $(package)_build_cmds + $(MAKE) +endef + +define $(package)_stage_cmds + $(MAKE) DESTDIR=$($(package)_staging_dir) install +endef + +define $(package)_postprocess_cmds +endef diff --git a/depends/packages/packages.mk b/depends/packages/packages.mk index 03908aba59..a0d377bb43 100644 --- a/depends/packages/packages.mk +++ b/depends/packages/packages.mk @@ -1,4 +1,4 @@ -packages:=boost openssl +packages:=boost openssl libevent native_packages := native_ccache native_comparisontool qt_native_packages = native_protobuf diff --git a/depends/patches/libevent/reuseaddr.patch b/depends/patches/libevent/reuseaddr.patch new file mode 100644 index 0000000000..58695c11f5 --- /dev/null +++ b/depends/patches/libevent/reuseaddr.patch @@ -0,0 +1,21 @@ +--- old/evutil.c 2015-08-28 19:26:23.488765923 -0400 ++++ new/evutil.c 2015-08-28 19:27:41.392767019 -0400 +@@ -321,15 +321,16 @@ + int + evutil_make_listen_socket_reuseable(evutil_socket_t sock) + { +-#ifndef WIN32 + int one = 1; ++#ifndef WIN32 + /* REUSEADDR on Unix means, "don't hang on to this address after the + * listener is closed." On Windows, though, it means "don't keep other + * processes from binding to this address while we're using it. */ + return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, + (ev_socklen_t)sizeof(one)); + #else +- return 0; ++ return setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*) &one, ++ (ev_socklen_t)sizeof(one)); + #endif + } + diff --git a/doc/build-unix.md b/doc/build-unix.md index 95e501ac69..e02a5e42f7 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -31,8 +31,9 @@ These dependencies are required: Library | Purpose | Description ------------|------------------|---------------------- - libssl | SSL Support | Secure communications - libboost | Boost | C++ Library + libssl | Crypto | Random Number Generation, Elliptic Curve Cryptography + libboost | Utility | Library for threading, data structures, etc + libevent | Networking | OS independent asynchronous networking Optional dependencies: @@ -57,7 +58,7 @@ Dependency Build Instructions: Ubuntu & Debian ---------------------------------------------- Build requirements: - sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev + sudo apt-get install build-essential libtool autotools-dev autoconf pkg-config libssl-dev libevent-dev For Ubuntu 12.04 and later or Debian 7 and later libboost-all-dev has to be installed: diff --git a/doc/release-notes.md b/doc/release-notes.md index 9caac4f7ae..2fc601d435 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -4,6 +4,29 @@ release-notes at release time) Notable changes =============== +SSL support for RPC dropped +---------------------------- + +SSL support for RPC, previously enabled by the option `rpcssl` has been dropped +from both the client and the server. This was done in preparation for removing +the dependency on OpenSSL for the daemon completely. + +Trying to use `rpcssl` will result in an error: + + Error: SSL mode for RPC (-rpcssl) is no longer supported. + +If you are one of the few people that relies on this feature, a flexible +migration path is to use `stunnel`. This is an utility that can tunnel +arbitrary TCP connections inside SSL. On e.g. Ubuntu it can be installed with: + + sudo apt-get install stunnel4 + +Then, to tunnel a SSL connection on 28332 to a RPC server bound on localhost on port 18332 do: + + stunnel -d 28332 -r 127.0.0.1:18332 -p stunnel.pem -P '' + +It can also be set up system-wide in inetd style. + Random-cookie RPC authentication --------------------------------- diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index f928cd09e3..514bdf5640 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -8,6 +8,11 @@ CURDIR=$(cd $(dirname "$0"); pwd) export BITCOIND=${REAL_BITCOIND} export BITCOINCLI=${REAL_BITCOINCLI} +if [ "x${EXEEXT}" = "x.exe" ]; then + echo "Win tests currently disabled" + exit 0 +fi + #Run the tests testScripts=( diff --git a/qa/rpc-tests/httpbasics.py b/qa/rpc-tests/httpbasics.py index 8ccb821286..b66533543d 100755 --- a/qa/rpc-tests/httpbasics.py +++ b/qa/rpc-tests/httpbasics.py @@ -22,7 +22,7 @@ except ImportError: class HTTPBasicsTest (BitcoinTestFramework): def setup_nodes(self): - return start_nodes(4, self.options.tmpdir, extra_args=[['-rpckeepalive=1'], ['-rpckeepalive=0'], [], []]) + return start_nodes(4, self.options.tmpdir) def run_test(self): @@ -84,9 +84,8 @@ class HTTPBasicsTest (BitcoinTestFramework): conn.request('POST', '/', '{"method": "getbestblockhash"}', headers) out1 = conn.getresponse().read(); assert_equal('"error":null' in out1, True) - assert_equal(conn.sock!=None, False) #connection must be closed because keep-alive was set to false - #node2 (third node) is running with standard keep-alive parameters which means keep-alive is off + #node2 (third node) is running with standard keep-alive parameters which means keep-alive is on urlNode2 = urlparse.urlparse(self.nodes[2].url) authpair = urlNode2.username + ':' + urlNode2.password headers = {"Authorization": "Basic " + base64.b64encode(authpair)} diff --git a/qa/rpc-tests/rest.py b/qa/rpc-tests/rest.py index 3a035f996c..e084ad55ab 100755 --- a/qa/rpc-tests/rest.py +++ b/qa/rpc-tests/rest.py @@ -32,10 +32,20 @@ def deser_uint256(f): r += t << (i * 32) return r -#allows simple http get calls with a request body -def http_get_call(host, port, path, requestdata = '', response_object = 0): +#allows simple http get calls +def http_get_call(host, port, path, response_object = 0): conn = httplib.HTTPConnection(host, port) - conn.request('GET', path, requestdata) + conn.request('GET', path) + + if response_object: + return conn.getresponse() + + return conn.getresponse().read() + +#allows simple http post calls with a request body +def http_post_call(host, port, path, requestdata = '', response_object = 0): + conn = httplib.HTTPConnection(host, port) + conn.request('POST', path, requestdata) if response_object: return conn.getresponse() @@ -137,7 +147,7 @@ class RESTTest (BitcoinTestFramework): binaryRequest += binascii.unhexlify(vintx); binaryRequest += pack("i", 0); - bin_response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest) + bin_response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', binaryRequest) output = StringIO.StringIO() output.write(bin_response) output.seek(0) @@ -175,14 +185,14 @@ class RESTTest (BitcoinTestFramework): #do some invalid requests json_request = '{"checkmempool' - response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True) + response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'json', json_request, True) assert_equal(response.status, 500) #must be a 500 because we send a invalid json request json_request = '{"checkmempool' - response = http_get_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True) + response = http_post_call(url.hostname, url.port, '/rest/getutxos'+self.FORMAT_SEPARATOR+'bin', json_request, True) assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request - response = http_get_call(url.hostname, url.port, '/rest/getutxos/checkmempool'+self.FORMAT_SEPARATOR+'bin', '', True) + response = http_post_call(url.hostname, url.port, '/rest/getutxos/checkmempool'+self.FORMAT_SEPARATOR+'bin', '', True) assert_equal(response.status, 500) #must be a 500 because we send a invalid bin request #test limits @@ -190,14 +200,14 @@ class RESTTest (BitcoinTestFramework): for x in range(0, 20): json_request += txid+'-'+str(n)+'/' json_request = json_request.rstrip("/") - response = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True) + response = http_post_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True) assert_equal(response.status, 500) #must be a 500 because we exceeding the limits json_request = '/checkmempool/' for x in range(0, 15): json_request += txid+'-'+str(n)+'/' json_request = json_request.rstrip("/"); - response = http_get_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True) + response = http_post_call(url.hostname, url.port, '/rest/getutxos'+json_request+self.FORMAT_SEPARATOR+'json', '', True) assert_equal(response.status, 200) #must be a 500 because we exceeding the limits self.nodes[0].generate(1) #generate block to not affect upcoming tests @@ -208,27 +218,27 @@ class RESTTest (BitcoinTestFramework): ################ # check binary format - response = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"bin", "", True) + response = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"bin", True) assert_equal(response.status, 200) assert_greater_than(int(response.getheader('content-length')), 80) response_str = response.read() # compare with block header - response_header = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"bin", "", True) + response_header = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"bin", True) assert_equal(response_header.status, 200) assert_equal(int(response_header.getheader('content-length')), 80) response_header_str = response_header.read() assert_equal(response_str[0:80], response_header_str) # check block hex format - response_hex = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"hex", "", True) + response_hex = http_get_call(url.hostname, url.port, '/rest/block/'+bb_hash+self.FORMAT_SEPARATOR+"hex", True) assert_equal(response_hex.status, 200) assert_greater_than(int(response_hex.getheader('content-length')), 160) response_hex_str = response_hex.read() assert_equal(response_str.encode("hex")[0:160], response_hex_str[0:160]) # compare with hex block header - response_header_hex = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"hex", "", True) + response_header_hex = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"hex", True) assert_equal(response_header_hex.status, 200) assert_greater_than(int(response_header_hex.getheader('content-length')), 160) response_header_hex_str = response_header_hex.read() @@ -241,7 +251,7 @@ class RESTTest (BitcoinTestFramework): assert_equal(block_json_obj['hash'], bb_hash) # compare with json block header - response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"json", "", True) + response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/1/'+bb_hash+self.FORMAT_SEPARATOR+"json", True) assert_equal(response_header_json.status, 200) response_header_json_str = response_header_json.read() json_obj = json.loads(response_header_json_str, parse_float=decimal.Decimal) @@ -265,7 +275,7 @@ class RESTTest (BitcoinTestFramework): #see if we can get 5 headers in one response self.nodes[1].generate(5) self.sync_all() - response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/5/'+bb_hash+self.FORMAT_SEPARATOR+"json", "", True) + response_header_json = http_get_call(url.hostname, url.port, '/rest/headers/5/'+bb_hash+self.FORMAT_SEPARATOR+"json", True) assert_equal(response_header_json.status, 200) response_header_json_str = response_header_json.read() json_obj = json.loads(response_header_json_str) @@ -278,7 +288,7 @@ class RESTTest (BitcoinTestFramework): assert_equal(json_obj['txid'], tx_hash) # check hex format response - hex_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"hex", "", True) + hex_string = http_get_call(url.hostname, url.port, '/rest/tx/'+tx_hash+self.FORMAT_SEPARATOR+"hex", True) assert_equal(hex_string.status, 200) assert_greater_than(int(response.getheader('content-length')), 10) diff --git a/src/Makefile.am b/src/Makefile.am index 456dfc32ce..390e9f1436 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -98,6 +98,8 @@ BITCOIN_CORE_H = \ eccryptoverify.h \ ecwrapper.h \ hash.h \ + httprpc.h \ + httpserver.h \ init.h \ key.h \ keystore.h \ @@ -164,13 +166,15 @@ obj/build.h: FORCE libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h # server: shared between bitcoind and bitcoin-qt -libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) +libbitcoin_server_a_CPPFLAGS = $(BITCOIN_INCLUDES) $(MINIUPNPC_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_server_a_SOURCES = \ addrman.cpp \ alert.cpp \ bloom.cpp \ chain.cpp \ checkpoints.cpp \ + httprpc.cpp \ + httpserver.cpp \ init.cpp \ leveldbwrapper.cpp \ main.cpp \ @@ -320,12 +324,12 @@ if ENABLE_WALLET bitcoind_LDADD += libbitcoin_wallet.a endif -bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) +bitcoind_LDADD += $(BOOST_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) # # bitcoin-cli binary # bitcoin_cli_SOURCES = bitcoin-cli.cpp -bitcoin_cli_CPPFLAGS = $(BITCOIN_INCLUDES) +bitcoin_cli_CPPFLAGS = $(BITCOIN_INCLUDES) $(EVENT_CFLAGS) bitcoin_cli_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) if TARGET_WINDOWS @@ -338,7 +342,7 @@ bitcoin_cli_LDADD = \ $(LIBBITCOIN_UTIL) \ $(LIBSECP256K1) -bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) +bitcoin_cli_LDADD += $(BOOST_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(EVENT_LIBS) # # bitcoin-tx binary # diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 748f2b14d5..8d60aca25c 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -362,7 +362,8 @@ if ENABLE_WALLET qt_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif qt_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) $(LIBMEMENV) \ - $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) + $(BOOST_LIBS) $(QT_LIBS) $(QT_DBUS_LIBS) $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) qt_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) qt_bitcoin_qt_LIBTOOLFLAGS = --tag CXX diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index c5392cf307..4250bb8f3b 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -32,7 +32,8 @@ qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_WALLET) endif qt_test_test_bitcoin_qt_LDADD += $(LIBBITCOIN_CLI) $(LIBBITCOIN_COMMON) $(LIBBITCOIN_UTIL) $(LIBBITCOIN_CRYPTO) $(LIBBITCOIN_UNIVALUE) $(LIBLEVELDB) \ $(LIBMEMENV) $(BOOST_LIBS) $(QT_DBUS_LIBS) $(QT_TEST_LIBS) $(QT_LIBS) \ - $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) + $(QR_LIBS) $(PROTOBUF_LIBS) $(BDB_LIBS) $(SSL_LIBS) $(CRYPTO_LIBS) $(MINIUPNPC_LIBS) $(LIBSECP256K1) \ + $(EVENT_PTHREADS_LIBS) $(EVENT_LIBS) qt_test_test_bitcoin_qt_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(QT_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) CLEAN_BITCOIN_QT_TEST = $(TEST_QT_MOC_CPP) qt/test/*.gcda qt/test/*.gcno diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 1c5a312874..866c6f2d44 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -11,6 +11,12 @@ #include "utilstrencodings.h" #include <boost/filesystem/operations.hpp> +#include <stdio.h> + +#include <event2/event.h> +#include <event2/http.h> +#include <event2/buffer.h> +#include <event2/keyvalq_struct.h> #include "univalue/univalue.h" @@ -32,9 +38,6 @@ std::string HelpMessageCli() strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); - strUsage += HelpMessageGroup(_("SSL options: (see the Bitcoin Wiki for SSL setup instructions)")); - strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections")); - return strUsage; } @@ -92,32 +95,75 @@ static bool AppInitRPC(int argc, char* argv[]) fprintf(stderr, "Error: Invalid combination of -regtest and -testnet.\n"); return false; } + if (GetBoolArg("-rpcssl", false)) + { + fprintf(stderr, "Error: SSL mode for RPC (-rpcssl) is no longer supported.\n"); + return false; + } return true; } -UniValue CallRPC(const string& strMethod, const UniValue& params) + +/** Reply structure for request_done to fill in */ +struct HTTPReply { - // Connect to localhost - bool fUseSSL = GetBoolArg("-rpcssl", false); - boost::asio::io_service io_service; - boost::asio::ssl::context context(io_service, boost::asio::ssl::context::sslv23); - context.set_options(boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::no_sslv3); - boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslStream(io_service, context); - SSLIOStreamDevice<boost::asio::ip::tcp> d(sslStream, fUseSSL); - boost::iostreams::stream< SSLIOStreamDevice<boost::asio::ip::tcp> > stream(d); - - const bool fConnected = d.connect(GetArg("-rpcconnect", "127.0.0.1"), GetArg("-rpcport", itostr(BaseParams().RPCPort()))); - if (!fConnected) - throw CConnectionFailed("couldn't connect to server"); + int status; + std::string body; +}; + +static void http_request_done(struct evhttp_request *req, void *ctx) +{ + HTTPReply *reply = static_cast<HTTPReply*>(ctx); + + if (req == NULL) { + /* If req is NULL, it means an error occurred while connecting, but + * I'm not sure how to find out which one. We also don't really care. + */ + reply->status = 0; + return; + } - // Find credentials to use + reply->status = evhttp_request_get_response_code(req); + + struct evbuffer *buf = evhttp_request_get_input_buffer(req); + if (buf) + { + size_t size = evbuffer_get_length(buf); + const char *data = (const char*)evbuffer_pullup(buf, size); + if (data) + reply->body = std::string(data, size); + evbuffer_drain(buf, size); + } +} + +UniValue CallRPC(const string& strMethod, const UniValue& params) +{ + std::string host = GetArg("-rpcconnect", "127.0.0.1"); + int port = GetArg("-rpcport", BaseParams().RPCPort()); + + // Create event base + struct event_base *base = event_base_new(); // TODO RAII + if (!base) + throw runtime_error("cannot create event_base"); + + // Synchronously look up hostname + struct evhttp_connection *evcon = evhttp_connection_base_new(base, NULL, host.c_str(), port); // TODO RAII + if (evcon == NULL) + throw runtime_error("create connection failed"); + evhttp_connection_set_timeout(evcon, GetArg("-rpctimeout", 30)); + + HTTPReply response; + struct evhttp_request *req = evhttp_request_new(http_request_done, (void*)&response); // TODO RAII + if (req == NULL) + throw runtime_error("create http request failed"); + + // Get credentials std::string strRPCUserColonPass; if (mapArgs["-rpcpassword"] == "") { // Try fall back to cookie-based authentication if no password is provided if (!GetAuthCookie(&strRPCUserColonPass)) { throw runtime_error(strprintf( - _("You must set rpcpassword=<password> in the configuration file:\n%s\n" - "If the file does not exist, create it with owner-readable-only file permissions."), + _("Could not locate RPC credentials. No authentication cookie could be found, and no rpcpassword is set in the configuration file (%s)"), GetConfigFile().string().c_str())); } @@ -125,34 +171,41 @@ UniValue CallRPC(const string& strMethod, const UniValue& params) strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; } - // HTTP basic authentication - map<string, string> mapRequestHeaders; - mapRequestHeaders["Authorization"] = string("Basic ") + EncodeBase64(strRPCUserColonPass); - - // Send request - string strRequest = JSONRPCRequest(strMethod, params, 1); - string strPost = HTTPPost(strRequest, mapRequestHeaders); - stream << strPost << std::flush; - - // Receive HTTP reply status - int nProto = 0; - int nStatus = ReadHTTPStatus(stream, nProto); + struct evkeyvalq *output_headers = evhttp_request_get_output_headers(req); + assert(output_headers); + evhttp_add_header(output_headers, "Host", host.c_str()); + evhttp_add_header(output_headers, "Connection", "close"); + evhttp_add_header(output_headers, "Authorization", (std::string("Basic ") + EncodeBase64(strRPCUserColonPass)).c_str()); + + // Attach request data + std::string strRequest = JSONRPCRequest(strMethod, params, 1); + struct evbuffer * output_buffer = evhttp_request_get_output_buffer(req); + assert(output_buffer); + evbuffer_add(output_buffer, strRequest.data(), strRequest.size()); + + int r = evhttp_make_request(evcon, req, EVHTTP_REQ_POST, "/"); + if (r != 0) { + evhttp_connection_free(evcon); + event_base_free(base); + throw CConnectionFailed("send http request failed"); + } - // Receive HTTP reply message headers and body - map<string, string> mapHeaders; - string strReply; - ReadHTTPMessage(stream, mapHeaders, strReply, nProto, std::numeric_limits<size_t>::max()); + event_base_dispatch(base); + evhttp_connection_free(evcon); + event_base_free(base); - if (nStatus == HTTP_UNAUTHORIZED) + if (response.status == 0) + throw CConnectionFailed("couldn't connect to server"); + else if (response.status == HTTP_UNAUTHORIZED) throw runtime_error("incorrect rpcuser or rpcpassword (authorization failed)"); - else if (nStatus >= 400 && nStatus != HTTP_BAD_REQUEST && nStatus != HTTP_NOT_FOUND && nStatus != HTTP_INTERNAL_SERVER_ERROR) - throw runtime_error(strprintf("server returned HTTP error %d", nStatus)); - else if (strReply.empty()) + else if (response.status >= 400 && response.status != HTTP_BAD_REQUEST && response.status != HTTP_NOT_FOUND && response.status != HTTP_INTERNAL_SERVER_ERROR) + throw runtime_error(strprintf("server returned HTTP error %d", response.status)); + else if (response.body.empty()) throw runtime_error("no response from server"); // Parse reply UniValue valReply(UniValue::VSTR); - if (!valReply.read(strReply)) + if (!valReply.read(response.body)) throw runtime_error("couldn't parse reply from server"); const UniValue& reply = valReply.get_obj(); if (reply.empty()) @@ -248,6 +301,10 @@ int CommandLineRPC(int argc, char *argv[]) int main(int argc, char* argv[]) { SetupEnvironment(); + if (!SetupNetworking()) { + fprintf(stderr, "Error: Initializing networking failed\n"); + exit(1); + } try { if(!AppInitRPC(argc, argv)) diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 39bb301f44..b512f74c22 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -10,11 +10,16 @@ #include "noui.h" #include "scheduler.h" #include "util.h" +#include "httpserver.h" +#include "httprpc.h" +#include "rpcserver.h" #include <boost/algorithm/string/predicate.hpp> #include <boost/filesystem.hpp> #include <boost/thread.hpp> +#include <stdio.h> + /* Introduction text for doxygen: */ /*! \mainpage Developer documentation @@ -44,7 +49,7 @@ void WaitForShutdown(boost::thread_group* threadGroup) } if (threadGroup) { - threadGroup->interrupt_all(); + Interrupt(*threadGroup); threadGroup->join_all(); } } @@ -154,7 +159,7 @@ bool AppInit(int argc, char* argv[]) if (!fRet) { - threadGroup.interrupt_all(); + Interrupt(threadGroup); // threadGroup.join_all(); was left out intentionally here, because we didn't re-test all of // the startup-failure cases to make sure they don't result in a hang due to some // thread-blocking-waiting-for-another-thread-during-startup case diff --git a/src/httprpc.cpp b/src/httprpc.cpp new file mode 100644 index 0000000000..98ac750bb1 --- /dev/null +++ b/src/httprpc.cpp @@ -0,0 +1,193 @@ +#include "httprpc.h" + +#include "base58.h" +#include "chainparams.h" +#include "httpserver.h" +#include "rpcprotocol.h" +#include "rpcserver.h" +#include "random.h" +#include "sync.h" +#include "util.h" +#include "utilstrencodings.h" +#include "ui_interface.h" + +#include <boost/algorithm/string.hpp> // boost::trim + +/** Simple one-shot callback timer to be used by the RPC mechanism to e.g. + * re-lock the wellet. + */ +class HTTPRPCTimer : public RPCTimerBase +{ +public: + HTTPRPCTimer(struct event_base* eventBase, boost::function<void(void)>& func, int64_t millis) : + ev(eventBase, false, func) + { + struct timeval tv; + tv.tv_sec = millis/1000; + tv.tv_usec = (millis%1000)*1000; + ev.trigger(&tv); + } +private: + HTTPEvent ev; +}; + +class HTTPRPCTimerInterface : public RPCTimerInterface +{ +public: + HTTPRPCTimerInterface(struct event_base* base) : base(base) + { + } + const char* Name() + { + return "HTTP"; + } + RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis) + { + return new HTTPRPCTimer(base, func, millis); + } +private: + struct event_base* base; +}; + + +/* Pre-base64-encoded authentication token */ +static std::string strRPCUserColonPass; +/* Stored RPC timer interface (for unregistration) */ +static HTTPRPCTimerInterface* httpRPCTimerInterface = 0; + +static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const UniValue& id) +{ + // Send error reply from json-rpc error object + int nStatus = HTTP_INTERNAL_SERVER_ERROR; + int code = find_value(objError, "code").get_int(); + + if (code == RPC_INVALID_REQUEST) + nStatus = HTTP_BAD_REQUEST; + else if (code == RPC_METHOD_NOT_FOUND) + nStatus = HTTP_NOT_FOUND; + + std::string strReply = JSONRPCReply(NullUniValue, objError, id); + + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(nStatus, strReply); +} + +static bool RPCAuthorized(const std::string& strAuth) +{ + if (strRPCUserColonPass.empty()) // Belt-and-suspenders measure if InitRPCAuthentication was not called + return false; + if (strAuth.substr(0, 6) != "Basic ") + return false; + std::string strUserPass64 = strAuth.substr(6); + boost::trim(strUserPass64); + std::string strUserPass = DecodeBase64(strUserPass64); + return TimingResistantEqual(strUserPass, strRPCUserColonPass); +} + +static bool HTTPReq_JSONRPC(HTTPRequest* req, const std::string &) +{ + // JSONRPC handles only POST + if (req->GetRequestMethod() != HTTPRequest::POST) { + req->WriteReply(HTTP_BAD_METHOD, "JSONRPC server handles only POST requests"); + return false; + } + // Check authorization + std::pair<bool, std::string> authHeader = req->GetHeader("authorization"); + if (!authHeader.first) { + req->WriteReply(HTTP_UNAUTHORIZED); + return false; + } + + if (!RPCAuthorized(authHeader.second)) { + LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", req->GetPeer().ToString()); + + /* Deter brute-forcing + If this results in a DoS the user really + shouldn't have their RPC port exposed. */ + MilliSleep(250); + + req->WriteReply(HTTP_UNAUTHORIZED); + return false; + } + + JSONRequest jreq; + try { + // Parse request + UniValue valRequest; + if (!valRequest.read(req->ReadBody())) + throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); + + std::string strReply; + // singleton request + if (valRequest.isObject()) { + jreq.parse(valRequest); + + UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); + + // Send reply + strReply = JSONRPCReply(result, NullUniValue, jreq.id); + + // array of requests + } else if (valRequest.isArray()) + strReply = JSONRPCExecBatch(valRequest.get_array()); + else + throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); + + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strReply); + } catch (const UniValue& objError) { + JSONErrorReply(req, objError, jreq.id); + return false; + } catch (const std::exception& e) { + JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); + return false; + } + return true; +} + +static bool InitRPCAuthentication() +{ + if (mapArgs["-rpcpassword"] == "") + { + LogPrintf("No rpcpassword set - using random cookie authentication\n"); + if (!GenerateAuthCookie(&strRPCUserColonPass)) { + uiInterface.ThreadSafeMessageBox( + _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode + "", CClientUIInterface::MSG_ERROR); + return false; + } + } else { + strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; + } + return true; +} + +bool StartHTTPRPC() +{ + LogPrint("rpc", "Starting HTTP RPC server\n"); + if (!InitRPCAuthentication()) + return false; + + RegisterHTTPHandler("/", true, HTTPReq_JSONRPC); + + assert(EventBase()); + httpRPCTimerInterface = new HTTPRPCTimerInterface(EventBase()); + RPCRegisterTimerInterface(httpRPCTimerInterface); + return true; +} + +void InterruptHTTPRPC() +{ + LogPrint("rpc", "Interrupting HTTP RPC server\n"); +} + +void StopHTTPRPC() +{ + LogPrint("rpc", "Stopping HTTP RPC server\n"); + UnregisterHTTPHandler("/", true); + if (httpRPCTimerInterface) { + RPCUnregisterTimerInterface(httpRPCTimerInterface); + delete httpRPCTimerInterface; + httpRPCTimerInterface = 0; + } +} diff --git a/src/httprpc.h b/src/httprpc.h new file mode 100644 index 0000000000..d354457188 --- /dev/null +++ b/src/httprpc.h @@ -0,0 +1,37 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_HTTPRPC_H +#define BITCOIN_HTTPRPC_H + +#include <string> +#include <map> + +class HTTPRequest; + +/** Start HTTP RPC subsystem. + * Precondition; HTTP and RPC has been started. + */ +bool StartHTTPRPC(); +/** Interrupt HTTP RPC subsystem. + */ +void InterruptHTTPRPC(); +/** Stop HTTP RPC subsystem. + * Precondition; HTTP and RPC has been stopped. + */ +void StopHTTPRPC(); + +/** Start HTTP REST subsystem. + * Precondition; HTTP and RPC has been started. + */ +bool StartREST(); +/** Interrupt RPC REST subsystem. + */ +void InterruptREST(); +/** Stop HTTP REST subsystem. + * Precondition; HTTP and RPC has been stopped. + */ +void StopREST(); + +#endif diff --git a/src/httpserver.cpp b/src/httpserver.cpp new file mode 100644 index 0000000000..7e599b1d78 --- /dev/null +++ b/src/httpserver.cpp @@ -0,0 +1,575 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "httpserver.h" + +#include "chainparamsbase.h" +#include "compat.h" +#include "util.h" +#include "netbase.h" +#include "rpcprotocol.h" // For HTTP status codes +#include "sync.h" +#include "ui_interface.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> + +#include <event2/event.h> +#include <event2/http.h> +#include <event2/thread.h> +#include <event2/buffer.h> +#include <event2/util.h> +#include <event2/keyvalq_struct.h> + +#ifdef EVENT__HAVE_NETINET_IN_H +#include <netinet/in.h> +#ifdef _XOPEN_SOURCE_EXTENDED +#include <arpa/inet.h> +#endif +#endif + +#include <boost/algorithm/string/case_conv.hpp> // for to_lower() +#include <boost/foreach.hpp> +#include <boost/scoped_ptr.hpp> + +/** HTTP request work item */ +class HTTPWorkItem : public HTTPClosure +{ +public: + HTTPWorkItem(HTTPRequest* req, const std::string &path, const HTTPRequestHandler& func): + req(req), path(path), func(func) + { + } + void operator()() + { + func(req.get(), path); + } + + boost::scoped_ptr<HTTPRequest> req; + +private: + std::string path; + HTTPRequestHandler func; +}; + +/** Simple work queue for distributing work over multiple threads. + * Work items are simply callable objects. + */ +template <typename WorkItem> +class WorkQueue +{ +private: + /** Mutex protects entire object */ + CWaitableCriticalSection cs; + CConditionVariable cond; + /* XXX in C++11 we can use std::unique_ptr here and avoid manual cleanup */ + std::deque<WorkItem*> queue; + bool running; + size_t maxDepth; + +public: + WorkQueue(size_t maxDepth) : running(true), + maxDepth(maxDepth) + { + } + /* Precondition: worker threads have all stopped */ + ~WorkQueue() + { + while (!queue.empty()) { + delete queue.front(); + queue.pop_front(); + } + } + /** Enqueue a work item */ + bool Enqueue(WorkItem* item) + { + boost::unique_lock<boost::mutex> lock(cs); + if (queue.size() >= maxDepth) { + return false; + } + queue.push_back(item); + cond.notify_one(); + return true; + } + /** Thread function */ + void Run() + { + while (running) { + WorkItem* i = 0; + { + boost::unique_lock<boost::mutex> lock(cs); + while (running && queue.empty()) + cond.wait(lock); + if (!running) + break; + i = queue.front(); + queue.pop_front(); + } + (*i)(); + delete i; + } + } + /** Interrupt and exit loops */ + void Interrupt() + { + boost::unique_lock<boost::mutex> lock(cs); + running = false; + cond.notify_all(); + } + + /** Return current depth of queue */ + size_t Depth() + { + boost::unique_lock<boost::mutex> lock(cs); + return queue.size(); + } +}; + +struct HTTPPathHandler +{ + HTTPPathHandler() {} + HTTPPathHandler(std::string prefix, bool exactMatch, HTTPRequestHandler handler): + prefix(prefix), exactMatch(exactMatch), handler(handler) + { + } + std::string prefix; + bool exactMatch; + HTTPRequestHandler handler; +}; + +/** HTTP module state */ + +//! libevent event loop +static struct event_base* eventBase = 0; +//! HTTP server +struct evhttp* eventHTTP = 0; +//! List of subnets to allow RPC connections from +static std::vector<CSubNet> rpc_allow_subnets; +//! Work queue for handling longer requests off the event loop thread +static WorkQueue<HTTPClosure>* workQueue = 0; +//! Handlers for (sub)paths +std::vector<HTTPPathHandler> pathHandlers; + +/** Check if a network address is allowed to access the HTTP server */ +static bool ClientAllowed(const CNetAddr& netaddr) +{ + if (!netaddr.IsValid()) + return false; + BOOST_FOREACH (const CSubNet& subnet, rpc_allow_subnets) + if (subnet.Match(netaddr)) + return true; + return false; +} + +/** Initialize ACL list for HTTP server */ +static bool InitHTTPAllowList() +{ + rpc_allow_subnets.clear(); + rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet + rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost + if (mapMultiArgs.count("-rpcallowip")) { + const std::vector<std::string>& vAllow = mapMultiArgs["-rpcallowip"]; + BOOST_FOREACH (std::string strAllow, vAllow) { + CSubNet subnet(strAllow); + if (!subnet.IsValid()) { + uiInterface.ThreadSafeMessageBox( + strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow), + "", CClientUIInterface::MSG_ERROR); + return false; + } + rpc_allow_subnets.push_back(subnet); + } + } + std::string strAllowed; + BOOST_FOREACH (const CSubNet& subnet, rpc_allow_subnets) + strAllowed += subnet.ToString() + " "; + LogPrint("http", "Allowing HTTP connections from: %s\n", strAllowed); + return true; +} + +/** HTTP request method as string - use for logging only */ +static std::string RequestMethodString(HTTPRequest::RequestMethod m) +{ + switch (m) { + case HTTPRequest::GET: + return "GET"; + break; + case HTTPRequest::POST: + return "POST"; + break; + case HTTPRequest::HEAD: + return "HEAD"; + break; + case HTTPRequest::PUT: + return "PUT"; + break; + default: + return "unknown"; + } +} + +/** HTTP request callback */ +static void http_request_cb(struct evhttp_request* req, void* arg) +{ + std::auto_ptr<HTTPRequest> hreq(new HTTPRequest(req)); + + LogPrint("http", "Received a %s request for %s from %s\n", + RequestMethodString(hreq->GetRequestMethod()), hreq->GetURI(), hreq->GetPeer().ToString()); + + // Early address-based allow check + if (!ClientAllowed(hreq->GetPeer())) { + hreq->WriteReply(HTTP_FORBIDDEN); + return; + } + + // Early reject unknown HTTP methods + if (hreq->GetRequestMethod() == HTTPRequest::UNKNOWN) { + hreq->WriteReply(HTTP_BADMETHOD); + return; + } + + // Find registered handler for prefix + std::string strURI = hreq->GetURI(); + std::string path; + std::vector<HTTPPathHandler>::const_iterator i = pathHandlers.begin(); + std::vector<HTTPPathHandler>::const_iterator iend = pathHandlers.end(); + for (; i != iend; ++i) { + bool match = false; + if (i->exactMatch) + match = (strURI == i->prefix); + else + match = (strURI.substr(0, i->prefix.size()) == i->prefix); + if (match) { + path = strURI.substr(i->prefix.size()); + break; + } + } + + // Dispatch to worker thread + if (i != iend) { + std::auto_ptr<HTTPWorkItem> item(new HTTPWorkItem(hreq.release(), path, i->handler)); + assert(workQueue); + if (workQueue->Enqueue(item.get())) + item.release(); /* if true, queue took ownership */ + else + item->req->WriteReply(HTTP_INTERNAL, "Work queue depth exceeded"); + } else { + hreq->WriteReply(HTTP_NOTFOUND); + } +} + +/** Event dispatcher thread */ +static void ThreadHTTP(struct event_base* base, struct evhttp* http) +{ + RenameThread("bitcoin-http"); + LogPrint("http", "Entering http event loop\n"); + event_base_dispatch(base); + // Event loop will be interrupted by InterruptHTTPServer() + LogPrint("http", "Exited http event loop\n"); +} + +/** Bind HTTP server to specified addresses */ +static bool HTTPBindAddresses(struct evhttp* http) +{ + int defaultPort = GetArg("-rpcport", BaseParams().RPCPort()); + int nBound = 0; + std::vector<std::pair<std::string, uint16_t> > endpoints; + + // Determine what addresses to bind to + if (!mapArgs.count("-rpcallowip")) { // Default to loopback if not allowing external IPs + endpoints.push_back(std::make_pair("::1", defaultPort)); + endpoints.push_back(std::make_pair("127.0.0.1", defaultPort)); + if (mapArgs.count("-rpcbind")) { + LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n"); + } + } else if (mapArgs.count("-rpcbind")) { // Specific bind address + const std::vector<std::string>& vbind = mapMultiArgs["-rpcbind"]; + for (std::vector<std::string>::const_iterator i = vbind.begin(); i != vbind.end(); ++i) { + int port = defaultPort; + std::string host; + SplitHostPort(*i, port, host); + endpoints.push_back(std::make_pair(host, port)); + } + } else { // No specific bind address specified, bind to any + endpoints.push_back(std::make_pair("::", defaultPort)); + endpoints.push_back(std::make_pair("0.0.0.0", defaultPort)); + } + + // Bind addresses + for (std::vector<std::pair<std::string, uint16_t> >::iterator i = endpoints.begin(); i != endpoints.end(); ++i) { + LogPrint("http", "Binding RPC on address %s port %i\n", i->first, i->second); + if (evhttp_bind_socket(http, i->first.empty() ? NULL : i->first.c_str(), i->second) == 0) { + nBound += 1; + } else { + LogPrintf("Binding RPC on address %s port %i failed.\n", i->first, i->second); + } + } + return nBound > 0; +} + +/** Simple wrapper to set thread name and run work queue */ +static void HTTPWorkQueueRun(WorkQueue<HTTPClosure>* queue) +{ + RenameThread("bitcoin-httpworker"); + queue->Run(); +} + +bool InitHTTPServer() +{ + struct evhttp* http = 0; + struct event_base* base = 0; + + if (!InitHTTPAllowList()) + return false; + + if (GetBoolArg("-rpcssl", false)) { + uiInterface.ThreadSafeMessageBox( + "SSL mode for RPC (-rpcssl) is no longer supported.", + "", CClientUIInterface::MSG_ERROR); + return false; + } + +#ifdef WIN32 + evthread_use_windows_threads(); +#else + evthread_use_pthreads(); +#endif + + base = event_base_new(); // XXX RAII + if (!base) { + LogPrintf("Couldn't create an event_base: exiting\n"); + return false; + } + + /* Create a new evhttp object to handle requests. */ + http = evhttp_new(base); // XXX RAII + if (!http) { + LogPrintf("couldn't create evhttp. Exiting.\n"); + event_base_free(base); + return false; + } + + evhttp_set_timeout(http, GetArg("-rpctimeout", DEFAULT_HTTP_TIMEOUT)); + evhttp_set_max_body_size(http, MAX_SIZE); + evhttp_set_gencb(http, http_request_cb, NULL); + + if (!HTTPBindAddresses(http)) { + LogPrintf("Unable to bind any endpoint for RPC server\n"); + evhttp_free(http); + event_base_free(base); + return false; + } + + LogPrint("http", "Initialized HTTP server\n"); + int workQueueDepth = std::max((long)GetArg("-rpcworkqueue", DEFAULT_HTTP_WORKQUEUE), 1L); + LogPrintf("HTTP: creating work queue of depth %d\n", workQueueDepth); + + workQueue = new WorkQueue<HTTPClosure>(workQueueDepth); + eventBase = base; + eventHTTP = http; + return true; +} + +bool StartHTTPServer(boost::thread_group& threadGroup) +{ + LogPrint("http", "Starting HTTP server\n"); + int rpcThreads = std::max((long)GetArg("-rpcthreads", DEFAULT_HTTP_THREADS), 1L); + LogPrintf("HTTP: starting %d worker threads\n", rpcThreads); + threadGroup.create_thread(boost::bind(&ThreadHTTP, eventBase, eventHTTP)); + + for (int i = 0; i < rpcThreads; i++) + threadGroup.create_thread(boost::bind(&HTTPWorkQueueRun, workQueue)); + return true; +} + +void InterruptHTTPServer() +{ + LogPrint("http", "Interrupting HTTP server\n"); + if (eventBase) + event_base_loopbreak(eventBase); + if (workQueue) + workQueue->Interrupt(); +} + +void StopHTTPServer() +{ + LogPrint("http", "Stopping HTTP server\n"); + delete workQueue; + if (eventHTTP) { + evhttp_free(eventHTTP); + eventHTTP = 0; + } + if (eventBase) { + event_base_free(eventBase); + eventBase = 0; + } +} + +struct event_base* EventBase() +{ + return eventBase; +} + +static void httpevent_callback_fn(evutil_socket_t, short, void* data) +{ + // Static handler: simply call inner handler + HTTPEvent *self = ((HTTPEvent*)data); + self->handler(); + if (self->deleteWhenTriggered) + delete self; +} + +HTTPEvent::HTTPEvent(struct event_base* base, bool deleteWhenTriggered, const boost::function<void(void)>& handler): + deleteWhenTriggered(deleteWhenTriggered), handler(handler) +{ + ev = event_new(base, -1, 0, httpevent_callback_fn, this); + assert(ev); +} +HTTPEvent::~HTTPEvent() +{ + event_free(ev); +} +void HTTPEvent::trigger(struct timeval* tv) +{ + if (tv == NULL) + event_active(ev, 0, 0); // immediately trigger event in main thread + else + evtimer_add(ev, tv); // trigger after timeval passed +} +HTTPRequest::HTTPRequest(struct evhttp_request* req) : req(req), + replySent(false) +{ +} +HTTPRequest::~HTTPRequest() +{ + if (!replySent) { + // Keep track of whether reply was sent to avoid request leaks + LogPrintf("%s: Unhandled request\n", __func__); + WriteReply(HTTP_INTERNAL, "Unhandled request"); + } + // evhttpd cleans up the request, as long as a reply was sent. +} + +std::pair<bool, std::string> HTTPRequest::GetHeader(const std::string& hdr) +{ + const struct evkeyvalq* headers = evhttp_request_get_input_headers(req); + assert(headers); + const char* val = evhttp_find_header(headers, hdr.c_str()); + if (val) + return std::make_pair(true, val); + else + return std::make_pair(false, ""); +} + +std::string HTTPRequest::ReadBody() +{ + struct evbuffer* buf = evhttp_request_get_input_buffer(req); + if (!buf) + return ""; + size_t size = evbuffer_get_length(buf); + /** Trivial implementation: if this is ever a performance bottleneck, + * internal copying can be avoided in multi-segment buffers by using + * evbuffer_peek and an awkward loop. Though in that case, it'd be even + * better to not copy into an intermediate string but use a stream + * abstraction to consume the evbuffer on the fly in the parsing algorithm. + */ + const char* data = (const char*)evbuffer_pullup(buf, size); + if (!data) // returns NULL in case of empty buffer + return ""; + std::string rv(data, size); + evbuffer_drain(buf, size); + return rv; +} + +void HTTPRequest::WriteHeader(const std::string& hdr, const std::string& value) +{ + struct evkeyvalq* headers = evhttp_request_get_output_headers(req); + assert(headers); + evhttp_add_header(headers, hdr.c_str(), value.c_str()); +} + +/** Closure sent to main thread to request a reply to be sent to + * a HTTP request. + * Replies must be sent in the main loop in the main http thread, + * this cannot be done from worker threads. + */ +void HTTPRequest::WriteReply(int nStatus, const std::string& strReply) +{ + assert(!replySent && req); + // Send event to main http thread to send reply message + struct evbuffer* evb = evhttp_request_get_output_buffer(req); + assert(evb); + evbuffer_add(evb, strReply.data(), strReply.size()); + HTTPEvent* ev = new HTTPEvent(eventBase, true, + boost::bind(evhttp_send_reply, req, nStatus, (const char*)NULL, (struct evbuffer *)NULL)); + ev->trigger(0); + replySent = true; + req = 0; // transferred back to main thread +} + +CService HTTPRequest::GetPeer() +{ + evhttp_connection* con = evhttp_request_get_connection(req); + CService peer; + if (con) { + // evhttp retains ownership over returned address string + const char* address = ""; + uint16_t port = 0; + evhttp_connection_get_peer(con, (char**)&address, &port); + peer = CService(address, port); + } + return peer; +} + +std::string HTTPRequest::GetURI() +{ + return evhttp_request_get_uri(req); +} + +HTTPRequest::RequestMethod HTTPRequest::GetRequestMethod() +{ + switch (evhttp_request_get_command(req)) { + case EVHTTP_REQ_GET: + return GET; + break; + case EVHTTP_REQ_POST: + return POST; + break; + case EVHTTP_REQ_HEAD: + return HEAD; + break; + case EVHTTP_REQ_PUT: + return PUT; + break; + default: + return UNKNOWN; + break; + } +} + +void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler) +{ + LogPrint("http", "Registering HTTP handler for %s (exactmath %d)\n", prefix, exactMatch); + pathHandlers.push_back(HTTPPathHandler(prefix, exactMatch, handler)); +} + +void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch) +{ + std::vector<HTTPPathHandler>::iterator i = pathHandlers.begin(); + std::vector<HTTPPathHandler>::iterator iend = pathHandlers.end(); + for (; i != iend; ++i) + if (i->prefix == prefix && i->exactMatch == exactMatch) + break; + if (i != iend) + { + LogPrint("http", "Unregistering HTTP handler for %s (exactmath %d)\n", prefix, exactMatch); + pathHandlers.erase(i); + } +} + diff --git a/src/httpserver.h b/src/httpserver.h new file mode 100644 index 0000000000..459c60c047 --- /dev/null +++ b/src/httpserver.h @@ -0,0 +1,149 @@ +// Copyright (c) 2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_HTTPSERVER_H +#define BITCOIN_HTTPSERVER_H + +#include <string> +#include <stdint.h> +#include <boost/thread.hpp> +#include <boost/scoped_ptr.hpp> +#include <boost/function.hpp> + +static const int DEFAULT_HTTP_THREADS=4; +static const int DEFAULT_HTTP_WORKQUEUE=16; +static const int DEFAULT_HTTP_TIMEOUT=30; + +struct evhttp_request; +struct event_base; +class CService; +class HTTPRequest; + +/** Initialize HTTP server. + * Call this before RegisterHTTPHandler or EventBase(). + */ +bool InitHTTPServer(); +/** Start HTTP server. + * This is separate from InitHTTPServer to give users race-condition-free time + * to register their handlers between InitHTTPServer and StartHTTPServer. + */ +bool StartHTTPServer(boost::thread_group& threadGroup); +/** Interrupt HTTP server threads */ +void InterruptHTTPServer(); +/** Stop HTTP server */ +void StopHTTPServer(); + +/** Handler for requests to a certain HTTP path */ +typedef boost::function<void(HTTPRequest* req, const std::string &)> HTTPRequestHandler; +/** Register handler for prefix. + * If multiple handlers match a prefix, the first-registered one will + * be invoked. + */ +void RegisterHTTPHandler(const std::string &prefix, bool exactMatch, const HTTPRequestHandler &handler); +/** Unregister handler for prefix */ +void UnregisterHTTPHandler(const std::string &prefix, bool exactMatch); + +/** Return evhttp event base. This can be used by submodules to + * queue timers or custom events. + */ +struct event_base* EventBase(); + +/** In-flight HTTP request. + * Thin C++ wrapper around evhttp_request. + */ +class HTTPRequest +{ +private: + struct evhttp_request* req; + bool replySent; + +public: + HTTPRequest(struct evhttp_request* req); + ~HTTPRequest(); + + enum RequestMethod { + UNKNOWN, + GET, + POST, + HEAD, + PUT + }; + + /** Get requested URI. + */ + std::string GetURI(); + + /** Get CService (address:ip) for the origin of the http request. + */ + CService GetPeer(); + + /** Get request method. + */ + RequestMethod GetRequestMethod(); + + /** + * Get the request header specified by hdr, or an empty string. + * Return an pair (isPresent,string). + */ + std::pair<bool, std::string> GetHeader(const std::string& hdr); + + /** + * Read request body. + * + * @note As this consumes the underlying buffer, call this only once. + * Repeated calls will return an empty string. + */ + std::string ReadBody(); + + /** + * Write output header. + * + * @note call this before calling WriteErrorReply or Reply. + */ + void WriteHeader(const std::string& hdr, const std::string& value); + + /** + * Write HTTP reply. + * nStatus is the HTTP status code to send. + * strReply is the body of the reply. Keep it empty to send a standard message. + * + * @note Can be called only once. As this will give the request back to the + * main thread, do not call any other HTTPRequest methods after calling this. + */ + void WriteReply(int nStatus, const std::string& strReply = ""); +}; + +/** Event handler closure. + */ +class HTTPClosure +{ +public: + virtual void operator()() = 0; + virtual ~HTTPClosure() {} +}; + +/** Event class. This can be used either as an cross-thread trigger or as a timer. + */ +class HTTPEvent +{ +public: + /** Create a new event. + * deleteWhenTriggered deletes this event object after the event is triggered (and the handler called) + * handler is the handler to call when the event is triggered. + */ + HTTPEvent(struct event_base* base, bool deleteWhenTriggered, const boost::function<void(void)>& handler); + ~HTTPEvent(); + + /** Trigger the event. If tv is 0, trigger it immediately. Otherwise trigger it after + * the given time has elapsed. + */ + void trigger(struct timeval* tv); + + bool deleteWhenTriggered; + boost::function<void(void)> handler; +private: + struct event* ev; +}; + +#endif // BITCOIN_HTTPSERVER_H diff --git a/src/init.cpp b/src/init.cpp index 3aebe4f7be..5759b4b428 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -16,6 +16,8 @@ #include "checkpoints.h" #include "compat/sanity.h" #include "consensus/validation.h" +#include "httpserver.h" +#include "httprpc.h" #include "key.h" #include "main.h" #include "miner.h" @@ -144,6 +146,15 @@ public: static CCoinsViewDB *pcoinsdbview = NULL; static CCoinsViewErrorCatcher *pcoinscatcher = NULL; +void Interrupt(boost::thread_group& threadGroup) +{ + InterruptHTTPServer(); + InterruptHTTPRPC(); + InterruptRPC(); + InterruptREST(); + threadGroup.interrupt_all(); +} + void Shutdown() { LogPrintf("%s: In progress...\n", __func__); @@ -158,7 +169,11 @@ void Shutdown() /// module was initialized. RenameThread("bitcoin-shutoff"); mempool.AddTransactionsUpdated(1); - StopRPCThreads(); + + StopHTTPRPC(); + StopREST(); + StopRPC(); + StopHTTPServer(); #ifdef ENABLE_WALLET if (pwalletMain) pwalletMain->Flush(false); @@ -372,7 +387,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-flushwallet", strprintf("Run a thread to flush wallet periodically (default: %u)", 1)); strUsage += HelpMessageOpt("-stopafterblockimport", strprintf("Stop running after importing blocks from disk (default: %u)", 0)); } - string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, mempoolrej, net, proxy, prune"; // Don't translate these and qt below + string debugCategories = "addrman, alert, bench, coindb, db, lock, rand, rpc, selectcoins, mempool, mempoolrej, net, proxy, prune, http"; // Don't translate these and qt below if (mode == HMM_BITCOIN_QT) debugCategories += ", qt"; strUsage += HelpMessageOpt("-debug=<category>", strprintf(_("Output debugging information (default: %u, supplying <category> is optional)"), 0) + ". " + @@ -422,14 +437,11 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-rpcpassword=<pw>", _("Password for JSON-RPC connections")); strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Listen for JSON-RPC connections on <port> (default: %u or testnet: %u)"), 8332, 18332)); strUsage += HelpMessageOpt("-rpcallowip=<ip>", _("Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times")); - strUsage += HelpMessageOpt("-rpcthreads=<n>", strprintf(_("Set the number of threads to service RPC calls (default: %d)"), 4)); - strUsage += HelpMessageOpt("-rpckeepalive", strprintf(_("RPC support for HTTP persistent connections (default: %d)"), 1)); - - strUsage += HelpMessageGroup(_("RPC SSL options: (see the Bitcoin Wiki for SSL setup instructions)")); - strUsage += HelpMessageOpt("-rpcssl", _("Use OpenSSL (https) for JSON-RPC connections")); - strUsage += HelpMessageOpt("-rpcsslcertificatechainfile=<file.cert>", strprintf(_("Server certificate file (default: %s)"), "server.cert")); - strUsage += HelpMessageOpt("-rpcsslprivatekeyfile=<file.pem>", strprintf(_("Server private key (default: %s)"), "server.pem")); - strUsage += HelpMessageOpt("-rpcsslciphers=<ciphers>", strprintf(_("Acceptable ciphers (default: %s)"), "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH")); + strUsage += HelpMessageOpt("-rpcthreads=<n>", strprintf(_("Set the number of threads to service RPC calls (default: %d)"), DEFAULT_HTTP_THREADS)); + if (showDebug) { + strUsage += HelpMessageOpt("-rpcworkqueue=<n>", strprintf("Set the depth of the work queue to service RPC calls (default: %d)", DEFAULT_HTTP_WORKQUEUE)); + strUsage += HelpMessageOpt("-rpctimeout=<n>", strprintf("Timeout during HTTP requests (default: %d)", DEFAULT_HTTP_TIMEOUT)); + } if (mode == HMM_BITCOIN_QT) { @@ -601,6 +613,23 @@ bool InitSanityCheck(void) return true; } +bool AppInitServers(boost::thread_group& threadGroup) +{ + RPCServer::OnStopped(&OnRPCStopped); + RPCServer::OnPreCommand(&OnRPCPreCommand); + if (!InitHTTPServer()) + return false; + if (!StartRPC()) + return false; + if (!StartHTTPRPC()) + return false; + if (GetBoolArg("-rest", false) && !StartREST()) + return false; + if (!StartHTTPServer(threadGroup)) + return false; + return true; +} + /** Initialize bitcoin. * @pre Parameters should be parsed and config file should be read. */ @@ -628,17 +657,12 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) typedef BOOL (WINAPI *PSETPROCDEPPOL)(DWORD); PSETPROCDEPPOL setProcDEPPol = (PSETPROCDEPPOL)GetProcAddress(GetModuleHandleA("Kernel32.dll"), "SetProcessDEPPolicy"); if (setProcDEPPol != NULL) setProcDEPPol(PROCESS_DEP_ENABLE); - - // Initialize Windows Sockets - WSADATA wsadata; - int ret = WSAStartup(MAKEWORD(2,2), &wsadata); - if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2) - { - return InitError(strprintf("Error: Winsock library failed to start (WSAStartup returned error %d)", ret)); - } #endif -#ifndef WIN32 + if (!SetupNetworking()) + return InitError("Error: Initializing networking failed"); + +#ifndef WIN32 if (GetBoolArg("-sysperms", false)) { #ifdef ENABLE_WALLET if (!GetBoolArg("-disablewallet", false)) @@ -961,9 +985,8 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) if (fServer) { uiInterface.InitMessage.connect(SetRPCWarmupStatus); - RPCServer::OnStopped(&OnRPCStopped); - RPCServer::OnPreCommand(&OnRPCPreCommand); - StartRPCThreads(); + if (!AppInitServers(threadGroup)) + return InitError(_("Unable to start HTTP server. See debug log for details.")); } int64_t nStart; diff --git a/src/init.h b/src/init.h index dcb2b29360..8cd51b0286 100644 --- a/src/init.h +++ b/src/init.h @@ -20,6 +20,8 @@ extern CWallet* pwalletMain; void StartShutdown(); bool ShutdownRequested(); +/** Interrupt threads */ +void Interrupt(boost::thread_group& threadGroup); void Shutdown(); bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 1da2d3e344..ea7f86d18e 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -266,13 +266,6 @@ void BitcoinCore::initialize() { qDebug() << __func__ << ": Running AppInit2 in thread"; int rv = AppInit2(threadGroup, scheduler); - if(rv) - { - /* Start a dummy RPC thread if no RPC thread is active yet - * to handle timeouts. - */ - StartDummyRPCThread(); - } Q_EMIT initializeResult(rv); } catch (const std::exception& e) { handleRunawayException(&e); @@ -286,7 +279,7 @@ void BitcoinCore::shutdown() try { qDebug() << __func__ << ": Running Shutdown in thread"; - threadGroup.interrupt_all(); + Interrupt(threadGroup); threadGroup.join_all(); Shutdown(); qDebug() << __func__ << ": Shutdown finished"; diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index d9d4f1d0ed..b742a47c9b 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -28,6 +28,7 @@ #include <QScrollBar> #include <QThread> #include <QTime> +#include <QTimer> #if QT_VERSION < 0x050000 #include <QUrl> @@ -66,6 +67,40 @@ Q_SIGNALS: void reply(int category, const QString &command); }; +/** Class for handling RPC timers + * (used for e.g. re-locking the wallet after a timeout) + */ +class QtRPCTimerBase: public QObject, public RPCTimerBase +{ + Q_OBJECT +public: + QtRPCTimerBase(boost::function<void(void)>& func, int64_t millis): + func(func) + { + timer.setSingleShot(true); + connect(&timer, SIGNAL(timeout()), this, SLOT(timeout())); + timer.start(millis); + } + ~QtRPCTimerBase() {} +private Q_SLOTS: + void timeout() { func(); } +private: + QTimer timer; + boost::function<void(void)> func; +}; + +class QtRPCTimerInterface: public RPCTimerInterface +{ +public: + ~QtRPCTimerInterface() {} + const char *Name() { return "Qt"; } + RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis) + { + return new QtRPCTimerBase(func, millis); + } +}; + + #include "rpcconsole.moc" /** @@ -232,6 +267,9 @@ RPCConsole::RPCConsole(const PlatformStyle *platformStyle, QWidget *parent) : ui->label_berkeleyDBVersion->hide(); ui->berkeleyDBVersion->hide(); #endif + // Register RPC timer interface + rpcTimerInterface = new QtRPCTimerInterface(); + RPCRegisterTimerInterface(rpcTimerInterface); startExecutor(); setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); @@ -246,6 +284,8 @@ RPCConsole::~RPCConsole() { GUIUtil::saveWindowGeometry("nRPCConsoleWindow", this); Q_EMIT stopExecutor(); + RPCUnregisterTimerInterface(rpcTimerInterface); + delete rpcTimerInterface; delete ui; } diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index b94efee84a..1409fca525 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -14,6 +14,7 @@ class ClientModel; class PlatformStyle; +class RPCTimerInterface; namespace Ui { class RPCConsole; @@ -108,6 +109,7 @@ private: NodeId cachedNodeid; QMenu *contextMenu; const PlatformStyle *platformStyle; + RPCTimerInterface *rpcTimerInterface; }; #endif // BITCOIN_QT_RPCCONSOLE_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index bb768f1325..f91de2008c 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -17,6 +17,8 @@ #include <QObject> #include <QTest> +#include <openssl/ssl.h> + #if defined(QT_STATICPLUGIN) && QT_VERSION < 0x050000 #include <QtPlugin> Q_IMPORT_PLUGIN(qcncodecs) @@ -36,6 +38,8 @@ int main(int argc, char *argv[]) QCoreApplication app(argc, argv); app.setApplicationName("Bitcoin-Qt-test"); + SSL_library_init(); + URITests test1; if (QTest::qExec(&test1) != 0) fInvalid = true; diff --git a/src/rest.cpp b/src/rest.cpp index 74d27e73bb..9405267067 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -7,6 +7,7 @@ #include "primitives/block.h" #include "primitives/transaction.h" #include "main.h" +#include "httpserver.h" #include "rpcserver.h" #include "streams.h" #include "sync.h" @@ -56,13 +57,6 @@ struct CCoin { } }; -class RestErr -{ -public: - enum HTTPStatusCode status; - string message; -}; - extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); extern UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool txDetails = false); extern UniValue mempoolInfoToJSON(); @@ -70,15 +64,14 @@ extern UniValue mempoolToJSON(bool fVerbose = false); extern void ScriptPubKeyToJSON(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex); extern UniValue blockheaderToJSON(const CBlockIndex* blockindex); -static RestErr RESTERR(enum HTTPStatusCode status, string message) +static bool RESTERR(HTTPRequest* req, enum HTTPStatusCode status, string message) { - RestErr re; - re.status = status; - re.message = message; - return re; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(status, message + "\r\n"); + return false; } -static enum RetFormat ParseDataFormat(vector<string>& params, const string strReq) +static enum RetFormat ParseDataFormat(vector<string>& params, const string& strReq) { boost::split(params, strReq, boost::is_any_of(".")); if (params.size() > 1) { @@ -115,28 +108,35 @@ static bool ParseHashStr(const string& strReq, uint256& v) return true; } -static bool rest_headers(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool CheckWarmup(HTTPRequest* req) +{ + std::string statusmessage; + if (RPCIsInWarmup(&statusmessage)) + return RESTERR(req, HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage); + return true; +} + +static bool rest_headers(HTTPRequest* req, + const std::string& strURIPart) { + if (!CheckWarmup(req)) + return false; vector<string> params; const RetFormat rf = ParseDataFormat(params, strURIPart); vector<string> path; boost::split(path, params[0], boost::is_any_of("/")); if (path.size() != 2) - throw RESTERR(HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>."); + return RESTERR(req, HTTP_BAD_REQUEST, "No header count specified. Use /rest/headers/<count>/<hash>.<ext>."); long count = strtol(path[0].c_str(), NULL, 10); if (count < 1 || count > 2000) - throw RESTERR(HTTP_BAD_REQUEST, "Header count out of range: " + path[0]); + return RESTERR(req, HTTP_BAD_REQUEST, "Header count out of range: " + path[0]); string hashStr = path[1]; uint256 hash; if (!ParseHashStr(hashStr, hash)) - throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); std::vector<const CBlockIndex *> headers; headers.reserve(count); @@ -160,28 +160,29 @@ static bool rest_headers(AcceptedConnection* conn, switch (rf) { case RF_BINARY: { string binaryHeader = ssHeader.str(); - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryHeader.size(), "application/octet-stream") << binaryHeader << std::flush; + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, binaryHeader); return true; } case RF_HEX: { string strHex = HexStr(ssHeader.begin(), ssHeader.end()) + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, strHex); return true; } - case RF_JSON: { UniValue jsonHeaders(UniValue::VARR); BOOST_FOREACH(const CBlockIndex *pindex, headers) { jsonHeaders.push_back(blockheaderToJSON(pindex)); } string strJSON = jsonHeaders.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } - default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: .bin, .hex)"); } } @@ -189,34 +190,33 @@ static bool rest_headers(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_block(AcceptedConnection* conn, +static bool rest_block(HTTPRequest* req, const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun, bool showTxDetails) { + if (!CheckWarmup(req)) + return false; vector<string> params; const RetFormat rf = ParseDataFormat(params, strURIPart); string hashStr = params[0]; uint256 hash; if (!ParseHashStr(hashStr, hash)) - throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); CBlock block; CBlockIndex* pblockindex = NULL; { LOCK(cs_main); if (mapBlockIndex.count(hash) == 0) - throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found"); + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); pblockindex = mapBlockIndex[hash]; if (fHavePruned && !(pblockindex->nStatus & BLOCK_HAVE_DATA) && pblockindex->nTx > 0) - throw RESTERR(HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not available (pruned data)"); if (!ReadBlockFromDisk(block, pblockindex)) - throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found"); + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); } CDataStream ssBlock(SER_NETWORK, PROTOCOL_VERSION); @@ -225,25 +225,28 @@ static bool rest_block(AcceptedConnection* conn, switch (rf) { case RF_BINARY: { string binaryBlock = ssBlock.str(); - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryBlock.size(), "application/octet-stream") << binaryBlock << std::flush; + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, binaryBlock); return true; } case RF_HEX: { string strHex = HexStr(ssBlock.begin(), ssBlock.end()) + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, strHex); return true; } case RF_JSON: { UniValue objBlock = blockToJSON(block, pblockindex, showTxDetails); string strJSON = objBlock.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); } } @@ -251,30 +254,20 @@ static bool rest_block(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_block_extended(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_block_extended(HTTPRequest* req, const std::string& strURIPart) { - return rest_block(conn, strURIPart, strRequest, mapHeaders, fRun, true); + return rest_block(req, strURIPart, true); } -static bool rest_block_notxdetails(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_block_notxdetails(HTTPRequest* req, const std::string& strURIPart) { - return rest_block(conn, strURIPart, strRequest, mapHeaders, fRun, false); + return rest_block(req, strURIPart, false); } -static bool rest_chaininfo(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_chaininfo(HTTPRequest* req, const std::string& strURIPart) { + if (!CheckWarmup(req)) + return false; vector<string> params; const RetFormat rf = ParseDataFormat(params, strURIPart); @@ -283,11 +276,12 @@ static bool rest_chaininfo(AcceptedConnection* conn, UniValue rpcParams(UniValue::VARR); UniValue chainInfoObject = getblockchaininfo(rpcParams, false); string strJSON = chainInfoObject.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); } } @@ -295,12 +289,10 @@ static bool rest_chaininfo(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_mempool_info(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_mempool_info(HTTPRequest* req, const std::string& strURIPart) { + if (!CheckWarmup(req)) + return false; vector<string> params; const RetFormat rf = ParseDataFormat(params, strURIPart); @@ -309,11 +301,12 @@ static bool rest_mempool_info(AcceptedConnection* conn, UniValue mempoolInfoObject = mempoolInfoToJSON(); string strJSON = mempoolInfoObject.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); } } @@ -321,12 +314,10 @@ static bool rest_mempool_info(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_mempool_contents(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_mempool_contents(HTTPRequest* req, const std::string& strURIPart) { + if (!CheckWarmup(req)) + return false; vector<string> params; const RetFormat rf = ParseDataFormat(params, strURIPart); @@ -335,11 +326,12 @@ static bool rest_mempool_contents(AcceptedConnection* conn, UniValue mempoolObject = mempoolToJSON(true); string strJSON = mempoolObject.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: json)"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: json)"); } } @@ -347,24 +339,22 @@ static bool rest_mempool_contents(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_tx(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_tx(HTTPRequest* req, const std::string& strURIPart) { + if (!CheckWarmup(req)) + return false; vector<string> params; const RetFormat rf = ParseDataFormat(params, strURIPart); string hashStr = params[0]; uint256 hash; if (!ParseHashStr(hashStr, hash)) - throw RESTERR(HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); + return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr); CTransaction tx; uint256 hashBlock = uint256(); if (!GetTransaction(hash, tx, hashBlock, true)) - throw RESTERR(HTTP_NOT_FOUND, hashStr + " not found"); + return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found"); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << tx; @@ -372,13 +362,15 @@ static bool rest_tx(AcceptedConnection* conn, switch (rf) { case RF_BINARY: { string binaryTx = ssTx.str(); - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, binaryTx.size(), "application/octet-stream") << binaryTx << std::flush; + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, binaryTx); return true; } case RF_HEX: { string strHex = HexStr(ssTx.begin(), ssTx.end()) + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, strHex); return true; } @@ -386,12 +378,13 @@ static bool rest_tx(AcceptedConnection* conn, UniValue objTx(UniValue::VOBJ); TxToJSON(tx, hashBlock, objTx); string strJSON = objTx.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); } } @@ -399,12 +392,10 @@ static bool rest_tx(AcceptedConnection* conn, return true; // continue to process further HTTP reqs on this cxn } -static bool rest_getutxos(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +static bool rest_getutxos(HTTPRequest* req, const std::string& strURIPart) { + if (!CheckWarmup(req)) + return false; vector<string> params; enum RetFormat rf = ParseDataFormat(params, strURIPart); @@ -416,8 +407,9 @@ static bool rest_getutxos(AcceptedConnection* conn, } // throw exception in case of a empty request - if (strRequest.length() == 0 && uriParts.size() == 0) - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); + std::string strRequestMutable = req->ReadBody(); + if (strRequestMutable.length() == 0 && uriParts.size() == 0) + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); bool fInputParsed = false; bool fCheckMemPool = false; @@ -441,7 +433,7 @@ static bool rest_getutxos(AcceptedConnection* conn, std::string strOutput = uriParts[i].substr(uriParts[i].find("-")+1); if (!ParseInt32(strOutput, &nOutput) || !IsHex(strTxid)) - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Parse error"); txid.SetHex(strTxid); vOutPoints.push_back(COutPoint(txid, (uint32_t)nOutput)); @@ -450,15 +442,13 @@ static bool rest_getutxos(AcceptedConnection* conn, if (vOutPoints.size() > 0) fInputParsed = true; else - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); } - string strRequestMutable = strRequest; //convert const string to string for allowing hex to bin converting - switch (rf) { case RF_HEX: { // convert hex to bin, continue then with bin part - std::vector<unsigned char> strRequestV = ParseHex(strRequest); + std::vector<unsigned char> strRequestV = ParseHex(strRequestMutable); strRequestMutable.assign(strRequestV.begin(), strRequestV.end()); } @@ -468,7 +458,7 @@ static bool rest_getutxos(AcceptedConnection* conn, if (strRequestMutable.size() > 0) { if (fInputParsed) //don't allow sending input over URI and HTTP RAW DATA - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Combination of URI scheme inputs and raw post data is not allowed"); CDataStream oss(SER_NETWORK, PROTOCOL_VERSION); oss << strRequestMutable; @@ -477,24 +467,24 @@ static bool rest_getutxos(AcceptedConnection* conn, } } catch (const std::ios_base::failure& e) { // abort in case of unreadable binary data - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Parse error"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Parse error"); } break; } case RF_JSON: { if (!fInputParsed) - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, "Error: empty request"); break; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); } } // limit max outpoints if (vOutPoints.size() > MAX_GETUTXOS_OUTPOINTS) - throw RESTERR(HTTP_INTERNAL_SERVER_ERROR, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size())); + return RESTERR(req, HTTP_INTERNAL_SERVER_ERROR, strprintf("Error: max outpoints exceeded (max: %d, tried: %d)", MAX_GETUTXOS_OUTPOINTS, vOutPoints.size())); // check spentness and form a bitmap (as well as a JSON capable human-readble string representation) vector<unsigned char> bitmap; @@ -544,7 +534,8 @@ static bool rest_getutxos(AcceptedConnection* conn, ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs; string ssGetUTXOResponseString = ssGetUTXOResponse.str(); - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, ssGetUTXOResponseString.size(), "application/octet-stream") << ssGetUTXOResponseString << std::flush; + req->WriteHeader("Content-Type", "application/octet-stream"); + req->WriteReply(HTTP_OK, ssGetUTXOResponseString); return true; } @@ -553,7 +544,8 @@ static bool rest_getutxos(AcceptedConnection* conn, ssGetUTXOResponse << chainActive.Height() << chainActive.Tip()->GetBlockHash() << bitmap << outs; string strHex = HexStr(ssGetUTXOResponse.begin(), ssGetUTXOResponse.end()) + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strHex, fRun, false, "text/plain") << std::flush; + req->WriteHeader("Content-Type", "text/plain"); + req->WriteReply(HTTP_OK, strHex); return true; } @@ -583,11 +575,12 @@ static bool rest_getutxos(AcceptedConnection* conn, // return json string string strJSON = objGetUTXOResponse.write() + "\n"; - conn->stream() << HTTPReply(HTTP_OK, strJSON, fRun) << std::flush; + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(HTTP_OK, strJSON); return true; } default: { - throw RESTERR(HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); + return RESTERR(req, HTTP_NOT_FOUND, "output format not found (available: " + AvailableDataFormatsString() + ")"); } } @@ -597,11 +590,7 @@ static bool rest_getutxos(AcceptedConnection* conn, static const struct { const char* prefix; - bool (*handler)(AcceptedConnection* conn, - const std::string& strURIPart, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun); + bool (*handler)(HTTPRequest* req, const std::string& strReq); } uri_prefixes[] = { {"/rest/tx/", rest_tx}, {"/rest/block/notxdetails/", rest_block_notxdetails}, @@ -613,29 +602,19 @@ static const struct { {"/rest/getutxos", rest_getutxos}, }; -bool HTTPReq_REST(AcceptedConnection* conn, - const std::string& strURI, - const string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun) +bool StartREST() { - try { - std::string statusmessage; - if (RPCIsInWarmup(&statusmessage)) - throw RESTERR(HTTP_SERVICE_UNAVAILABLE, "Service temporarily unavailable: " + statusmessage); - - for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) { - unsigned int plen = strlen(uri_prefixes[i].prefix); - if (strURI.substr(0, plen) == uri_prefixes[i].prefix) { - string strURIPart = strURI.substr(plen); - return uri_prefixes[i].handler(conn, strURIPart, strRequest, mapHeaders, fRun); - } - } - } catch (const RestErr& re) { - conn->stream() << HTTPReply(re.status, re.message + "\r\n", false, false, "text/plain") << std::flush; - return false; - } + for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) + RegisterHTTPHandler(uri_prefixes[i].prefix, false, uri_prefixes[i].handler); + return true; +} - conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush; - return false; +void InterruptREST() +{ +} + +void StopREST() +{ + for (unsigned int i = 0; i < ARRAYLEN(uri_prefixes); i++) + UnregisterHTTPHandler(uri_prefixes[i].prefix, false); } diff --git a/src/rpcprotocol.cpp b/src/rpcprotocol.cpp index 2e5c913734..d83cd87f94 100644 --- a/src/rpcprotocol.cpp +++ b/src/rpcprotocol.cpp @@ -5,7 +5,6 @@ #include "rpcprotocol.h" -#include "clientversion.h" #include "random.h" #include "tinyformat.h" #include "util.h" @@ -16,236 +15,8 @@ #include <stdint.h> #include <fstream> -#include <boost/algorithm/string.hpp> -#include <boost/asio.hpp> -#include <boost/asio/ssl.hpp> -#include <boost/bind.hpp> -#include <boost/filesystem.hpp> -#include <boost/foreach.hpp> -#include <boost/iostreams/concepts.hpp> -#include <boost/iostreams/stream.hpp> -#include <boost/shared_ptr.hpp> - -#include "univalue/univalue.h" - using namespace std; -//! Number of bytes to allocate and read at most at once in post data -const size_t POST_READ_SIZE = 256 * 1024; - -/** - * HTTP protocol - * - * This ain't Apache. We're just using HTTP header for the length field - * and to be compatible with other JSON-RPC implementations. - */ - -string HTTPPost(const string& strMsg, const map<string,string>& mapRequestHeaders) -{ - ostringstream s; - s << "POST / HTTP/1.1\r\n" - << "User-Agent: bitcoin-json-rpc/" << FormatFullVersion() << "\r\n" - << "Host: 127.0.0.1\r\n" - << "Content-Type: application/json\r\n" - << "Content-Length: " << strMsg.size() << "\r\n" - << "Connection: close\r\n" - << "Accept: application/json\r\n"; - BOOST_FOREACH(const PAIRTYPE(string, string)& item, mapRequestHeaders) - s << item.first << ": " << item.second << "\r\n"; - s << "\r\n" << strMsg; - - return s.str(); -} - -static string rfc1123Time() -{ - return DateTimeStrFormat("%a, %d %b %Y %H:%M:%S +0000", GetTime()); -} - -static const char *httpStatusDescription(int nStatus) -{ - switch (nStatus) { - case HTTP_OK: return "OK"; - case HTTP_BAD_REQUEST: return "Bad Request"; - case HTTP_FORBIDDEN: return "Forbidden"; - case HTTP_NOT_FOUND: return "Not Found"; - case HTTP_INTERNAL_SERVER_ERROR: return "Internal Server Error"; - default: return ""; - } -} - -string HTTPError(int nStatus, bool keepalive, bool headersOnly) -{ - if (nStatus == HTTP_UNAUTHORIZED) - return strprintf("HTTP/1.0 401 Authorization Required\r\n" - "Date: %s\r\n" - "Server: bitcoin-json-rpc/%s\r\n" - "WWW-Authenticate: Basic realm=\"jsonrpc\"\r\n" - "Content-Type: text/html\r\n" - "Content-Length: 296\r\n" - "\r\n" - "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n" - "\"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd\">\r\n" - "<HTML>\r\n" - "<HEAD>\r\n" - "<TITLE>Error</TITLE>\r\n" - "<META HTTP-EQUIV='Content-Type' CONTENT='text/html; charset=ISO-8859-1'>\r\n" - "</HEAD>\r\n" - "<BODY><H1>401 Unauthorized.</H1></BODY>\r\n" - "</HTML>\r\n", rfc1123Time(), FormatFullVersion()); - - return HTTPReply(nStatus, httpStatusDescription(nStatus), keepalive, - headersOnly, "text/plain"); -} - -string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, const char *contentType) -{ - return strprintf( - "HTTP/1.1 %d %s\r\n" - "Date: %s\r\n" - "Connection: %s\r\n" - "Content-Length: %u\r\n" - "Content-Type: %s\r\n" - "Server: bitcoin-json-rpc/%s\r\n" - "\r\n", - nStatus, - httpStatusDescription(nStatus), - rfc1123Time(), - keepalive ? "keep-alive" : "close", - contentLength, - contentType, - FormatFullVersion()); -} - -string HTTPReply(int nStatus, const string& strMsg, bool keepalive, - bool headersOnly, const char *contentType) -{ - if (headersOnly) - { - return HTTPReplyHeader(nStatus, keepalive, 0, contentType); - } else { - return HTTPReplyHeader(nStatus, keepalive, strMsg.size(), contentType) + strMsg; - } -} - -bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, - string& http_method, string& http_uri) -{ - string str; - getline(stream, str); - - // HTTP request line is space-delimited - vector<string> vWords; - boost::split(vWords, str, boost::is_any_of(" ")); - if (vWords.size() < 2) - return false; - - // HTTP methods permitted: GET, POST - http_method = vWords[0]; - if (http_method != "GET" && http_method != "POST") - return false; - - // HTTP URI must be an absolute path, relative to current host - http_uri = vWords[1]; - if (http_uri.size() == 0 || http_uri[0] != '/') - return false; - - // parse proto, if present - string strProto = ""; - if (vWords.size() > 2) - strProto = vWords[2]; - - proto = 0; - const char *ver = strstr(strProto.c_str(), "HTTP/1."); - if (ver != NULL) - proto = atoi(ver+7); - - return true; -} - -int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto) -{ - string str; - getline(stream, str); - vector<string> vWords; - boost::split(vWords, str, boost::is_any_of(" ")); - if (vWords.size() < 2) - return HTTP_INTERNAL_SERVER_ERROR; - proto = 0; - const char *ver = strstr(str.c_str(), "HTTP/1."); - if (ver != NULL) - proto = atoi(ver+7); - return atoi(vWords[1].c_str()); -} - -int ReadHTTPHeaders(std::basic_istream<char>& stream, map<string, string>& mapHeadersRet) -{ - int nLen = 0; - while (true) - { - string str; - std::getline(stream, str); - if (str.empty() || str == "\r") - break; - string::size_type nColon = str.find(":"); - if (nColon != string::npos) - { - string strHeader = str.substr(0, nColon); - boost::trim(strHeader); - boost::to_lower(strHeader); - string strValue = str.substr(nColon+1); - boost::trim(strValue); - mapHeadersRet[strHeader] = strValue; - if (strHeader == "content-length") - nLen = atoi(strValue.c_str()); - } - } - return nLen; -} - - -int ReadHTTPMessage(std::basic_istream<char>& stream, map<string, - string>& mapHeadersRet, string& strMessageRet, - int nProto, size_t max_size) -{ - mapHeadersRet.clear(); - strMessageRet = ""; - - // Read header - int nLen = ReadHTTPHeaders(stream, mapHeadersRet); - if (nLen < 0 || (size_t)nLen > max_size) - return HTTP_INTERNAL_SERVER_ERROR; - - // Read message - if (nLen > 0) - { - vector<char> vch; - size_t ptr = 0; - while (ptr < (size_t)nLen) - { - size_t bytes_to_read = std::min((size_t)nLen - ptr, POST_READ_SIZE); - vch.resize(ptr + bytes_to_read); - stream.read(&vch[ptr], bytes_to_read); - if (!stream) // Connection lost while reading - return HTTP_INTERNAL_SERVER_ERROR; - ptr += bytes_to_read; - } - strMessageRet = string(vch.begin(), vch.end()); - } - - string sConHdr = mapHeadersRet["connection"]; - - if ((sConHdr != "close") && (sConHdr != "keep-alive")) - { - if (nProto >= 1) - mapHeadersRet["connection"] = "keep-alive"; - else - mapHeadersRet["connection"] = "close"; - } - - return HTTP_OK; -} - /** * JSON-RPC protocol. Bitcoin speaks version 1.0 for maximum compatibility, * but uses JSON-RPC 1.1/2.0 standards for parts of the 1.0 standard that were diff --git a/src/rpcprotocol.h b/src/rpcprotocol.h index 2360ec2c60..5381e4bcfd 100644 --- a/src/rpcprotocol.h +++ b/src/rpcprotocol.h @@ -10,10 +10,6 @@ #include <map> #include <stdint.h> #include <string> -#include <boost/iostreams/concepts.hpp> -#include <boost/iostreams/stream.hpp> -#include <boost/asio.hpp> -#include <boost/asio/ssl.hpp> #include <boost/filesystem.hpp> #include "univalue/univalue.h" @@ -26,6 +22,7 @@ enum HTTPStatusCode HTTP_UNAUTHORIZED = 401, HTTP_FORBIDDEN = 403, HTTP_NOT_FOUND = 404, + HTTP_BAD_METHOD = 405, HTTP_INTERNAL_SERVER_ERROR = 500, HTTP_SERVICE_UNAVAILABLE = 503, }; @@ -79,88 +76,6 @@ enum RPCErrorCode RPC_WALLET_ALREADY_UNLOCKED = -17, //! Wallet is already unlocked }; -/** - * IOStream device that speaks SSL but can also speak non-SSL - */ -template <typename Protocol> -class SSLIOStreamDevice : public boost::iostreams::device<boost::iostreams::bidirectional> { -public: - SSLIOStreamDevice(boost::asio::ssl::stream<typename Protocol::socket> &streamIn, bool fUseSSLIn) : stream(streamIn) - { - fUseSSL = fUseSSLIn; - fNeedHandshake = fUseSSLIn; - } - - void handshake(boost::asio::ssl::stream_base::handshake_type role) - { - if (!fNeedHandshake) return; - fNeedHandshake = false; - stream.handshake(role); - } - std::streamsize read(char* s, std::streamsize n) - { - handshake(boost::asio::ssl::stream_base::server); // HTTPS servers read first - if (fUseSSL) return stream.read_some(boost::asio::buffer(s, n)); - return stream.next_layer().read_some(boost::asio::buffer(s, n)); - } - std::streamsize write(const char* s, std::streamsize n) - { - handshake(boost::asio::ssl::stream_base::client); // HTTPS clients write first - if (fUseSSL) return boost::asio::write(stream, boost::asio::buffer(s, n)); - return boost::asio::write(stream.next_layer(), boost::asio::buffer(s, n)); - } - bool connect(const std::string& server, const std::string& port) - { - using namespace boost::asio::ip; - tcp::resolver resolver(stream.get_io_service()); - tcp::resolver::iterator endpoint_iterator; -#if BOOST_VERSION >= 104300 - try { -#endif - // The default query (flags address_configured) tries IPv6 if - // non-localhost IPv6 configured, and IPv4 if non-localhost IPv4 - // configured. - tcp::resolver::query query(server.c_str(), port.c_str()); - endpoint_iterator = resolver.resolve(query); -#if BOOST_VERSION >= 104300 - } catch (const boost::system::system_error&) { - // If we at first don't succeed, try blanket lookup (IPv4+IPv6 independent of configured interfaces) - tcp::resolver::query query(server.c_str(), port.c_str(), resolver_query_base::flags()); - endpoint_iterator = resolver.resolve(query); - } -#endif - boost::system::error_code error = boost::asio::error::host_not_found; - tcp::resolver::iterator end; - while (error && endpoint_iterator != end) - { - stream.lowest_layer().close(); - stream.lowest_layer().connect(*endpoint_iterator++, error); - } - if (error) - return false; - return true; - } - -private: - bool fNeedHandshake; - bool fUseSSL; - boost::asio::ssl::stream<typename Protocol::socket>& stream; -}; - -std::string HTTPPost(const std::string& strMsg, const std::map<std::string,std::string>& mapRequestHeaders); -std::string HTTPError(int nStatus, bool keepalive, - bool headerOnly = false); -std::string HTTPReplyHeader(int nStatus, bool keepalive, size_t contentLength, - const char *contentType = "application/json"); -std::string HTTPReply(int nStatus, const std::string& strMsg, bool keepalive, - bool headerOnly = false, - const char *contentType = "application/json"); -bool ReadHTTPRequestLine(std::basic_istream<char>& stream, int &proto, - std::string& http_method, std::string& http_uri); -int ReadHTTPStatus(std::basic_istream<char>& stream, int &proto); -int ReadHTTPHeaders(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet); -int ReadHTTPMessage(std::basic_istream<char>& stream, std::map<std::string, std::string>& mapHeadersRet, - std::string& strMessageRet, int nProto, size_t max_size); std::string JSONRPCRequest(const std::string& strMethod, const UniValue& params, const UniValue& id); UniValue JSONRPCReplyObj(const UniValue& result, const UniValue& error, const UniValue& id); std::string JSONRPCReply(const UniValue& result, const UniValue& error, const UniValue& id); diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 9362401b1e..b831d3d3b2 100644 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -12,13 +12,9 @@ #include "ui_interface.h" #include "util.h" #include "utilstrencodings.h" -#ifdef ENABLE_WALLET -#include "wallet/wallet.h" -#endif -#include <boost/algorithm/string.hpp> -#include <boost/asio.hpp> -#include <boost/asio/ssl.hpp> +#include "univalue/univalue.h" + #include <boost/bind.hpp> #include <boost/filesystem.hpp> #include <boost/foreach.hpp> @@ -27,28 +23,20 @@ #include <boost/shared_ptr.hpp> #include <boost/signals2/signal.hpp> #include <boost/thread.hpp> +#include <boost/algorithm/string/case_conv.hpp> // for to_upper() -#include "univalue/univalue.h" - -using namespace boost::asio; using namespace RPCServer; using namespace std; -static std::string strRPCUserColonPass; - static bool fRPCRunning = false; static bool fRPCInWarmup = true; static std::string rpcWarmupStatus("RPC server started"); static CCriticalSection cs_rpcWarmup; - -//! These are created by StartRPCThreads, destroyed in StopRPCThreads -static boost::asio::io_service* rpc_io_service = NULL; -static map<string, boost::shared_ptr<deadline_timer> > deadlineTimers; -static ssl::context* rpc_ssl_context = NULL; -static boost::thread_group* rpc_worker_group = NULL; -static boost::asio::io_service::work *rpc_dummy_work = NULL; -static std::vector<CSubNet> rpc_allow_subnets; //!< List of subnets to allow RPC connections from -static std::vector< boost::shared_ptr<ip::tcp::acceptor> > rpc_acceptors; +/* Timer-creating functions */ +static std::vector<RPCTimerInterface*> timerInterfaces; +/* Map of name to timer. + * @note Can be changed to std::unique_ptr when C++11 */ +static std::map<std::string, boost::shared_ptr<RPCTimerBase> > deadlineTimers; static struct CRPCSignals { @@ -169,7 +157,6 @@ vector<unsigned char> ParseHexO(const UniValue& o, string strKey) return ParseHexV(find_value(o, strKey), strKey); } - /** * Note: This interface may still be subject to change. */ @@ -261,8 +248,6 @@ UniValue stop(const UniValue& params, bool fHelp) return "Bitcoin server stopping"; } - - /** * Call Table */ @@ -399,7 +384,7 @@ CRPCTable::CRPCTable() } } -const CRPCCommand *CRPCTable::operator[](const std::string& name) const +const CRPCCommand *CRPCTable::operator[](const std::string &name) const { map<string, const CRPCCommand*>::const_iterator it = mapCommands.find(name); if (it == mapCommands.end()) @@ -407,373 +392,26 @@ const CRPCCommand *CRPCTable::operator[](const std::string& name) const return (*it).second; } - -bool HTTPAuthorized(map<string, string>& mapHeaders) -{ - string strAuth = mapHeaders["authorization"]; - if (strAuth.substr(0,6) != "Basic ") - return false; - string strUserPass64 = strAuth.substr(6); boost::trim(strUserPass64); - string strUserPass = DecodeBase64(strUserPass64); - return TimingResistantEqual(strUserPass, strRPCUserColonPass); -} - -void ErrorReply(std::ostream& stream, const UniValue& objError, const UniValue& id) -{ - // Send error reply from json-rpc error object - int nStatus = HTTP_INTERNAL_SERVER_ERROR; - int code = find_value(objError, "code").get_int(); - if (code == RPC_INVALID_REQUEST) nStatus = HTTP_BAD_REQUEST; - else if (code == RPC_METHOD_NOT_FOUND) nStatus = HTTP_NOT_FOUND; - string strReply = JSONRPCReply(NullUniValue, objError, id); - stream << HTTPReply(nStatus, strReply, false) << std::flush; -} - -CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address) -{ - CNetAddr netaddr; - // Make sure that IPv4-compatible and IPv4-mapped IPv6 addresses are treated as IPv4 addresses - if (address.is_v6() - && (address.to_v6().is_v4_compatible() - || address.to_v6().is_v4_mapped())) - address = address.to_v6().to_v4(); - - if(address.is_v4()) - { - boost::asio::ip::address_v4::bytes_type bytes = address.to_v4().to_bytes(); - netaddr.SetRaw(NET_IPV4, &bytes[0]); - } - else - { - boost::asio::ip::address_v6::bytes_type bytes = address.to_v6().to_bytes(); - netaddr.SetRaw(NET_IPV6, &bytes[0]); - } - return netaddr; -} - -bool ClientAllowed(const boost::asio::ip::address& address) -{ - CNetAddr netaddr = BoostAsioToCNetAddr(address); - BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets) - if (subnet.Match(netaddr)) - return true; - return false; -} - -template <typename Protocol> -class AcceptedConnectionImpl : public AcceptedConnection -{ -public: - AcceptedConnectionImpl( - boost::asio::io_service& io_service, - ssl::context &context, - bool fUseSSL) : - sslStream(io_service, context), - _d(sslStream, fUseSSL), - _stream(_d) - { - } - - virtual std::iostream& stream() - { - return _stream; - } - - virtual std::string peer_address_to_string() const - { - return peer.address().to_string(); - } - - virtual void close() - { - _stream.close(); - } - - typename Protocol::endpoint peer; - boost::asio::ssl::stream<typename Protocol::socket> sslStream; - -private: - SSLIOStreamDevice<Protocol> _d; - boost::iostreams::stream< SSLIOStreamDevice<Protocol> > _stream; -}; - -void ServiceConnection(AcceptedConnection *conn); - -//! Forward declaration required for RPCListen -template <typename Protocol, typename SocketAcceptorService> -static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, - ssl::context& context, - bool fUseSSL, - boost::shared_ptr< AcceptedConnection > conn, - const boost::system::error_code& error); - -/** - * Sets up I/O resources to accept and handle a new connection. - */ -template <typename Protocol, typename SocketAcceptorService> -static void RPCListen(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, - ssl::context& context, - const bool fUseSSL) -{ - // Accept connection - boost::shared_ptr< AcceptedConnectionImpl<Protocol> > conn(new AcceptedConnectionImpl<Protocol>(acceptor->get_io_service(), context, fUseSSL)); - - acceptor->async_accept( - conn->sslStream.lowest_layer(), - conn->peer, - boost::bind(&RPCAcceptHandler<Protocol, SocketAcceptorService>, - acceptor, - boost::ref(context), - fUseSSL, - conn, - _1)); -} - - -/** - * Accept and handle incoming connection. - */ -template <typename Protocol, typename SocketAcceptorService> -static void RPCAcceptHandler(boost::shared_ptr< basic_socket_acceptor<Protocol, SocketAcceptorService> > acceptor, - ssl::context& context, - const bool fUseSSL, - boost::shared_ptr< AcceptedConnection > conn, - const boost::system::error_code& error) +bool StartRPC() { - // Immediately start accepting new connections, except when we're cancelled or our socket is closed. - if (error != boost::asio::error::operation_aborted && acceptor->is_open()) - RPCListen(acceptor, context, fUseSSL); - - AcceptedConnectionImpl<ip::tcp>* tcp_conn = dynamic_cast< AcceptedConnectionImpl<ip::tcp>* >(conn.get()); - - if (error) - { - // TODO: Actually handle errors - LogPrintf("%s: Error: %s\n", __func__, error.message()); - } - // Restrict callers by IP. It is important to - // do this before starting client thread, to filter out - // certain DoS and misbehaving clients. - else if (tcp_conn && !ClientAllowed(tcp_conn->peer.address())) - { - // Only send a 403 if we're not using SSL to prevent a DoS during the SSL handshake. - if (!fUseSSL) - conn->stream() << HTTPError(HTTP_FORBIDDEN, false) << std::flush; - conn->close(); - } - else { - ServiceConnection(conn.get()); - conn->close(); - } -} - -static ip::tcp::endpoint ParseEndpoint(const std::string &strEndpoint, int defaultPort) -{ - std::string addr; - int port = defaultPort; - SplitHostPort(strEndpoint, port, addr); - return ip::tcp::endpoint(boost::asio::ip::address::from_string(addr), port); -} - -void StartRPCThreads() -{ - rpc_allow_subnets.clear(); - rpc_allow_subnets.push_back(CSubNet("127.0.0.0/8")); // always allow IPv4 local subnet - rpc_allow_subnets.push_back(CSubNet("::1")); // always allow IPv6 localhost - if (mapMultiArgs.count("-rpcallowip")) - { - const vector<string>& vAllow = mapMultiArgs["-rpcallowip"]; - BOOST_FOREACH(string strAllow, vAllow) - { - CSubNet subnet(strAllow); - if(!subnet.IsValid()) - { - uiInterface.ThreadSafeMessageBox( - strprintf("Invalid -rpcallowip subnet specification: %s. Valid are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24).", strAllow), - "", CClientUIInterface::MSG_ERROR); - StartShutdown(); - return; - } - rpc_allow_subnets.push_back(subnet); - } - } - std::string strAllowed; - BOOST_FOREACH(const CSubNet &subnet, rpc_allow_subnets) - strAllowed += subnet.ToString() + " "; - LogPrint("rpc", "Allowing RPC connections from: %s\n", strAllowed); - - if (mapArgs["-rpcpassword"] == "") - { - LogPrintf("No rpcpassword set - using random cookie authentication\n"); - if (!GenerateAuthCookie(&strRPCUserColonPass)) { - uiInterface.ThreadSafeMessageBox( - _("Error: A fatal internal error occurred, see debug.log for details"), // Same message as AbortNode - "", CClientUIInterface::MSG_ERROR); - StartShutdown(); - return; - } - } else { - strRPCUserColonPass = mapArgs["-rpcuser"] + ":" + mapArgs["-rpcpassword"]; - } - - assert(rpc_io_service == NULL); - rpc_io_service = new boost::asio::io_service(); - rpc_ssl_context = new ssl::context(*rpc_io_service, ssl::context::sslv23); - - const bool fUseSSL = GetBoolArg("-rpcssl", false); - - if (fUseSSL) - { - rpc_ssl_context->set_options(ssl::context::no_sslv2 | ssl::context::no_sslv3); - - boost::filesystem::path pathCertFile(GetArg("-rpcsslcertificatechainfile", "server.cert")); - if (!pathCertFile.is_complete()) pathCertFile = boost::filesystem::path(GetDataDir()) / pathCertFile; - if (boost::filesystem::exists(pathCertFile)) rpc_ssl_context->use_certificate_chain_file(pathCertFile.string()); - else LogPrintf("ThreadRPCServer ERROR: missing server certificate file %s\n", pathCertFile.string()); - - boost::filesystem::path pathPKFile(GetArg("-rpcsslprivatekeyfile", "server.pem")); - if (!pathPKFile.is_complete()) pathPKFile = boost::filesystem::path(GetDataDir()) / pathPKFile; - if (boost::filesystem::exists(pathPKFile)) rpc_ssl_context->use_private_key_file(pathPKFile.string(), ssl::context::pem); - else LogPrintf("ThreadRPCServer ERROR: missing server private key file %s\n", pathPKFile.string()); - - string strCiphers = GetArg("-rpcsslciphers", "TLSv1.2+HIGH:TLSv1+HIGH:!SSLv2:!aNULL:!eNULL:!3DES:@STRENGTH"); - SSL_CTX_set_cipher_list(rpc_ssl_context->impl(), strCiphers.c_str()); - } - - std::vector<ip::tcp::endpoint> vEndpoints; - bool bBindAny = false; - int defaultPort = GetArg("-rpcport", BaseParams().RPCPort()); - if (!mapArgs.count("-rpcallowip")) // Default to loopback if not allowing external IPs - { - vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::loopback(), defaultPort)); - vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::loopback(), defaultPort)); - if (mapArgs.count("-rpcbind")) - { - LogPrintf("WARNING: option -rpcbind was ignored because -rpcallowip was not specified, refusing to allow everyone to connect\n"); - } - } else if (mapArgs.count("-rpcbind")) // Specific bind address - { - BOOST_FOREACH(const std::string &addr, mapMultiArgs["-rpcbind"]) - { - try { - vEndpoints.push_back(ParseEndpoint(addr, defaultPort)); - } - catch (const boost::system::system_error&) - { - uiInterface.ThreadSafeMessageBox( - strprintf(_("Could not parse -rpcbind value %s as network address"), addr), - "", CClientUIInterface::MSG_ERROR); - StartShutdown(); - return; - } - } - } else { // No specific bind address specified, bind to any - vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v6::any(), defaultPort)); - vEndpoints.push_back(ip::tcp::endpoint(boost::asio::ip::address_v4::any(), defaultPort)); - // Prefer making the socket dual IPv6/IPv4 instead of binding - // to both addresses separately. - bBindAny = true; - } - - bool fListening = false; - std::string strerr; - std::string straddress; - BOOST_FOREACH(const ip::tcp::endpoint &endpoint, vEndpoints) - { - try { - boost::asio::ip::address bindAddress = endpoint.address(); - straddress = bindAddress.to_string(); - LogPrintf("Binding RPC on address %s port %i (IPv4+IPv6 bind any: %i)\n", straddress, endpoint.port(), bBindAny); - boost::system::error_code v6_only_error; - boost::shared_ptr<ip::tcp::acceptor> acceptor(new ip::tcp::acceptor(*rpc_io_service)); - - acceptor->open(endpoint.protocol()); - acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true)); - - // Try making the socket dual IPv6/IPv4 when listening on the IPv6 "any" address - acceptor->set_option(boost::asio::ip::v6_only( - !bBindAny || bindAddress != boost::asio::ip::address_v6::any()), v6_only_error); - - acceptor->bind(endpoint); - acceptor->listen(socket_base::max_connections); - - RPCListen(acceptor, *rpc_ssl_context, fUseSSL); - - fListening = true; - rpc_acceptors.push_back(acceptor); - // If dual IPv6/IPv4 bind successful, skip binding to IPv4 separately - if(bBindAny && bindAddress == boost::asio::ip::address_v6::any() && !v6_only_error) - break; - } - catch (const boost::system::system_error& e) - { - LogPrintf("ERROR: Binding RPC on address %s port %i failed: %s\n", straddress, endpoint.port(), e.what()); - strerr = strprintf(_("An error occurred while setting up the RPC address %s port %u for listening: %s"), straddress, endpoint.port(), e.what()); - } - } - - if (!fListening) { - uiInterface.ThreadSafeMessageBox(strerr, "", CClientUIInterface::MSG_ERROR); - StartShutdown(); - return; - } - - rpc_worker_group = new boost::thread_group(); - for (int i = 0; i < GetArg("-rpcthreads", 4); i++) - rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service)); + LogPrint("rpc", "Starting RPC\n"); fRPCRunning = true; g_rpcSignals.Started(); + return true; } -void StartDummyRPCThread() +void InterruptRPC() { - if(rpc_io_service == NULL) - { - rpc_io_service = new boost::asio::io_service(); - /* Create dummy "work" to keep the thread from exiting when no timeouts active, - * see http://www.boost.org/doc/libs/1_51_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.stopping_the_io_service_from_running_out_of_work */ - rpc_dummy_work = new boost::asio::io_service::work(*rpc_io_service); - rpc_worker_group = new boost::thread_group(); - rpc_worker_group->create_thread(boost::bind(&boost::asio::io_service::run, rpc_io_service)); - fRPCRunning = true; - } + LogPrint("rpc", "Interrupting RPC\n"); + // Interrupt e.g. running longpolls + fRPCRunning = false; } -void StopRPCThreads() +void StopRPC() { - if (rpc_io_service == NULL) return; - // Set this to false first, so that longpolling loops will exit when woken up - fRPCRunning = false; - - // First, cancel all timers and acceptors - // This is not done automatically by ->stop(), and in some cases the destructor of - // boost::asio::io_service can hang if this is skipped. - boost::system::error_code ec; - BOOST_FOREACH(const boost::shared_ptr<ip::tcp::acceptor> &acceptor, rpc_acceptors) - { - acceptor->cancel(ec); - if (ec) - LogPrintf("%s: Warning: %s when cancelling acceptor\n", __func__, ec.message()); - } - rpc_acceptors.clear(); - BOOST_FOREACH(const PAIRTYPE(std::string, boost::shared_ptr<deadline_timer>) &timer, deadlineTimers) - { - timer.second->cancel(ec); - if (ec) - LogPrintf("%s: Warning: %s when cancelling timer\n", __func__, ec.message()); - } + LogPrint("rpc", "Stopping RPC\n"); deadlineTimers.clear(); - - DeleteAuthCookie(); - - rpc_io_service->stop(); g_rpcSignals.Stopped(); - if (rpc_worker_group != NULL) - rpc_worker_group->join_all(); - delete rpc_dummy_work; rpc_dummy_work = NULL; - delete rpc_worker_group; rpc_worker_group = NULL; - delete rpc_ssl_context; rpc_ssl_context = NULL; - delete rpc_io_service; rpc_io_service = NULL; } bool IsRPCRunning() @@ -802,36 +440,6 @@ bool RPCIsInWarmup(std::string *outStatus) return fRPCInWarmup; } -void RPCRunHandler(const boost::system::error_code& err, boost::function<void(void)> func) -{ - if (!err) - func(); -} - -void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds) -{ - assert(rpc_io_service != NULL); - - if (deadlineTimers.count(name) == 0) - { - deadlineTimers.insert(make_pair(name, - boost::shared_ptr<deadline_timer>(new deadline_timer(*rpc_io_service)))); - } - deadlineTimers[name]->expires_from_now(boost::posix_time::seconds(nSeconds)); - deadlineTimers[name]->async_wait(boost::bind(RPCRunHandler, _1, func)); -} - -class JSONRequest -{ -public: - UniValue id; - string strMethod; - UniValue params; - - JSONRequest() { id = NullUniValue; } - void parse(const UniValue& valRequest); -}; - void JSONRequest::parse(const UniValue& valRequest) { // Parse request @@ -862,7 +470,6 @@ void JSONRequest::parse(const UniValue& valRequest) throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array"); } - static UniValue JSONRPCExecOne(const UniValue& req) { UniValue rpc_result(UniValue::VOBJ); @@ -887,7 +494,7 @@ static UniValue JSONRPCExecOne(const UniValue& req) return rpc_result; } -static string JSONRPCExecBatch(const UniValue& vReq) +std::string JSONRPCExecBatch(const UniValue& vReq) { UniValue ret(UniValue::VARR); for (unsigned int reqIdx = 0; reqIdx < vReq.size(); reqIdx++) @@ -896,107 +503,6 @@ static string JSONRPCExecBatch(const UniValue& vReq) return ret.write() + "\n"; } -static bool HTTPReq_JSONRPC(AcceptedConnection *conn, - string& strRequest, - map<string, string>& mapHeaders, - bool fRun) -{ - // Check authorization - if (mapHeaders.count("authorization") == 0) - { - conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush; - return false; - } - - if (!HTTPAuthorized(mapHeaders)) - { - LogPrintf("ThreadRPCServer incorrect password attempt from %s\n", conn->peer_address_to_string()); - /* Deter brute-forcing - We don't support exposing the RPC port, so this shouldn't result - in a DoS. */ - MilliSleep(250); - - conn->stream() << HTTPError(HTTP_UNAUTHORIZED, false) << std::flush; - return false; - } - - JSONRequest jreq; - try - { - // Parse request - UniValue valRequest; - if (!valRequest.read(strRequest)) - throw JSONRPCError(RPC_PARSE_ERROR, "Parse error"); - - string strReply; - - // singleton request - if (valRequest.isObject()) { - jreq.parse(valRequest); - - UniValue result = tableRPC.execute(jreq.strMethod, jreq.params); - - // Send reply - strReply = JSONRPCReply(result, NullUniValue, jreq.id); - - // array of requests - } else if (valRequest.isArray()) - strReply = JSONRPCExecBatch(valRequest.get_array()); - else - throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error"); - - conn->stream() << HTTPReplyHeader(HTTP_OK, fRun, strReply.size()) << strReply << std::flush; - } - catch (const UniValue& objError) - { - ErrorReply(conn->stream(), objError, jreq.id); - return false; - } - catch (const std::exception& e) - { - ErrorReply(conn->stream(), JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id); - return false; - } - return true; -} - -void ServiceConnection(AcceptedConnection *conn) -{ - bool fRun = true; - while (fRun && !ShutdownRequested()) - { - int nProto = 0; - map<string, string> mapHeaders; - string strRequest, strMethod, strURI; - - // Read HTTP request line - if (!ReadHTTPRequestLine(conn->stream(), nProto, strMethod, strURI)) - break; - - // Read HTTP message headers and body - ReadHTTPMessage(conn->stream(), mapHeaders, strRequest, nProto, MAX_SIZE); - - // HTTP Keep-Alive is false; close connection immediately - if ((mapHeaders["connection"] == "close") || (!GetBoolArg("-rpckeepalive", true))) - fRun = false; - - // Process via JSON-RPC API - if (strURI == "/") { - if (!HTTPReq_JSONRPC(conn, strRequest, mapHeaders, fRun)) - break; - - // Process via HTTP REST API - } else if (strURI.substr(0, 6) == "/rest/" && GetBoolArg("-rest", false)) { - if (!HTTPReq_REST(conn, strURI, strRequest, mapHeaders, fRun)) - break; - - } else { - conn->stream() << HTTPError(HTTP_NOT_FOUND, false) << std::flush; - break; - } - } -} - UniValue CRPCTable::execute(const std::string &strMethod, const UniValue ¶ms) const { // Return immediately if in warmup @@ -1037,4 +543,26 @@ std::string HelpExampleRpc(const std::string& methodname, const std::string& arg "\"method\": \"" + methodname + "\", \"params\": [" + args + "] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/\n"; } +void RPCRegisterTimerInterface(RPCTimerInterface *iface) +{ + timerInterfaces.push_back(iface); +} + +void RPCUnregisterTimerInterface(RPCTimerInterface *iface) +{ + std::vector<RPCTimerInterface*>::iterator i = std::find(timerInterfaces.begin(), timerInterfaces.end(), iface); + assert(i != timerInterfaces.end()); + timerInterfaces.erase(i); +} + +void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds) +{ + if (timerInterfaces.empty()) + throw JSONRPCError(RPC_INTERNAL_ERROR, "No timer handler registered for RPC"); + deadlineTimers.erase(name); + RPCTimerInterface* timerInterface = timerInterfaces[0]; + LogPrint("rpc", "queue run of timer %s in %i seconds (using %s)\n", name, nSeconds, timerInterface->Name()); + deadlineTimers.insert(std::make_pair(name, timerInterface->NewTimer(func, nSeconds*1000))); +} + const CRPCTable tableRPC; diff --git a/src/rpcserver.h b/src/rpcserver.h index 3a71fd510f..83cc37918b 100644 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -32,26 +32,17 @@ namespace RPCServer class CBlockIndex; class CNetAddr; -class AcceptedConnection +class JSONRequest { public: - virtual ~AcceptedConnection() {} + UniValue id; + std::string strMethod; + UniValue params; - virtual std::iostream& stream() = 0; - virtual std::string peer_address_to_string() const = 0; - virtual void close() = 0; + JSONRequest() { id = NullUniValue; } + void parse(const UniValue& valRequest); }; -/** Start RPC threads */ -void StartRPCThreads(); -/** - * Alternative to StartRPCThreads for the GUI, when no server is - * used. The RPC thread in this case is only used to handle timeouts. - * If real RPC threads have already been started this is a no-op. - */ -void StartDummyRPCThread(); -/** Stop RPC threads */ -void StopRPCThreads(); /** Query whether RPC is running */ bool IsRPCRunning(); @@ -81,15 +72,45 @@ void RPCTypeCheck(const UniValue& params, void RPCTypeCheckObj(const UniValue& o, const std::map<std::string, UniValue::VType>& typesExpected, bool fAllowNull=false); +/** Opaque base class for timers returned by NewTimerFunc. + * This provides no methods at the moment, but makes sure that delete + * cleans up the whole state. + */ +class RPCTimerBase +{ +public: + virtual ~RPCTimerBase() {} +}; + /** - * Run func nSeconds from now. Uses boost deadline timers. + * RPC timer "driver". + */ +class RPCTimerInterface +{ +public: + virtual ~RPCTimerInterface() {} + /** Implementation name */ + virtual const char *Name() = 0; + /** Factory function for timers. + * RPC will call the function to create a timer that will call func in *millis* milliseconds. + * @note As the RPC mechanism is backend-neutral, it can use different implementations of timers. + * This is needed to cope with the case in which there is no HTTP server, but + * only GUI RPC console, and to break the dependency of pcserver on httprpc. + */ + virtual RPCTimerBase* NewTimer(boost::function<void(void)>& func, int64_t millis) = 0; +}; + +/** Register factory function for timers */ +void RPCRegisterTimerInterface(RPCTimerInterface *iface); +/** Unregister factory function for timers */ +void RPCUnregisterTimerInterface(RPCTimerInterface *iface); + +/** + * Run func nSeconds from now. * Overrides previous timer <name> (if any). */ void RPCRunLater(const std::string& name, boost::function<void(void)> func, int64_t nSeconds); -//! Convert boost::asio address to CNetAddr -extern CNetAddr BoostAsioToCNetAddr(boost::asio::ip::address address); - typedef UniValue(*rpcfn_type)(const UniValue& params, bool fHelp); class CRPCCommand @@ -134,9 +155,6 @@ extern uint256 ParseHashO(const UniValue& o, std::string strKey); extern std::vector<unsigned char> ParseHexV(const UniValue& v, std::string strName); extern std::vector<unsigned char> ParseHexO(const UniValue& o, std::string strKey); -extern void InitRPCMining(); -extern void ShutdownRPCMining(); - extern int64_t nWalletUnlockTime; extern CAmount AmountFromValue(const UniValue& value); extern UniValue ValueFromAmount(const CAmount& amount); @@ -244,11 +262,9 @@ extern UniValue getchaintips(const UniValue& params, bool fHelp); extern UniValue invalidateblock(const UniValue& params, bool fHelp); extern UniValue reconsiderblock(const UniValue& params, bool fHelp); -// in rest.cpp -extern bool HTTPReq_REST(AcceptedConnection *conn, - const std::string& strURI, - const std::string& strRequest, - const std::map<std::string, std::string>& mapHeaders, - bool fRun); +bool StartRPC(); +void InterruptRPC(); +void StopRPC(); +std::string JSONRPCExecBatch(const UniValue& vReq); #endif // BITCOIN_RPCSERVER_H diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index a65572e6f6..c0476db99b 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -224,21 +224,6 @@ BOOST_AUTO_TEST_CASE(json_parse_errors) BOOST_CHECK_THROW(ParseNonRFCJSONValue("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNL"), std::runtime_error); } -BOOST_AUTO_TEST_CASE(rpc_boostasiotocnetaddr) -{ - // Check IPv4 addresses - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("1.2.3.4")).ToString(), "1.2.3.4"); - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("127.0.0.1")).ToString(), "127.0.0.1"); - // Check IPv6 addresses - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::1")).ToString(), "::1"); - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("123:4567:89ab:cdef:123:4567:89ab:cdef")).ToString(), - "123:4567:89ab:cdef:123:4567:89ab:cdef"); - // v4 compatible must be interpreted as IPv4 - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::0:127.0.0.1")).ToString(), "127.0.0.1"); - // v4 mapped must be interpreted as IPv4 - BOOST_CHECK_EQUAL(BoostAsioToCNetAddr(boost::asio::ip::address::from_string("::ffff:127.0.0.1")).ToString(), "127.0.0.1"); -} - BOOST_AUTO_TEST_CASE(rpc_ban) { BOOST_CHECK_NO_THROW(CallRPC(string("clearbanned"))); diff --git a/src/util.cpp b/src/util.cpp index a7ec740de8..f50d25e17a 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -794,6 +794,18 @@ void SetupEnvironment() boost::filesystem::path::imbue(loc); } +bool SetupNetworking() +{ +#ifdef WIN32 + // Initialize Windows Sockets + WSADATA wsadata; + int ret = WSAStartup(MAKEWORD(2,2), &wsadata); + if (ret != NO_ERROR || LOBYTE(wsadata.wVersion ) != 2 || HIBYTE(wsadata.wVersion) != 2) + return false; +#endif + return true; +} + void SetThreadPriority(int nPriority) { #ifdef WIN32 diff --git a/src/util.h b/src/util.h index afc9a378bb..0b2dc01ac6 100644 --- a/src/util.h +++ b/src/util.h @@ -59,6 +59,7 @@ inline std::string _(const char* psz) } void SetupEnvironment(); +bool SetupNetworking(); /** Return true if log accepts specified category */ bool LogAcceptCategory(const char* category); |