comes in plain
[ghsmtp.git] / socks5.cpp
blobc4aab6a925bfa2fdfc0f7d7ed1fd7a3f163f3f26
1 #include "DNS.hpp"
2 #include "POSIX.hpp"
3 #include "Sock.hpp"
4 #include "osutil.hpp"
6 #include <cstddef>
7 #include <memory>
9 #include <arpa/inet.h>
11 #include <sys/socket.h>
12 #include <sys/types.h>
14 #include <unistd.h>
16 #include <glog/logging.h>
18 #include <fmt/format.h>
20 #define CRLF "\r\n"
22 constexpr auto socks_version = 5;
24 namespace {
25 using octet = uint8_t;
27 octet constexpr lo(uint16_t n) { return octet(n & 0xFF); }
28 octet constexpr hi(uint16_t n) { return octet((n >> 8) & 0xFF); }
30 enum class auth_method : octet {
31 no_auth = 0,
32 none = 0xff,
35 constexpr char const* c_str(auth_method auth)
37 switch (auth) {
38 case auth_method::no_auth: return "no authentication required";
39 case auth_method::none: return "no acceptable methods";
41 return "*** unknown auth_method ***";
44 std::ostream& operator<<(std::ostream& os, auth_method const& auth)
46 return os << c_str(auth);
49 class greeting {
50 octet version_{socks_version};
51 octet nmethod_{1};
52 auth_method method_{auth_method::no_auth};
55 class response {
56 octet version_;
57 auth_method method_;
59 public:
60 auto version() const { return version_; }
61 auto method() const { return method_; }
64 enum class command : octet {
65 connect = 1,
66 bind = 2,
67 udp_associate = 3,
70 constexpr char const* c_str(command cmd)
72 switch (cmd) {
73 case command::connect: return "connect";
74 case command::bind: return "bind";
75 case command::udp_associate: return "UDP associate";
77 return "*** unknown command ***";
80 enum class address_type : octet {
81 ip4_address = 1,
82 domain_name = 3,
83 ip6_address = 4,
86 constexpr char const* c_str(address_type at)
88 switch (at) {
89 case address_type::ip4_address: return "IPv4 address";
90 case address_type::domain_name: return "domain name";
91 case address_type::ip6_address: return "IPv6 address";
93 return "*** unknown address type ***";
96 std::ostream& operator<<(std::ostream& os, address_type const& at)
98 return os << c_str(at);
101 class request_domain {
102 octet version_{socks_version};
103 command cmd_{command::connect};
104 octet reserved_{0};
105 address_type typ_{address_type::domain_name};
106 octet var_[258]; // 255 + 1 + 2
108 public:
109 request_domain(char const* addr, uint16_t port)
111 auto const len = strlen(addr);
112 CHECK_LE(len, 255);
113 var_[0] = static_cast<octet>(len);
114 memcpy(var_ + 1, addr, len);
115 var_[len + 1] = hi(port);
116 var_[len + 2] = lo(port);
119 ssize_t size() const { return offsetof(request_domain, var_) + var_[0] + 3; }
122 class request4 {
123 octet version_{socks_version};
124 command cmd_{command::connect};
125 octet reserved_{0};
126 address_type typ_{address_type::ip4_address};
127 octet ip4_[4];
128 octet port_hi_;
129 octet port_lo_;
131 void addr_(char const* addr)
133 CHECK_EQ(inet_pton(AF_INET, addr, reinterpret_cast<void*>(ip4_)), 1);
135 void port_(uint16_t port)
137 port_hi_ = hi(port);
138 port_lo_ = lo(port);
141 public:
142 request4(char const* addr, uint16_t port)
144 addr_(addr);
145 port_(port);
149 enum class reply_field : octet {
150 succeeded,
151 server_failure,
152 not_allowed,
153 network_unreachable,
154 host_unreachable,
155 connection_refused,
156 TTL_expired,
157 command_not_supported,
158 address_type_not_supported,
161 constexpr char const* c_str(reply_field rp)
163 switch (rp) {
164 case reply_field::succeeded: return "succeeded";
165 case reply_field::server_failure: return "server_failure";
166 case reply_field::not_allowed: return "not_allowed";
167 case reply_field::network_unreachable: return "network_unreachable";
168 case reply_field::host_unreachable: return "host_unreachable";
169 case reply_field::connection_refused: return "connection_refused";
170 case reply_field::TTL_expired: return "TTL_expired";
171 case reply_field::command_not_supported: return "command_not_supported";
172 case reply_field::address_type_not_supported:
173 return "address_type_not_supported";
175 return "*** unknown reply field ***";
178 std::ostream& operator<<(std::ostream& os, reply_field const& rp)
180 return os << c_str(rp);
183 class reply4 {
184 octet version_;
185 reply_field reply_;
186 octet reserved_;
187 address_type type_;
188 octet ip4_[4];
189 octet port_lo_;
190 octet port_hi_;
192 public:
193 auto version() const { return version_; }
194 auto reply() const { return reply_; }
195 auto type() const { return type_; }
197 std::string addr() const
199 std::string a;
200 a.resize(16);
201 CHECK_NOTNULL(inet_ntop(AF_INET, reinterpret_cast<void const*>(ip4_), &a[0],
202 a.size()));
203 return a;
205 uint16_t port() const { return (port_hi_ << 8) + port_lo_; }
208 DNS::RR_collection
209 get_tlsa_rrs(DNS::Resolver& res, Domain const& domain, uint16_t port)
211 CHECK(!domain.ascii().empty());
213 auto const tlsa{fmt::format("_{:d}._tcp.{}", port, domain.ascii())};
215 DNS::Query q(res, DNS::RR_type::TLSA, tlsa);
217 if (q.nx_domain()) {
218 LOG(INFO) << "TLSA data not found for " << domain << ':' << port;
221 auto const tlsa_rrs{q.get_records()};
223 if (q.bogus_or_indeterminate()) {
224 LOG(WARNING) << "TLSA data bogus_or_indeterminate";
227 return tlsa_rrs;
230 template <class T>
231 void read_checked(int fd, T& obj, std::string_view msg)
233 PCHECK(read(fd, &obj, sizeof(obj)) == sizeof(obj)) << msg;
236 template <class T>
237 void write_checked(int fd, T const& obj, std::string_view msg)
239 PCHECK(write(fd, &obj, sizeof(obj)) == sizeof(obj)) << msg;
241 } // namespace
243 int main(int argc, char* argv[])
245 auto const fd = socket(AF_INET, SOCK_STREAM, 0);
246 PCHECK(fd >= 0) << "socket() failed";
248 auto constexpr tor_host{"127.0.0.1"};
249 auto constexpr tor_port{9050};
251 auto in4{sockaddr_in{}};
252 in4.sin_family = AF_INET;
253 in4.sin_port = htons(tor_port);
254 CHECK_EQ(inet_pton(AF_INET, tor_host, reinterpret_cast<void*>(&in4.sin_addr)),
256 PCHECK(connect(fd, reinterpret_cast<const sockaddr*>(&in4), sizeof(in4)) == 0)
257 << "connect failed: ";
259 greeting grtng;
260 write_checked(fd, grtng, "greeting write failed");
262 response rspns;
263 read_checked(fd, rspns, "response read failed");
265 CHECK_EQ(rspns.version(), socks_version);
266 CHECK_EQ(rspns.method(), auth_method::no_auth);
268 auto constexpr domain{"digilicious.com"};
269 uint16_t constexpr port{443};
271 // request4 request("108.83.36.113", port);
272 request_domain request(domain, port);
273 PCHECK(write(fd, &request, request.size()) == request.size())
274 << "request write failed";
276 reply4 reply;
277 read_checked(fd, reply, "reply read failed");
279 CHECK_EQ(reply.version(), socks_version);
280 CHECK_EQ(reply.reply(), reply_field::succeeded);
281 CHECK_EQ(reply.type(), address_type::ip4_address);
283 LOG(INFO) << "connected to " << reply.addr() << ':' << reply.port() << '\n';
285 POSIX::set_nonblocking(fd);
286 Sock sock(fd, fd);
288 auto const config_dir = osutil::get_config_dir();
289 DNS::Resolver res(config_dir);
290 auto tlsa_rrs = get_tlsa_rrs(res, Domain(domain), port);
292 LOG(INFO) << "starting TLS";
294 sock.starttls_client(config_dir, nullptr, domain, tlsa_rrs,
295 !tlsa_rrs.empty());
297 sock.out() << "GET / HTTP/1.1" CRLF "Host: " << domain << CRLF CRLF
298 << std::flush;
300 std::string line;
301 while (std::getline(sock.in(), line)) {
302 std::cout << line << '\n';
303 if (line == "</html>")
304 break;