/* doscan - Denial Of Service Capable Auditing of Networks * Copyright (C) 2003, 2004 Florian Weimer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "half_duplex.h" #include "opt.h" #include "proto.h" #include "results.h" #include "rx.h" #include "scan_tcp.h" #include "scan_trigger.h" #include "subnets.h" #include "tcp_server.h" #include "utils.h" #include #include #include #include #include #include #include #include static bool proxy_start(subnets&); static void proxy_open(scan_host_t *); typedef enum { method_get, method_connect } method_type; static method_type method; static unsigned listen_port; void proto_http_proxy_register() { proto_register ("http_proxy", proxy_start, proxy_open); } static void proxy_open (scan_host_t *) { } // Shared mark between client and server, to recognize connections. static const std::string mark("doscan open proxy test"); class http_reply { const std::string& data; static const rx regexp_return_code; static const unsigned max_reply_size = 4000; public: http_reply(const std::string& reply); static const unsigned max_header_size = 4000; typedef enum { yes, maybe, no} state; state return_code_available() const; state reply_available() const; unsigned return_code() const; bool success() const; std::string reply() const; }; inline http_reply::http_reply(const std::string& reply) : data(reply) { } const rx http_reply::regexp_return_code("^HTTP/(\\d+\\.\\d+) (\\d\\d\\d)" "(?: |\r\n)"); inline http_reply::state http_reply::return_code_available() const { if (data.find("\r\n") == std::string::npos) { return maybe; } if (data.size() > max_reply_size) { return no; } if (regexp_return_code.exec(data)) { return yes; } else { return no; } } unsigned http_reply::return_code() const { rx::matches matches; if (regexp_return_code.exec(data, matches)) { return atoi(matches[2].data().c_str()); } else { abort(); } } inline bool http_reply::success() const { unsigned ret = return_code(); return ret >= 200 && ret <= 299; }; inline http_reply::state http_reply::reply_available() const { std::string::size_type pos = data.find("\r\n\r\n"); if (pos != std::string::npos && data.size() >= pos + 4 + mark.size()) { return yes; } else { return maybe; } } std::string http_reply::reply() const { std::string::size_type pos = data.find("\r\n\r\n"); if (pos == std::string::npos) { return std::string(); } else { return data.substr(pos + 4); } } class http_client : public tcp_half_duplex_handler { virtual void ready(int state, int error, const std::string& data); void make_get_request(method_type, int& state, std::string&); public: http_client(event_queue&, ipv4_t); }; http_client::http_client(event_queue& q, ipv4_t host) : tcp_half_duplex_handler(q, host, opt_port) { set_relative_timeout(opt_connect_timeout); } void http_client::make_get_request(method_type method, int& state, std::string& req) { // Obtain our address for the back-connect. sockaddr_in sa; socklen_t namelen = sizeof(sa); int result = getsockname(fd(), reinterpret_cast(&sa), &namelen); if (result == -1) { fprintf(stderr, "%s: getpeername failed, error was: %s\n", opt_program, strerror(errno)); exit(EXIT_FAILURE); } char buf[100]; switch (method) { case method_get: // We use HTTP 1.0 so that we can omit the Host: header. sprintf(buf, "GET http://%s:%u/doscan-probe/%u HTTP/1.0\r\n\r\n", inet_ntoa(sa.sin_addr), listen_port, static_cast(time(0))); state = 20; break; case method_connect: sprintf(buf, "CONNECT %s:%u HTTP/1.0\r\n\r\n", inet_ntoa(sa.sin_addr), listen_port); state = 10; break; } req.assign(buf); } void http_client::ready(int state, int error, const std::string& data) { std::string request; if (error) { return; } switch (state) { case 0: if (get_error()) { return; } make_get_request(method, state, request); send(state, request); return; case 10: // Wait for CONNECT result. If ready, send the GET request. { http_reply reply(data); switch (reply.return_code_available()) { case http_reply::yes: if (reply.success()) { // New send get request. make_get_request(method_get, state, request); send(state, request); return; } else { // Error code, we are not welcome. Good! return; } case http_reply::no: results_add_unquoted(host(), "invalid return code: " + quote(data)); break; case http_reply::maybe: request_more_data(state); } } case 20: // Connection is established. Wait for the line with the return // code. { http_reply reply(data); switch (reply.return_code_available()) { case http_reply::yes: if (reply.success()) { goto process_reply; } else { // Error code, we are not welcome. Good! return; } case http_reply::no: results_add_unquoted(host(), "invalid return code: " + quote(data)); break; case http_reply::maybe: request_more_data(state); } } return; process_reply: state = 21; case 21: // Reply indicating success was encountered. We have to check the // contents. { http_reply reply(data); switch (reply.reply_available()) { case http_reply::yes: if (reply.reply() == mark) { results_add_unquoted(host(), "open proxy"); } return; case http_reply::maybe: request_more_data(state); return; case http_reply::no: results_add_unquoted(host(), "invalid header: " + quote(data)); return; } } } abort(); } class http_server : public half_duplex_handler { ipv4_t host; // peer address static const unsigned max_request_size = 4000; static const rx regexp_request; virtual void ready(); virtual void error(int); void maybe_request_more_data(); void bad_request(); void serve_request(); public: http_server(event_queue&, int fd, ipv4_t host, unsigned port); }; inline http_server::http_server(event_queue& q, int fd, ipv4_t h, unsigned) : half_duplex_handler(q, fd, true), host(h) { set_relative_timeout(opt_connect_timeout); } const rx http_server::regexp_request("^GET (?:http://[0-9.]+(?::\\d+)?)?" "/([^ ]*) (HTTP/\\d+\\.\\d+)\r\n", PCRE_CASELESS); void http_server::ready() { switch (state) { case 0: if (receive_buffer.find("\r\n") == std::string::npos) { maybe_request_more_data(); return; } // We have received the first line, advance to the next state. state++; case 1: // Wait until the header is complete. { rx::matches matches; if (regexp_request.exec(receive_buffer, matches)) { if (receive_buffer.find("\r\n\r\n") != std::string::npos) { serve_request(); return; } else { maybe_request_more_data(); state++; return; } } else{ bad_request(); return; } } case 2: if (receive_buffer.find("\r\n\r\n") != std::string::npos) { serve_request(); return; } maybe_request_more_data(); return; case 99: return; } abort(); } void http_server::maybe_request_more_data() { if (receive_buffer.size() > max_request_size) { bad_request(); } else { request_more_data(); } } void http_server::bad_request() { results_add_unquoted(host, RESULTS_ERROR_NOMATCH, "invalid request to server: " + quote(receive_buffer)); send("HTTP/1.1 400 Bad Request\r\n" "Connection: close\r\n" "Content-Type: application/text/plain\r\n" "\r\n" "Bad request\r\n"); state = 99; } void http_server::serve_request() { send_buffer.assign("HTTP/1.0 200 OK\r\n" "Connection: close\r\n" "Content-Type: application/octet-stream\r\n" "\r\n"); send_buffer.append(mark); send(); state = 99; } class trigger_handler : public scan_trigger::default_handler { tcp_accept_handler* server; virtual void all_connected(); public: trigger_handler(tcp_accept_handler*); }; trigger_handler::trigger_handler(tcp_accept_handler* s) : server(s) { } void trigger_handler::all_connected() { server->terminate(); } void http_server::error(int) { // FIXME: We should log the connection attempt. } static bool proxy_start(subnets& nets) { if (strcmp(opt_send, "GET") == 0) { method = method_get; } else if(strcmp(opt_send, "CONNECT") == 0) { method = method_connect; } else { fprintf (stderr, "%s: --send GET or --send CONNECT required\n", opt_program); exit (EXIT_FAILURE); } listen_port = atoi(opt_receive); if (listen_port == 0 || listen_port >= 65535) { fprintf (stderr, "%s: --receive PORT required, 0 < PORT < 65535\n", opt_program); exit (EXIT_FAILURE); } if (opt_banner_size) { fprintf (stderr, "%s: --banner is not supported by this module.\n", opt_program); exit (EXIT_FAILURE); } std::auto_ptr q(event_queue::create(opt_fd_count)); trigger_handler th(new tcp_default_accept_handler(*q, listen_port)); scan_trigger t(*q, nets, th, opt_fd_count, opt_add_timeout, opt_add_burst); q->run(); return false; } // arch-tag: d774d4cf-6b7b-4a25-9649-c5da11123554