/***************************************************************************
 * ncat_connect.c -- Ncat connect mode.                                    *
 ***********************IMPORTANT NMAP LICENSE TERMS************************
 *                                                                         *
 * The Nmap Security Scanner is (C) 1996-2009 Insecure.Com LLC. Nmap is    *
 * also a registered trademark of Insecure.Com LLC.  This program is free  *
 * software; you may redistribute and/or modify it under the terms of the  *
 * GNU General Public License as published by the Free Software            *
 * Foundation; Version 2 with the clarifications and exceptions described  *
 * below.  This guarantees your right to use, modify, and redistribute     *
 * this software under certain conditions.  If you wish to embed Nmap      *
 * technology into proprietary software, we sell alternative licenses      *
 * (contact sales@insecure.com).  Dozens of software vendors already       *
 * license Nmap technology such as host discovery, port scanning, OS       *
 * detection, and version detection.                                       *
 *                                                                         *
 * Note that the GPL places important restrictions on "derived works", yet *
 * it does not provide a detailed definition of that term.  To avoid       *
 * misunderstandings, we consider an application to constitute a           *
 * "derivative work" for the purpose of this license if it does any of the *
 * following:                                                              *
 * o Integrates source code from Nmap                                      *
 * o Reads or includes Nmap copyrighted data files, such as                *
 *   nmap-os-db or nmap-service-probes.                                    *
 * o Executes Nmap and parses the results (as opposed to typical shell or  *
 *   execution-menu apps, which simply display raw Nmap output and so are  *
 *   not derivative works.)                                                * 
 * o Integrates/includes/aggregates Nmap into a proprietary executable     *
 *   installer, such as those produced by InstallShield.                   *
 * o Links to a library or executes a program that does any of the above   *
 *                                                                         *
 * The term "Nmap" should be taken to also include any portions or derived *
 * works of Nmap.  This list is not exclusive, but is meant to clarify our *
 * interpretation of derived works with some common examples.  Our         *
 * interpretation applies only to Nmap--we don't speak for other people's  *
 * GPL works.                                                              *
 *                                                                         *
 * If you have any questions about the GPL licensing restrictions on using *
 * Nmap in non-GPL works, we would be happy to help.  As mentioned above,  *
 * we also offer alternative license to integrate Nmap into proprietary    *
 * applications and appliances.  These contracts have been sold to dozens  *
 * of software vendors, and generally include a perpetual license as well  *
 * as providing for priority support and updates as well as helping to     *
 * fund the continued development of Nmap technology.  Please email        *
 * sales@insecure.com for further information.                             *
 *                                                                         *
 * As a special exception to the GPL terms, Insecure.Com LLC grants        *
 * permission to link the code of this program with any version of the     *
 * OpenSSL library which is distributed under a license identical to that  *
 * listed in the included COPYING.OpenSSL file, and distribute linked      *
 * combinations including the two. You must obey the GNU GPL in all        *
 * respects for all of the code used other than OpenSSL.  If you modify    *
 * this file, you may extend this exception to your version of the file,   *
 * but you are not obligated to do so.                                     *
 *                                                                         *
 * If you received these files with a written license agreement or         *
 * contract stating terms other than the terms above, then that            *
 * alternative license agreement takes precedence over these comments.     *
 *                                                                         *
 * Source is provided to this software because we believe users have a     *
 * right to know exactly what a program is going to do before they run it. *
 * This also allows you to audit the software for security holes (none     *
 * have been found so far).                                                *
 *                                                                         *
 * Source code also allows you to port Nmap to new platforms, fix bugs,    *
 * and add new features.  You are highly encouraged to send your changes   *
 * to nmap-dev@insecure.org for possible incorporation into the main       *
 * distribution.  By sending these changes to Fyodor or one of the         *
 * Insecure.Org development mailing lists, it is assumed that you are      *
 * offering the Nmap Project (Insecure.Com LLC) the unlimited,             *
 * non-exclusive right to reuse, modify, and relicense the code.  Nmap     *
 * will always be available Open Source, but this is important because the *
 * inability to relicense code has caused devastating problems for other   *
 * Free Software projects (such as KDE and NASM).  We also occasionally    *
 * relicense the code to third parties as discussed above.  If you wish to *
 * specify special license conditions of your contributions, just say so   *
 * when you send them.                                                     *
 *                                                                         *
 * 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 v2.0 for more details at                         *
 * http://www.gnu.org/licenses/gpl-2.0.html , or in the COPYING file       *
 * included with Nmap.                                                     *
 *                                                                         *
 ***************************************************************************/

