diff options
author | Robert Newson <rnewson@apache.org> | 2014-01-06 23:38:00 +0000 |
---|---|---|
committer | Robert Newson <rnewson@apache.org> | 2014-01-06 23:38:00 +0000 |
commit | 1c0fe36523c54d752aad49c8af3cceb745f3661f (patch) | |
tree | f0bcc2a0f437dedba3bc11f1549c521cb0e54c0a | |
parent | 4e9335e0a2463ddf19aeef02fde18d0c5ae209f3 (diff) | |
parent | 9dd093df4577a889cd6ef2bb8fb6dfdd2f0381cb (diff) | |
download | couchdb-1c0fe36523c54d752aad49c8af3cceb745f3661f.tar.gz |
Merge branch '2025-feature-socks5'
-rw-r--r-- | share/doc/src/api/server/common.rst | 2 | ||||
-rw-r--r-- | share/doc/src/whatsnew/1.6.rst | 1 | ||||
-rw-r--r-- | src/couch_replicator/src/couch_replicator_utils.erl | 5 | ||||
-rw-r--r-- | src/ibrowse/Makefile.am | 2 | ||||
-rw-r--r-- | src/ibrowse/ibrowse_http_client.erl | 62 | ||||
-rw-r--r-- | src/ibrowse/ibrowse_lib.erl | 7 | ||||
-rw-r--r-- | src/ibrowse/ibrowse_socks5.erl | 109 |
7 files changed, 163 insertions, 25 deletions
diff --git a/share/doc/src/api/server/common.rst b/share/doc/src/api/server/common.rst index 98bb19b56..4e5614c0c 100644 --- a/share/doc/src/api/server/common.rst +++ b/share/doc/src/api/server/common.rst @@ -359,7 +359,7 @@ jumping to ``offset`` bytes towards the beginning of the file first: Required administrator's privileges on target server. :<json array doc_ids: Array of document IDs to be synchronized :<json string proxy: Address of a proxy server through which replication - should occur + should occur (protocol can be "http" or "socks5") :<json string source: Source database name or URL :<json string target: Target database name or URL :>header Content-Type: - :mimetype:`application/json` diff --git a/share/doc/src/whatsnew/1.6.rst b/share/doc/src/whatsnew/1.6.rst index 2a0fc6ec0..28e5ac290 100644 --- a/share/doc/src/whatsnew/1.6.rst +++ b/share/doc/src/whatsnew/1.6.rst @@ -51,3 +51,4 @@ Version 1.6.0 * :issue:`1962`: Various replicator enhancements :commit:`1d5fe2aa` write access for checkpoints is not required on source db :commit:`0693f98e` make the replication checkpoint interval configurable + :issue:`2025`: Support SOCKS5 protocol for replication sources and targets diff --git a/src/couch_replicator/src/couch_replicator_utils.erl b/src/couch_replicator/src/couch_replicator_utils.erl index 240d7d45e..99ddebf97 100644 --- a/src/couch_replicator/src/couch_replicator_utils.erl +++ b/src/couch_replicator/src/couch_replicator_utils.erl @@ -300,9 +300,10 @@ parse_proxy_params(ProxyUrl) -> host = Host, port = Port, username = User, - password = Passwd + password = Passwd, + protocol = Protocol } = ibrowse_lib:parse_url(ProxyUrl), - [{proxy_host, Host}, {proxy_port, Port}] ++ + [{proxy_protocol, Protocol}, {proxy_host, Host}, {proxy_port, Port}] ++ case is_list(User) andalso is_list(Passwd) of false -> []; diff --git a/src/ibrowse/Makefile.am b/src/ibrowse/Makefile.am index 869bd1072..7c48169b1 100644 --- a/src/ibrowse/Makefile.am +++ b/src/ibrowse/Makefile.am @@ -19,6 +19,7 @@ ibrowse_file_collection = \ ibrowse_http_client.erl \ ibrowse_lb.erl \ ibrowse_lib.erl \ + ibrowse_socks5.erl \ ibrowse_sup.erl \ ibrowse_test.erl @@ -29,6 +30,7 @@ ibrowseebin_make_generated_file_list = \ ibrowse_http_client.beam \ ibrowse_lb.beam \ ibrowse_lib.beam \ + ibrowse_socks5.beam \ ibrowse_sup.beam \ ibrowse_test.beam diff --git a/src/ibrowse/ibrowse_http_client.erl b/src/ibrowse/ibrowse_http_client.erl index c01385a90..a1cf6eb25 100644 --- a/src/ibrowse/ibrowse_http_client.erl +++ b/src/ibrowse/ibrowse_http_client.erl @@ -39,7 +39,8 @@ -record(state, {host, port, connect_timeout, inactivity_timer_ref, - use_proxy = false, proxy_auth_digest, + use_http_proxy = false, http_proxy_auth_digest, + socks5_host, socks5_port, socks5_user, socks5_password, ssl_options = [], is_ssl = false, socket, proxy_tunnel_setup = false, tunnel_setup_queue = [], @@ -488,9 +489,21 @@ handle_sock_closed(#state{reply_buffer = Buf, reqs = Reqs, http_status_code = SC State end. -do_connect(Host, Port, Options, #state{is_ssl = true, - use_proxy = false, - ssl_options = SSLOptions}, +do_connect(Host, Port, Options, #state{socks5_host = SocksHost}=State, Timeout) + when SocksHost /= undefined -> + ProxyOptions = [ + {user, State#state.socks5_user}, + {password, State#state.socks5_password}, + {host, SocksHost}, + {port, State#state.socks5_port}, + {is_ssl, State#state.is_ssl}, + {ssl_opts, State#state.ssl_options}], + ibrowse_socks5:connect(Host, Port, ProxyOptions, + get_sock_options(SocksHost, Options, []), + Timeout); +do_connect(Host, Port, Options, #state{is_ssl = true, + use_http_proxy = false, + ssl_options = SSLOptions}, Timeout) -> ssl:connect(Host, Port, get_sock_options(Host, Options, SSLOptions), Timeout); do_connect(Host, Port, Options, _State, Timeout) -> @@ -541,7 +554,7 @@ filter_sock_options(Opts) -> do_send(Req, #state{socket = Sock, is_ssl = true, - use_proxy = true, + use_http_proxy = true, proxy_tunnel_setup = Pts}) when Pts /= done -> gen_tcp:send(Sock, Req); do_send(Req, #state{socket = Sock, is_ssl = true}) -> ssl:send(Sock, Req); do_send(Req, #state{socket = Sock, is_ssl = false}) -> gen_tcp:send(Sock, Req). @@ -589,7 +602,7 @@ maybe_chunked_encode(Data, true) -> do_close(#state{socket = undefined}) -> ok; do_close(#state{socket = Sock, is_ssl = true, - use_proxy = true, + use_http_proxy = true, proxy_tunnel_setup = Pts }) when Pts /= done -> catch gen_tcp:close(Sock); do_close(#state{socket = Sock, is_ssl = true}) -> catch ssl:close(Sock); @@ -602,7 +615,7 @@ active_once(#state{socket = Socket} = State) -> do_setopts(_Sock, [], _) -> ok; do_setopts(Sock, Opts, #state{is_ssl = true, - use_proxy = true, + use_http_proxy = true, proxy_tunnel_setup = Pts} ) when Pts /= done -> inet:setopts(Sock, Opts); do_setopts(Sock, Opts, #state{is_ssl = true}) -> ssl:setopts(Sock, Opts); @@ -621,17 +634,28 @@ send_req_1(From, port = Port} = Url, Headers, Method, Body, Options, Timeout, #state{socket = undefined} = State) -> + ProxyHost = get_value(proxy_host, Options, false), + ProxyProtocol = get_value(proxy_protocol, Options, http), {Host_1, Port_1, State_1} = - case get_value(proxy_host, Options, false) of - false -> + case {ProxyHost, ProxyProtocol} of + {false, _} -> {Host, Port, State}; - PHost -> + {_, http} -> ProxyUser = get_value(proxy_user, Options, []), ProxyPassword = get_value(proxy_password, Options, []), Digest = http_auth_digest(ProxyUser, ProxyPassword), - {PHost, get_value(proxy_port, Options, 80), - State#state{use_proxy = true, - proxy_auth_digest = Digest}} + {ProxyHost, get_value(proxy_port, Options, 80), + State#state{use_http_proxy = true, + http_proxy_auth_digest = Digest}}; + {_, socks5} -> + ProxyUser = list_to_binary(get_value(proxy_user, Options, [])), + ProxyPassword = list_to_binary(get_value(proxy_password, Options, [])), + ProxyPort = get_value(proxy_port, Options, 1080), + {Host, Port, + State#state{socks5_host = ProxyHost, + socks5_port = ProxyPort, + socks5_user = ProxyUser, + socks5_password = ProxyPassword}} end, State_2 = check_ssl_options(Options, State_1), do_trace("Connecting...~n", []), @@ -662,7 +686,7 @@ send_req_1(From, Headers, Method, Body, Options, Timeout, #state{ proxy_tunnel_setup = false, - use_proxy = true, + use_http_proxy = true, is_ssl = true} = State) -> Ref = case Timeout of infinity -> @@ -850,11 +874,11 @@ add_auth_headers(#url{username = User, end, add_proxy_auth_headers(State, Headers_1). -add_proxy_auth_headers(#state{use_proxy = false}, Headers) -> +add_proxy_auth_headers(#state{use_http_proxy = false}, Headers) -> Headers; -add_proxy_auth_headers(#state{proxy_auth_digest = []}, Headers) -> +add_proxy_auth_headers(#state{http_proxy_auth_digest = []}, Headers) -> Headers; -add_proxy_auth_headers(#state{proxy_auth_digest = Auth_digest}, Headers) -> +add_proxy_auth_headers(#state{http_proxy_auth_digest = Auth_digest}, Headers) -> [{"Proxy-Authorization", ["Basic ", Auth_digest]} | Headers]. http_auth_digest([], []) -> @@ -863,7 +887,7 @@ http_auth_digest(Username, Password) -> ibrowse_lib:encode_base64(Username ++ [$: | Password]). make_request(Method, Headers, AbsPath, RelPath, Body, Options, - #state{use_proxy = UseProxy, is_ssl = Is_ssl}, ReqId) -> + #state{use_http_proxy = UseHttpProxy, is_ssl = Is_ssl}, ReqId) -> HttpVsn = http_vsn_string(get_value(http_vsn, Options, {1,1})), Fun1 = fun({X, Y}) when is_atom(X) -> {to_lower(atom_to_list(X)), X, Y}; @@ -906,7 +930,7 @@ make_request(Method, Headers, AbsPath, RelPath, Body, Options, Headers_2 end, Headers_4 = cons_headers(Headers_3), - Uri = case get_value(use_absolute_uri, Options, false) or UseProxy of + Uri = case get_value(use_absolute_uri, Options, false) or UseHttpProxy of true -> case Is_ssl of true -> diff --git a/src/ibrowse/ibrowse_lib.erl b/src/ibrowse/ibrowse_lib.erl index 1ce6bd4a2..7b12cb318 100644 --- a/src/ibrowse/ibrowse_lib.erl +++ b/src/ibrowse/ibrowse_lib.erl @@ -362,9 +362,10 @@ parse_url([], get_password, Url, TmpAcc) -> parse_url([], State, Url, TmpAcc) -> {invalid_uri_2, State, Url, TmpAcc}. -default_port(http) -> 80; -default_port(https) -> 443; -default_port(ftp) -> 21. +default_port(socks5) -> 1080; +default_port(http) -> 80; +default_port(https) -> 443; +default_port(ftp) -> 21. printable_date() -> {{Y,Mo,D},{H, M, S}} = calendar:local_time(), diff --git a/src/ibrowse/ibrowse_socks5.erl b/src/ibrowse/ibrowse_socks5.erl new file mode 100644 index 000000000..d00df4400 --- /dev/null +++ b/src/ibrowse/ibrowse_socks5.erl @@ -0,0 +1,109 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-module(ibrowse_socks5). + +-define(VERSION, 5). +-define(CONNECT, 1). + +-define(NO_AUTH, 0). +-define(USERPASS, 2). +-define(UNACCEPTABLE, 16#FF). +-define(RESERVED, 0). + +-define(ATYP_IPV4, 1). +-define(ATYP_DOMAINNAME, 3). +-define(ATYP_IPV6, 4). + +-define(SUCCEEDED, 0). + +-export([connect/5]). + +-import(ibrowse_lib, [get_value/2, get_value/3]). + +connect(TargetHost, TargetPort, ProxyOptions, Options, Timeout) -> + case gen_tcp:connect(get_value(host, ProxyOptions), + get_value(port, ProxyOptions), + Options, Timeout) of + {ok, Socket} -> + case handshake(Socket, Options) of + ok -> + case connect(TargetHost, TargetPort, Socket) of + ok -> + maybe_ssl(Socket, ProxyOptions, Timeout); + Else -> + gen_tcp:close(Socket), + Else + end; + Else -> + gen_tcp:close(Socket), + Else + end; + Else -> + Else + end. + +handshake(Socket, ProxyOptions) when is_port(Socket) -> + {Handshake, Success} = case get_value(user, ProxyOptions, <<>>) of + <<>> -> + {<<?VERSION, 1, ?NO_AUTH>>, ?NO_AUTH}; + User -> + Password = get_value(password, ProxyOptions, <<>>), + {<<?VERSION, 1, ?USERPASS, (byte_size(User)), User, + (byte_size(Password)), Password>>, ?USERPASS} + end, + ok = gen_tcp:send(Socket, Handshake), + case gen_tcp:recv(Socket, 0) of + {ok, <<?VERSION, Success>>} -> + ok; + {ok, <<?VERSION, ?UNACCEPTABLE>>} -> + {error, unacceptable}; + {error, Reason} -> + {error, Reason} + end. + +connect(Host, Port, Via) when is_list(Host) -> + connect(list_to_binary(Host), Port, Via); +connect(Host, Port, Via) when is_binary(Host), is_integer(Port), + is_port(Via) -> + ok = gen_tcp:send(Via, + <<?VERSION, ?CONNECT, ?RESERVED, ?ATYP_DOMAINNAME, + (byte_size(Host)), Host/binary, + (Port):16>>), + case gen_tcp:recv(Via, 0) of + {ok, <<?VERSION, ?SUCCEEDED, ?RESERVED, _/binary>>} -> + ok; + {ok, <<?VERSION, Rep, ?RESERVED, _/binary>>} -> + {error, rep(Rep)}; + {error, Reason} -> + {error, Reason} + end. + +maybe_ssl(Socket, ProxyOptions, Timeout) -> + IsSsl = get_value(is_ssl, ProxyOptions, false), + SslOpts = get_value(ssl_opts, ProxyOptions, []), + case IsSsl of + false -> + {ok, Socket}; + true -> + ssl:connect(Socket, SslOpts, Timeout) + end. + +rep(0) -> succeeded; +rep(1) -> server_fail; +rep(2) -> disallowed_by_ruleset; +rep(3) -> network_unreachable; +rep(4) -> host_unreachable; +rep(5) -> connection_refused; +rep(6) -> ttl_expired; +rep(7) -> command_not_supported; +rep(8) -> address_type_not_supported. |