/**
 * TCP port scanner (SYN scan)
 * Probably education use only - this is just an simple
 * example.
 * 		- urug@urug.net
 */

#include <netinet/in.h>	// sockaddr
#define __FAVOR_BSD
#include <netinet/tcp.h>// tcphdr
#include <netinet/ip.h> // ip

#include <unistd.h>	// exit
#include <iostream>	// io
#include <cassert>	// assert
#include <cstring>	// memset, memcpy
#include <sys/socket.h>	// socket, inet_ntoa
#include <arpa/inet.h>	// inet_ntoa
#include <cstdlib>	// rand
#include <pthread.h>	// pthread
#include <netdb.h>	// gethostbyname
#include <cerrno>	// errno
#include <ctime>	// time

using std::cout;
using std::endl;

int  opt_dport = -1;		// Port do sprawdzenia (nie skanuj
                                // wszystkiego)

#define TCPMAXPORT	65535
#define TCPMINPORT	0
#define TCPDELAY	5000

struct net_addr
{
    sockaddr_in	daddr, saddr;

    in_addr	*dst;	// &daddr.sin_addr
    in_addr	*src;	// &saddr.sin_addr
};

struct net_prot
{
    tcphdr tcp;
};

struct connection
{
    net_addr	link;
    net_prot	pkt;

    int		sock;
};


/**
 * usage --
 *	Wyswietla informacje o przelacznikach itp.
*/
void usage( const char *fn )
{
    cout << "Weasel SYN scanner by urug(at)urug.net" << endl
         << "Usage: " << fn << " [options] <source> <target>" << endl << endl
         << "options:" << endl
         << "\t-p port\t:: check only this port" << endl << endl
         << "Change source to your real source IP address." << endl
         << "It's not ip spoofing feature!" << endl;

    exit(1);
}


/**
 * make_link --
 *	Zwraca socket, wypelnia target informacjami na temat celu.
*/
int make_link( net_addr *link, const char *dst, const char *src )
{
    link->dst = &link->daddr.sin_addr;
    link->src = &link->saddr.sin_addr;

    link->daddr.sin_family = link->saddr.sin_family = PF_INET;
    
    struct hostent *dst_host = gethostbyname(dst);
    if ( !dst_host )
    {
        cout << "gethostbyname(): " << hstrerror(h_errno) << endl;
        return -1;
    }
    memcpy(&link->dst->s_addr, dst_host->h_addr_list[0], dst_host->h_length);

    struct hostent *src_host = gethostbyname(src);
    if ( !src_host )
    {
        cout << "gethostbyname(): " << hstrerror(h_errno) << endl;
        return -1;
    }
    memcpy(&link->src->s_addr, src_host->h_addr_list[0], src_host->h_length);

    int sock = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
    if ( sock == -1 )
    {
        cout << "socket(): " << strerror(errno) << endl;
        return -1;
    }

    return sock;
}


/**
 * print_link --
 * 	Wyswietla informacje o polaczeniu.
*/
void print_hello( const net_addr *link )
{
    cout << "[Scanning " << inet_ntoa(*link->dst);
    if ( opt_dport > -1 )
    {
        cout << ":" << opt_dport;
    }
    cout << "] from [" << inet_ntoa(*link->src) << "]" << endl;
}


/**
 * close_link --
 *	Zakoncz polaczenie.
*/
void close_link( int sock )
{
    close(sock);
}


/**
 * tcp_header_template --
 * 	Przygotowuje wzor naglowka TCP, wypelnia wiekszosc potrzebnych pol.
*/
void proto_header_template( net_prot *pkt, const size_t size )
{
    memset(pkt, 0, size);

    // potrzebny do generowania portow zrodlowych.
    srand(time(NULL));

    pkt->tcp.th_sport = htons(rand() % TCPMAXPORT);
    pkt->tcp.th_off = 5;
    pkt->tcp.th_win = htons(TCP_MSS);
    pkt->tcp.th_flags = TH_SYN;
}


/**
 * tcp_dport_set --
 * 	Ustaw port docelowy w naglowku TCP
*/
void tcp_dport_set( net_prot *pkt, const int dport )
{
    pkt->tcp.th_dport = htons(dport);
}


/**
 * in_cksum -- (Kod z ping.c)
 * 	Algorytm liczacy sume kontrolna
*/
unsigned short in_cksum( unsigned char *data, int len )
{
    long sum = 0;  /* assume 32 bit long, 16 bit short */
    unsigned short *temp = (unsigned short *)data;
            
    while(len > 1) {
        sum += *temp++;
        if(sum & 0x80000000)   /* if high order bit set, fold */
            sum = (sum & 0xFFFF) + (sum >> 16);
        len -= 2;
    }
    
    if(len)       /* take care of left over byte */
        sum += (unsigned short) *((unsigned char *)temp);
    
    while(sum>>16)
        sum = (sum & 0xFFFF) + (sum >> 16);
    
    return ~sum;
}