/* $Id$ */

#include "nsock.h"
#include "ncat.h"
#include "util.h"
#include "sys_wrap.h"

#include "nbase.h"
#include "http.h"

#ifndef WIN32
#include <unistd.h>
#include <netdb.h>
#endif
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#ifdef HAVE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif

static void connect_evt_handler(nsock_pool nsp, nsock_event evt, void *mydata);

#ifdef HAVE_OPENSSL
/* This callback is called for every certificate in a chain. ok is true if
   OpenSSL's internal verification has verified the certificate. We don't change
   anything about the verification, we only need access to the certificates to
   provide diagnostics. */
static int verify_callback(int ok, X509_STORE_CTX *store)
{
    X509 *cert = X509_STORE_CTX_get_current_cert(store);
    int err = X509_STORE_CTX_get_error(store);

    /* Print the subject, issuer, and fingerprint depending on the verbosity
       level. */
    if ((!ok && o.verbose) || o.debug > 1) {
        char digest_buf[SHA1_STRING_LENGTH + 1];

        loguser("Subject: ");
        X509_NAME_print_ex_fp(stderr, X509_get_subject_name(cert), 0, XN_FLAG_ONELINE);
        loguser("\n");
        loguser("Issuer: ");
        X509_NAME_print_ex_fp(stderr, X509_get_issuer_name(cert), 0, XN_FLAG_ONELINE);
        loguser("\n");

        assert(ssl_cert_fp_str_sha1(cert, digest_buf, sizeof(digest_buf)) != NULL);
        loguser("SHA-1 fingerprint: %s\n", digest_buf);
    }

    if (!ok && o.verbose) {
        loguser("Error: certificate verification failed (%s).\n",
            X509_verify_cert_error_string(err));
    }

    return ok;
}

static void set_ssl_ctx_options(SSL_CTX *ctx)
{   
    if (o.sslverify) {
        SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, verify_callback);
   
        if (o.ssltrustfile == NULL) {
            ssl_load_default_ca_certs(ctx);
        } else {
            if (o.debug)
                logdebug("Using trusted CA certificates from %s.\n", o.ssltrustfile);
            if (SSL_CTX_load_verify_locations(ctx, o.ssltrustfile, NULL) != 1) {
                bye("Could not load trusted certificates from %s.\n%s",
                    o.ssltrustfile, ERR_error_string(ERR_get_error(), NULL));
            }
        }
    } else {
        if (o.ssl && o.debug)
            logdebug("Not doing certificate verification.\n");
    }
   
    if (o.sslcert != NULL && o.sslkey != NULL){
      if (SSL_CTX_use_certificate_file(ctx, o.sslcert, SSL_FILETYPE_PEM) != 1)
            bye("SSL_CTX_use_certificate_file(): %s.", ERR_error_string(ERR_get_error(), NULL));
      if (SSL_CTX_use_PrivateKey_file(ctx, o.sslkey, SSL_FILETYPE_PEM) != 1)
            bye("SSL_CTX_use_Privatekey_file(): %s.", ERR_error_string(ERR_get_error(), NULL)); 
    } else {   
      if ((o.sslcert == NULL)!= (o.sslkey == NULL))
            bye("The --ssl-key and --ssl-cert options must be used together.");
    }    
}
#endif

/* Depending on verbosity, print a message that a connection was established. */
static void connect_report(nsock_iod nsi)
{
    struct sockaddr_storage peer;

    nsi_getlastcommunicationinfo(nsi, NULL, NULL, NULL,
        (struct sockaddr *) &peer, sizeof(peer));

    if (o.verbose) {
#ifdef HAVE_OPENSSL
        if (nsi_checkssl(nsi)) {
            X509 *cert;
            X509_NAME *subject;
            char digest_buf[SHA1_STRING_LENGTH + 1];

            loguser("SSL connection to %s:%hu.", inet_socktop(&peer), nsi_peerport(nsi));

            cert = SSL_get_peer_certificate((SSL *) nsi_getssl(nsi));
            assert(cert != NULL);

            subject = X509_get_subject_name(cert);
            if (subject != NULL) {
                char buf[256];
                int n;

                n = X509_NAME_get_text_by_NID(subject, NID_organizationName, buf, sizeof(buf));
                if (n >= 0 && n <= sizeof(buf) - 1)
                    loguser(" %s", buf);
            }

            loguser("\n");

            assert(ssl_cert_fp_str_sha1(cert, digest_buf, sizeof(digest_buf)) != NULL);
            loguser("SHA-1 fingerprint: %s\n", digest_buf);
        } else {
            loguser("Connected to %s:%hu.\n", inet_socktop(&peer), nsi_peerport(nsi));
        }
#else
        loguser("Connected to %s:%hu.\n", inet_socktop(&peer), nsi_peerport(nsi));
#endif
    }
}

