diff --git a/td/generate/scheme/td_api.tl b/td/generate/scheme/td_api.tl index 0172ad203..5d07c70cc 100644 --- a/td/generate/scheme/td_api.tl +++ b/td/generate/scheme/td_api.tl @@ -2051,6 +2051,9 @@ textParseModeHTML = TextParseMode; //@description A SOCKS5 proxy server @username Username for logging in; may be empty @password Password for logging in; may be empty proxyTypeSocks5 username:string password:string = ProxyType; +//@description A HTTP transparent proxy server @username Username for logging in; may be empty @password Password for logging in; may be empty +proxyTypeHttp username:string password:string = ProxyType; + //@description An MTProto proxy server @secret The proxy's secret in hexadecimal encoding proxyTypeMtproto secret:string = ProxyType; diff --git a/td/generate/scheme/td_api.tlo b/td/generate/scheme/td_api.tlo index 052c3108e..bced32b08 100644 Binary files a/td/generate/scheme/td_api.tlo and b/td/generate/scheme/td_api.tlo differ diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 41a92b3f8..f4d6ed2d8 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -3244,7 +3244,11 @@ class CliClient final : public Actor { if (!user.empty() && password.empty()) { type = make_tl_object(user); } else { - type = make_tl_object(user, password); + if (port == "80") { + type = make_tl_object(user, password); + } else { + type = make_tl_object(user, password); + } } send_request(make_tl_object(server, to_integer(port), op == "aeproxy", std::move(type))); } else if (op == "gproxy" || op == "gproxies") { diff --git a/td/telegram/net/ConnectionCreator.cpp b/td/telegram/net/ConnectionCreator.cpp index 70f75bb87..8f68087aa 100644 --- a/td/telegram/net/ConnectionCreator.cpp +++ b/td/telegram/net/ConnectionCreator.cpp @@ -23,6 +23,7 @@ #include "td/mtproto/RawConnection.h" #include "td/net/GetHostByNameActor.h" +#include "td/net/HttpProxy.h" #include "td/net/Socks5.h" #include "td/net/TransparentProxy.h" @@ -174,6 +175,9 @@ class ConnectionCreator::ProxyInfo { bool use_socks5_proxy() const { return proxy_type() == Proxy::Type::Socks5; } + bool use_http_proxy() const { + return proxy_type() == Proxy::Type::Http; + } bool use_mtproto_proxy() const { return proxy_type() == Proxy::Type::Mtproto; } @@ -194,7 +198,7 @@ template void Proxy::parse(T &parser) { using td::parse; parse(type_, parser); - if (type_ == Proxy::Type::Socks5) { + if (type_ == Proxy::Type::Socks5 || type_ == Proxy::Type::Http) { parse(server_, parser); parse(port_, parser); parse(user_, parser); @@ -212,7 +216,7 @@ template void Proxy::store(T &storer) const { using td::store; store(type_, storer); - if (type_ == Proxy::Type::Socks5) { + if (type_ == Proxy::Type::Socks5 || type_ == Proxy::Type::Http) { store(server_, storer); store(port_, storer); store(user_, storer); @@ -230,6 +234,8 @@ StringBuilder &operator<<(StringBuilder &string_builder, const Proxy &proxy) { switch (proxy.type()) { case Proxy::Type::Socks5: return string_builder << "ProxySocks5 " << proxy.server() << ":" << proxy.port(); + case Proxy::Type::Http: + return string_builder << "ProxyHttp " << proxy.server() << ":" << proxy.port(); case Proxy::Type::Mtproto: return string_builder << "ProxyMtproto " << proxy.server() << ":" << proxy.port() << "/" << proxy.secret(); case Proxy::Type::None: @@ -298,6 +304,11 @@ void ConnectionCreator::add_proxy(string server, int32 port, bool enable, new_proxy = Proxy::socks5(server, port, type->username_, type->password_); break; } + case td_api::proxyTypeHttp::ID: { + auto type = td_api::move_object_as(proxy_type); + new_proxy = Proxy::http(server, port, type->username_, type->password_); + break; + } case td_api::proxyTypeMtproto::ID: { auto type = td_api::move_object_as(proxy_type); if (hex_decode(type->secret_).is_error()) { @@ -384,6 +395,8 @@ void ConnectionCreator::get_proxy_link(int32 proxy_id, Promise promise) url += "socks"; is_socks = true; break; + case Proxy::Type::Http: + return promise.set_error(Status::Error(400, "HTTP proxy can't have public link")); case Proxy::Type::Mtproto: url += "proxy"; break; @@ -493,7 +506,7 @@ void ConnectionCreator::ping_proxy_resolved(int32 proxy_id, IPAddress ip_address std::move(transport_type), std::move(promise)); }); CHECK(proxy.use_proxy()); - if (proxy.use_socks5_proxy()) { + if (proxy.use_socks5_proxy() || proxy.use_http_proxy()) { class Callback : public TransparentProxy::Callback { public: explicit Callback(Promise promise) : promise_(std::move(promise)) { @@ -507,12 +520,19 @@ void ConnectionCreator::ping_proxy_resolved(int32 proxy_id, IPAddress ip_address private: Promise promise_; }; - LOG(INFO) << "Start socks5: " << extra.debug_str; + LOG(INFO) << "Start ping proxy: " << extra.debug_str; auto token = next_token(); - children_[token] = { - false, create_actor("PingSocks5", std::move(socket_fd), extra.mtproto_ip, proxy.proxy().user().str(), - proxy.proxy().password().str(), - std::make_unique(std::move(socket_fd_promise)), create_reference(token))}; + if (proxy.use_socks5_proxy()) { + children_[token] = {false, create_actor("PingSocks5", std::move(socket_fd), extra.mtproto_ip, + proxy.proxy().user().str(), proxy.proxy().password().str(), + std::make_unique(std::move(socket_fd_promise)), + create_reference(token))}; + } else { + children_[token] = {false, create_actor("PingHttpProxy", std::move(socket_fd), extra.mtproto_ip, + proxy.proxy().user().str(), proxy.proxy().password().str(), + std::make_unique(std::move(socket_fd_promise)), + create_reference(token))}; + } } else { socket_fd_promise.set_value(std::move(socket_fd)); } @@ -574,7 +594,7 @@ void ConnectionCreator::disable_proxy_impl() { void ConnectionCreator::on_proxy_changed(bool from_db) { send_closure(G()->state_manager(), &StateManager::on_proxy, - active_proxy_id_ != 0 && proxies_[active_proxy_id_].type() == Proxy::Type::Socks5); + active_proxy_id_ != 0 && proxies_[active_proxy_id_].type() != Proxy::Type::Mtproto); if (!from_db) { for (auto &child : children_) { @@ -638,6 +658,9 @@ td_api::object_ptr ConnectionCreator::get_proxy_object(int32 prox case Proxy::Type::Socks5: type = make_tl_object(proxy.user().str(), proxy.password().str()); break; + case Proxy::Type::Http: + type = make_tl_object(proxy.user().str(), proxy.password().str()); + break; case Proxy::Type::Mtproto: type = make_tl_object(proxy.secret().str()); break; @@ -776,9 +799,10 @@ Result ConnectionCreator::find_connection(const ProxyInfo &proxy, DcId extra.check_mode |= info.should_check; - if (proxy.use_socks5_proxy()) { + if (proxy.use_socks5_proxy() || proxy.use_http_proxy()) { extra.mtproto_ip = info.option->get_ip_address(); - extra.debug_str = PSTRING() << "Socks5 " << proxy.ip_address() << " --> " << extra.mtproto_ip << extra.debug_str; + extra.debug_str = PSTRING() << (proxy.use_socks5_proxy() ? "Socks5 " : "HTTP ") << proxy.ip_address() << " --> " + << extra.mtproto_ip << extra.debug_str; LOG(INFO) << "Create: " << extra.debug_str; return SocketFd::open(proxy.ip_address()); } else { @@ -905,7 +929,7 @@ void ConnectionCreator::client_loop(ClientInfo &client) { client.is_media ? media_net_stats_callback_ : common_net_stats_callback_, actor_id(this), client.hash, extra.stat); - if (proxy.use_socks5_proxy()) { + if (proxy.use_socks5_proxy() || proxy.use_http_proxy()) { class Callback : public TransparentProxy::Callback { public: explicit Callback(Promise promise, std::unique_ptr stats_callback) @@ -937,13 +961,21 @@ void ConnectionCreator::client_loop(ClientInfo &client) { bool was_connected_{false}; std::unique_ptr stats_callback_; }; - LOG(INFO) << "Start socks5: " << extra.debug_str; + LOG(INFO) << "Start " << (proxy.use_socks5_proxy() ? "Socks5" : "HTTP") << ": " << extra.debug_str; auto token = next_token(); - children_[token] = { - true, create_actor("Socks5", std::move(socket_fd), extra.mtproto_ip, proxy.proxy().user().str(), - proxy.proxy().password().str(), - std::make_unique(std::move(promise), std::move(stats_callback)), - create_reference(token))}; + if (proxy.use_socks5_proxy()) { + children_[token] = { + true, create_actor("Socks5", std::move(socket_fd), extra.mtproto_ip, proxy.proxy().user().str(), + proxy.proxy().password().str(), + std::make_unique(std::move(promise), std::move(stats_callback)), + create_reference(token))}; + } else { + children_[token] = { + true, create_actor("HttpProxy", std::move(socket_fd), extra.mtproto_ip, + proxy.proxy().user().str(), proxy.proxy().password().str(), + std::make_unique(std::move(promise), std::move(stats_callback)), + create_reference(token))}; + } } else { ConnectionData data; data.socket_fd = std::move(socket_fd); diff --git a/td/telegram/net/ConnectionCreator.h b/td/telegram/net/ConnectionCreator.h index 73e6a69c4..d95ef940b 100644 --- a/td/telegram/net/ConnectionCreator.h +++ b/td/telegram/net/ConnectionCreator.h @@ -60,6 +60,16 @@ class Proxy { return proxy; } + static Proxy http(string server, int32 port, string user, string password) { + Proxy proxy; + proxy.type_ = Type::Http; + proxy.server_ = std::move(server); + proxy.port_ = std::move(port); + proxy.user_ = std::move(user); + proxy.password_ = std::move(password); + return proxy; + } + static Proxy mtproto(string server, int32 port, string secret) { Proxy proxy; proxy.type_ = Type::Mtproto; @@ -89,7 +99,7 @@ class Proxy { return secret_; } - enum class Type : int32 { None, Socks5, Mtproto }; + enum class Type : int32 { None, Socks5, Mtproto, Http }; Type type() const { return type_; } diff --git a/tdnet/CMakeLists.txt b/tdnet/CMakeLists.txt index 66763aa6d..db128f476 100644 --- a/tdnet/CMakeLists.txt +++ b/tdnet/CMakeLists.txt @@ -14,6 +14,7 @@ set(TDNET_SOURCE td/net/HttpFile.cpp td/net/HttpInboundConnection.cpp td/net/HttpOutboundConnection.cpp + td/net/HttpProxy.cpp td/net/HttpQuery.cpp td/net/HttpReader.cpp td/net/Socks5.cpp @@ -30,6 +31,7 @@ set(TDNET_SOURCE td/net/HttpHeaderCreator.h td/net/HttpInboundConnection.h td/net/HttpOutboundConnection.h + td/net/HttpProxy.h td/net/HttpQuery.h td/net/HttpReader.h td/net/NetStats.h diff --git a/tdnet/td/net/HttpProxy.cpp b/tdnet/td/net/HttpProxy.cpp new file mode 100644 index 000000000..422683e19 --- /dev/null +++ b/tdnet/td/net/HttpProxy.cpp @@ -0,0 +1,103 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/net/HttpProxy.h" + +#include "td/utils/base64.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/port/Fd.h" +#include "td/utils/Slice.h" + +namespace td { + +void HttpProxy::send_connect() { + VLOG(proxy) << "Send CONNECT to proxy"; + CHECK(state_ == State::SendConnect); + state_ = State::WaitConnectResponse; + + string host = PSTRING() << ip_address_.get_ip_str() << ':' << ip_address_.get_port(); + string proxy_authorization; + if (!username_.empty() || !password_.empty()) { + auto userinfo = PSTRING() << username_ << ':' << password_; + proxy_authorization = PSTRING() << "Proxy-Authorization: basic " << td::base64_encode(userinfo) << "\r\n"; + } + fd_.output_buffer().append(PSLICE() << "CONNECT " << host << " HTTP/1.1\r\n" + << "Host: " << host << "\r\n" + << proxy_authorization << "\r\n"); +} + +Status HttpProxy::wait_connect_response() { + CHECK(state_ == State::WaitConnectResponse); + auto it = fd_.input_buffer().clone(); + VLOG(proxy) << "Receive CONNECT response of size " << it.size(); + if (it.size() < 12 + 1 + 1) { + return Status::OK(); + } + char begin_buf[12]; + MutableSlice begin(begin_buf, 12); + it.advance(12, begin); + if ((begin.substr(0, 10) != "HTTP/1.1 2" && begin.substr(0, 10) != "HTTP/1.0 2") || !is_digit(begin[10]) || + !is_digit(begin[11])) { + return Status::Error("Failed to connect"); + } + + size_t total_size = 12; + char c; + MutableSlice c_slice(&c, 1); + while (!it.empty()) { + it.advance(1, c_slice); + total_size++; + if (c == '\n') { + break; + } + } + if (it.empty()) { + return Status::OK(); + } + + char prev = '\n'; + size_t pos = 0; + bool found = false; + while (!it.empty()) { + it.advance(1, c_slice); + total_size++; + if (c == '\n') { + if (pos == 0 || (pos == 1 && prev == '\r')) { + found = true; + break; + } + pos = 0; + } else { + pos++; + } + prev = c; + } + if (!found) { + CHECK(it.empty()); + return Status::OK(); + } + + fd_.input_buffer().advance(total_size); + stop(); + return Status::OK(); +} + +Status HttpProxy::loop_impl() { + switch (state_) { + case State::SendConnect: + send_connect(); + break; + case State::WaitConnectResponse: + TRY_STATUS(wait_connect_response()); + break; + default: + UNREACHABLE(); + } + return Status::OK(); +} + +} // namespace td diff --git a/tdnet/td/net/HttpProxy.h b/tdnet/td/net/HttpProxy.h new file mode 100644 index 000000000..4ae65bf4a --- /dev/null +++ b/tdnet/td/net/HttpProxy.h @@ -0,0 +1,28 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/net/TransparentProxy.h" + +#include "td/utils/Status.h" + +namespace td { + +class HttpProxy : public TransparentProxy { + public: + using TransparentProxy::TransparentProxy; + + private: + enum class State { SendConnect, WaitConnectResponse } state_ = State::SendConnect; + + void send_connect(); + Status wait_connect_response(); + + Status loop_impl() override; +}; + +} // namespace td diff --git a/tdnet/td/net/Socks5.cpp b/tdnet/td/net/Socks5.cpp index 494a6d7be..4e1fdc273 100644 --- a/tdnet/td/net/Socks5.cpp +++ b/tdnet/td/net/Socks5.cpp @@ -44,7 +44,6 @@ Status Socks5::wait_greeting_response() { } auto authentication_method = slice[1]; if (authentication_method == '\0') { - state_ = State::SendIpAddress; send_ip_address(); return Status::OK(); } @@ -90,14 +89,12 @@ Status Socks5::wait_password_response() { return Status::Error("Wrong username or password"); } - state_ = State::SendIpAddress; send_ip_address(); return Status::OK(); } void Socks5::send_ip_address() { VLOG(proxy) << "Send IP address"; - CHECK(state_ == State::SendIpAddress); callback_->on_connected(); string request; request += '\x05'; @@ -182,8 +179,7 @@ Status Socks5::loop_impl() { case State::WaitIpAddressResponse: TRY_STATUS(wait_ip_address_response()); break; - case State::SendIpAddress: - case State::Stop: + default: UNREACHABLE(); } return Status::OK(); diff --git a/tdnet/td/net/Socks5.h b/tdnet/td/net/Socks5.h index 76f2a768f..779bb4d39 100644 --- a/tdnet/td/net/Socks5.h +++ b/tdnet/td/net/Socks5.h @@ -21,9 +21,7 @@ class Socks5 : public TransparentProxy { SendGreeting, WaitGreetingResponse, WaitPasswordResponse, - SendIpAddress, - WaitIpAddressResponse, - Stop + WaitIpAddressResponse } state_ = State::SendGreeting; void send_greeting(); diff --git a/tdnet/td/net/TransparentProxy.cpp b/tdnet/td/net/TransparentProxy.cpp index 9b997f0b5..385952f11 100644 --- a/tdnet/td/net/TransparentProxy.cpp +++ b/tdnet/td/net/TransparentProxy.cpp @@ -15,7 +15,7 @@ namespace td { int VERBOSITY_NAME(proxy) = VERBOSITY_NAME(DEBUG); TransparentProxy::TransparentProxy(SocketFd socket_fd, IPAddress ip_address, string username, string password, - std::unique_ptr callback, ActorShared<> parent) + std::unique_ptr callback, ActorShared<> parent) : fd_(std::move(socket_fd)) , ip_address_(std::move(ip_address)) , username_(std::move(username)) diff --git a/tdnet/td/net/TransparentProxy.h b/tdnet/td/net/TransparentProxy.h index ef3bbd548..434979035 100644 --- a/tdnet/td/net/TransparentProxy.h +++ b/tdnet/td/net/TransparentProxy.h @@ -31,8 +31,8 @@ class TransparentProxy : public Actor { virtual void on_connected() = 0; }; - TransparentProxy(SocketFd socket_fd, IPAddress ip_address, string username, string password, std::unique_ptr callback, - ActorShared<> parent); + TransparentProxy(SocketFd socket_fd, IPAddress ip_address, string username, string password, + std::unique_ptr callback, ActorShared<> parent); protected: BufferedFd fd_; diff --git a/tdutils/td/utils/port/IPAddress.cpp b/tdutils/td/utils/port/IPAddress.cpp index 41f5340ec..3e909a255 100644 --- a/tdutils/td/utils/port/IPAddress.cpp +++ b/tdutils/td/utils/port/IPAddress.cpp @@ -405,7 +405,7 @@ Status IPAddress::init_peer_address(const SocketFd &socket_fd) { } static CSlice get_ip_str(int family, const void *addr) { - const int buf_size = INET6_ADDRSTRLEN; //, INET_ADDRSTRLEN; + const int buf_size = INET6_ADDRSTRLEN; static TD_THREAD_LOCAL char *buf; init_thread_local(buf, buf_size);