/**
 * tcp_cksum -- (z google.pl ;])
 *	Policz sume kontrolna pakietu.
*/
void tcp_cksum( net_prot *pkt, const net_addr *link )
{
    struct pseudo_header 
    {
        unsigned long  source_ip;
        unsigned long  dest_ip;
        unsigned char  reserved;
        unsigned char  protocol;
        unsigned short tcp_length;
    };

    unsigned char *hdr = new unsigned char[ sizeof(pseudo_header) + sizeof(pkt->tcp) ];
    pseudo_header *pseudo = reinterpret_cast<pseudo_header *>(hdr);

    // Wyzeruj dawna sume kontrolna, by nie powstaly przeklamania w nowej
    pkt->tcp.th_sum = 0;

    pseudo->source_ip = link->src->s_addr;
    pseudo->dest_ip = link->dst->s_addr;
    pseudo->reserved = 0;
    pseudo->protocol = IPPROTO_TCP;
    pseudo->tcp_length = htons(sizeof(pkt->tcp));

    memcpy((hdr + sizeof(pseudo_header)), &pkt->tcp, sizeof(pkt->tcp));

    pkt->tcp.th_sum = in_cksum(hdr, sizeof(pseudo_header) + sizeof(pkt->tcp));

    delete [] hdr;
}


/**
 * receiver --
 *	Odbiera pakiety TCP.
*/
void *receiver( void *data )
{
    connection *c = reinterpret_cast<connection *>(data);
    char       *pkt = new char[ sizeof(tcphdr) + sizeof(ip) ];
    int		ret;

    while ( (ret = recvfrom(c->sock, pkt, sizeof(tcphdr) + sizeof(ip), 0, NULL, NULL)) > 0 )
    {
//        ip     *hdrip  = reinterpret_cast<ip *>(pkt);
        tcphdr *hdrtcp = reinterpret_cast<tcphdr *>(pkt + sizeof(ip));

//        cout << inet_ntoa(hdrip->ip_src) << ":" << ntohs(hdrtcp->th_sport) << " => "
//             << inet_ntoa(hdrip->ip_dst) << ":" << ntohs(hdrtcp->th_dport) << endl;

        // Jesli port docelowy pakietu zgadza sie z naszym portem zrodlowym,
        // oraz jesli pakiet ma ustawione flagi SYN oraz ACK, to znaczy ze
        // port jest otwarty.
        if ( hdrtcp->th_dport == c->pkt.tcp.th_sport && hdrtcp->th_flags == (TH_SYN | TH_ACK) )
        {
            cout << "\t<open> TCP/" << ntohs(hdrtcp->th_sport) << endl; 
        }
    }

    if( ret == -1 )
    {
        cout << "recvfrom(): " << strerror(errno) << endl;
    }

    pthread_exit(0);
}


/**
 * main --
 *	Sparsuj parametry startowe, rozpocznij polaczenie
*/
int main( int argc, char **argv )
{
    int ch;
    while ( (ch = getopt(argc, argv, "p:")) > -1 )
    {
        switch (ch)
        {
            case 'p':
                      opt_dport = atoi(optarg);
                      assert(opt_dport >= TCPMINPORT && opt_dport <= TCPMAXPORT);
                      break;
            case '?':
                      exit(1);
            default:
                      usage(argv[0]);
        }
    }

    // Poza przelacznikami, wymagany jest tez target
    if ( argc - optind != 2 )
    {
        usage(argv[0]);
    }
    char *source = argv[optind++];
    char *target = argv[optind];

    connection c;
    c.sock   = make_link(&c.link, target, source);
    if ( c.sock == -1 )
    {
        return 1;
    }

    // Informacje diagnostyczne o polaczeniu.
    print_hello(&c.link);

    pthread_t th_receiver;
    pthread_create(&th_receiver, NULL, receiver, &c);

    proto_header_template(&c.pkt, sizeof(c.pkt));
    bool stop = false;
    for ( int i = TCPMINPORT; i <= TCPMAXPORT && !stop; i++ )
    {
        if ( opt_dport != -1 )
        {
            i = opt_dport;
            stop = true;
        }

        tcp_dport_set(&c.pkt, i);
        tcp_cksum(&c.pkt, &c.link);
        if ( sendto(c.sock, &c.pkt, sizeof(c.pkt), 0, reinterpret_cast<sockaddr *>(&c.link.daddr), sizeof(c.link.daddr)) == -1 )
        {
            cout << "sendto(): " << strerror(errno) << endl;
            break;
        }

        usleep(TCPDELAY);
    }

    pthread_cancel(th_receiver);
    close_link(c.sock);
}