int ncat_connect(void) {
    struct conn_state cs;
    nsock_pool mypool;
    nsock_event_id ev;
    int rc;
    
    /* Create an nsock pool */
    if ((mypool = nsp_new(NULL)) == NULL)
        bye("Failed to create nsock_pool.");
     
    if (o.debug > 1)
        /* A trace level of 1 still gives you an awful lot. */
        nsp_settrace(mypool, 1, nsock_gettimeofday());
    
#ifdef HAVE_OPENSSL
    set_ssl_ctx_options((SSL_CTX *)nsp_ssl_init(mypool));
#endif

    /* create an iod for a new socket */
    if ((cs.sock_nsi = nsi_new(mypool, NULL)) == NULL)
        bye("Failed to create nsock_iod.");

    if (srcaddr.ss_family != AF_UNSPEC)
        nsi_set_localaddr(cs.sock_nsi, &srcaddr, srcaddrlen);

    if (o.numsrcrtes) {
        struct sockaddr_in *sin = (struct sockaddr_in *) &targetss;
        unsigned char *ipopts = NULL;
        size_t ipoptslen = 0;

        if (o.af != AF_INET)
            bye("Sorry, -g can only currently be used with IPv4.");
        ipopts = buildsrcrte(sin->sin_addr, o.srcrtes, o.numsrcrtes, o.srcrteptr, &ipoptslen);

        nsi_set_ipoptions(cs.sock_nsi, ipopts, ipoptslen);
        free(ipopts); /* Nsock has its own copy */
    }
    
    if (o.udp) {
        ev = nsock_connect_udp(mypool, cs.sock_nsi, connect_evt_handler,
                               &cs, (struct sockaddr *) &targetss, targetsslen,
                               inet_port(&targetss));
    }
#ifdef HAVE_OPENSSL
    else if (o.ssl) {
        cs.ssl_session = NULL;
        ev = nsock_connect_ssl(mypool, cs.sock_nsi, connect_evt_handler,
                               o.conntimeout, &cs,
                               (struct sockaddr *) &targetss, targetsslen,
                               inet_port(&targetss), cs.ssl_session);
    }
#endif
    else if((httpconnect.ss_family != AF_UNSPEC)||(socksconnect.ss_family != AF_UNSPEC)){
        static int connect_socket ;
        connect_socket=do_connect(SOCK_STREAM);

        if(connect_socket == -1)
            bye("Connection failed");

        static char *proxy_request;
        char *headerbuf;
        char *statusbuf ;
        struct socket_buffer stateful_buf;
        int len;
        char* line;
        int read_timeout = DEFAULT_READ_TIMEOUT;
        size_t n;


        socket_buffer_init(&stateful_buf,connect_socket);


        if(o.verbose)
            loguser("connected to %s:%hu\n", inet_socktop(&targetss), inet_port(&targetss));

        if(httpconnect.ss_family != AF_UNSPEC){
            proxy_request = http_proxy_client_request(o.proxy_auth);
            len=strlen(proxy_request);
            if(send(connect_socket,proxy_request,len,0)!= -1){

                if (http_read_status_line(&stateful_buf,&statusbuf)!= 0){
                    if (o.verbose)
                        logdebug("Error reading Status-Line.\n");
                    return 0;
                }
                if(o.debug > 1)
                    logdebug("Status-Line: %s", statusbuf);

                if(http_parse_status_line_code(statusbuf)!= 200)
                    bye("proxy negotiation failed");

                if(http_read_header(&stateful_buf,&headerbuf) != 0) {
                    if (o.verbose)
                        logdebug("Error reading header.\n");
                    return 0;
                }
                free(headerbuf);
                free(statusbuf);
            }
        } 

        if(socksconnect.ss_family != AF_UNSPEC){
            struct sockaddr_in *sin = (struct sockaddr_in *) &socksconnect;
            struct socks4_data socks4msg;
            char socksbuf[7];

            /* Fill the socks4_data struct */
            zmem(&socks4msg, sizeof(socks4msg));
            socks4msg.version = SOCKS4_VERSION;
            socks4msg.type = SOCKS_CONNECT;
            socks4msg.port = sin->sin_port;
            socks4msg.address = sin->sin_addr.s_addr;
            if (o.proxy_auth)
                Strncpy(socks4msg.username, (char *) o.proxy_auth, sizeof(socks4msg.username));

            len=8+strlen(socks4msg.username)+1;

            if(send(connect_socket,(char *) &socks4msg,len,0)!= -1){
                /*The size of the socks4 response is "7" bytes.So read exactly "7" bytes from the buffer */

                if(socket_buffer_readcount(&stateful_buf,socksbuf,7)<0)
                    bye("Connection to SOCKS4 proxy failed: Invalid SOCKS4 response."); 
                if(socksbuf[1]!=90)
                    bye("Connection to SOCKS4 proxy failed");
            }
        }

        /* Clear out whatever is left in the socket buffer which may be
           already sent by proxy server along with http response headers. */
        line=socket_buffer_remainder(&stateful_buf,&n);

        //write the left in data to STDOUT  
        Write(STDOUT_FILENO,line,n);

        //Once the proxy negotiation is successfully done,Nsock takes over the control for Data passing. 
        cs.sock_nsi=nsi_new2(mypool,connect_socket,NULL); 

        /* Create IOD for nsp->stdin */
        if ((cs.stdin_nsi = nsi_new2(mypool,0, NULL)) == NULL)
            bye("Failed to create stdin nsiod.");

        if (o.idletimeout)
            read_timeout = o.idletimeout;

        if (o.sendonly == 0) {
            /* socket event? */
            nsock_read(mypool,cs.sock_nsi,connect_evt_handler,
                    read_timeout,&cs);
        }

        if (o.recvonly == 0) {
            /* stdin-fd event? */
            nsock_readbytes(mypool,cs.stdin_nsi,
                    connect_evt_handler,read_timeout,&cs, 0);
        }
    }else {
       
        ev = nsock_connect_tcp(mypool, cs.sock_nsi, connect_evt_handler,
                               o.conntimeout, &cs,
                               (struct sockaddr *) &targetss, targetsslen,
                               inet_port(&targetss));
    }
    
    /* connect */
    rc = nsock_loop(mypool, -1);
    
    nsp_delete(mypool);

    return rc;
}

/* handle nsock-powered connections */
static void connect_evt_handler(nsock_pool nsp, nsock_event evt, void *mydata)
{
    int nbytes = 100, read_timeout = DEFAULT_READ_TIMEOUT,
        write_timeout = DEFAULT_WRITE_TIMEOUT;
    static int holdstdin;
    int mysock;
    char *buf = NULL;
    nsock_iod nsi = nse_iod(evt);

    enum nse_status status = nse_status(evt);
    enum nse_type type = nse_type(evt);
    struct conn_state *cs;

    /* drop conn_evt data into our struct */
    cs = (struct conn_state *) mydata;

    /* debugging */
    if (o.debug > 1)
        logdebug("Received callback of type %s with status %s\n",
                 nse_type2str(type), nse_status2str(status));

    /* User-defined read/write timeouts */
    if (o.idletimeout)
            write_timeout = read_timeout = o.idletimeout;

    mysock = nsi_getsd(cs->sock_nsi);

    /* Handle nsock responses for the connection */
    if (status == NSE_STATUS_SUCCESS) {
        switch (type) {
        case NSE_TYPE_CONNECT:
        case NSE_TYPE_CONNECT_SSL:

#ifdef HAVE_OPENSSL
            if (nsi_checkssl(cs->sock_nsi)) {
                /* Check the domain name. ssl_post_connect_check prints an
                   error message if appropriate. */
                if (!ssl_post_connect_check((SSL *)nsi_getssl(cs->sock_nsi), o.target))
                    bye("Certificate verification error.");

                if (cs->ssl_session) {
                    if (cs->ssl_session == (SSL_SESSION *)
                            (nsi_get0_ssl_session(cs->sock_nsi))) {
                        /* nothing required */
                    } else {
                        SSL_SESSION_free((SSL_SESSION *) cs->ssl_session);
                        cs->ssl_session = (SSL_SESSION *)
                            (nsi_get1_ssl_session(cs->sock_nsi));
                    }
                } else {
                    cs->ssl_session = (SSL_SESSION *)
                        (nsi_get1_ssl_session(cs->sock_nsi));
                }
            }
#endif

            connect_report(cs->sock_nsi);

            /* Create IOD for nsp->stdin */
            if ((cs->stdin_nsi = nsi_new2(nsp, 0, NULL)) == NULL)
                bye("Failed to create stdin nsiod.");

            /* command to execute */
            if (o.cmdexec) {
             /* Convert Nsock's non-blocking socket to an ordinary blocking one. It's
                possible for a program to write fast enough that it will get an
                EAGAIN on write on a non-blocking socket.*/
                block_socket(mysock);
                netexec(mysock, o.cmdexec);
            }

           
            if (o.sendonly == 0) {
                /* socket event? */
                cs->latest_readsockev =
                    nsock_read(nsp, cs->sock_nsi, connect_evt_handler,
                               read_timeout, cs);
            }

            if (o.recvonly == 0 && holdstdin == 0) {
                /* stdin-fd event? */
                cs->latest_readstdinev =
                    nsock_readbytes(nsp, cs->stdin_nsi,
                                    connect_evt_handler, read_timeout, cs, 0);
            }

            break;

        case NSE_TYPE_READ:
            /* read buffer */
            buf = nse_readbuf(evt, &nbytes);

            /* READ from socket */
            if (nsi == cs->sock_nsi) {
                if (o.linedelay)
                    ncat_delay_timer(o.linedelay);

                if (o.sendonly == 0) {
                    
                        if (o.telnet)
                            dotelnet(mysock, (unsigned char *) buf, nbytes);

                        /* Write socket data to stdout */
                        Write(STDOUT_FILENO, buf, nbytes);
                        ncat_log_recv(buf, nbytes);

                        cs->latest_readsockev =
                            nsock_readbytes(nsp, cs->sock_nsi,
                                       connect_evt_handler,
                                       read_timeout, cs, 0);
                    
                }
            } else {
                /* read from stdin */
                if (o.linedelay)
                    ncat_delay_timer(o.linedelay);

                if (o.recvonly == 0) {
                    char *tmp = NULL;

                    if (o.crlf) {
                        if (fix_line_endings(buf, &nbytes, &tmp))
                            buf = tmp;
                    }

                    nsock_write(nsp, cs->sock_nsi, connect_evt_handler,
                            write_timeout, cs, buf, nbytes);
                    ncat_log_send(buf, nbytes);

                    if (tmp)
                        free(tmp); /* buf */
                }

            }

            break;

        case NSE_TYPE_WRITE:
            if (nsi == cs->sock_nsi) {
                /* The write to the socket was successful. Allow reading
                   more from stdin now. */
                cs->latest_readstdinev =
                    nsock_readbytes(nsp, cs->stdin_nsi,
                            connect_evt_handler, read_timeout, cs, 0);
            }
            break;

        case NSE_TYPE_TIMER:
            break;

        default:
            bye("connect_evt_handler got bogus type.");
            break;
        }   /* end switch */

    } else if (status == NSE_STATUS_EOF) {
        /* Close up if we either got EOF from network side (e.g. the
         * TCP connection closed on remote side), or if we got EOF
         * on stdin while using --send-only mode. */
        if (nsi == cs->sock_nsi || o.sendonly) {
            if(o.verbose)
                loguser("Finished. %lu bytes sent, %lu bytes received. \n",nsi_get_write_count(cs->sock_nsi),nsi_get_read_count(cs->sock_nsi));
            nsi_delete(cs->stdin_nsi, NSOCK_PENDING_NOTIFY);
            nsi_delete(cs->sock_nsi, NSOCK_PENDING_NOTIFY);
        }
    } else if (status == NSE_STATUS_ERROR) {
        loguser("%s.\n", socket_strerror(nse_errorcode(evt)));
        exit(1);
    } else if (status == NSE_STATUS_TIMEOUT) {
        loguser("%s.\n", socket_strerror(ETIMEDOUT));
        exit(1);
    }

    return;
}