/*
 ============================================================================
 Name        : pingscan.c
 Author      : Chebotarev Roman
 Version     : 0.9
 Copyright   : All rights reserved
 Description : pingscan project
 ============================================================================
 */

/* Need for compile in Visual Studio
 * #define WIN32_LEAN_AND_MEAN
 * #define _MT
 */
#include <winsock2.h>
#include <Ipexport.h>
#include <process.h>
#include <stdio.h>
#include <stdint.h>
#include "pingscan.h"
#include "func.h"
#include "raw_ping.h"

uint32_t    start_scan_time;
uint32_t    end_scan_time;
/* For scan mode */
IcmpSendEcho     icmp_echo;                /* Pointer to function. See pingscan.h */
IcmpCreateFile     icmp_create_file;        /* Pointer to function. See pingscan.h */
IcmpCloseHandle    icmp_close_file;
uint32_t         hosts_count;            /* Total hosts responded */
/* For flood mode */
uint8_t            flood_mode;
uint32_t         total_sended;            /* Total number of sended requests */
uint32_t         unfinished_echoes_count;/* The number of requests which have not received answers */

int main(int arc, char *arv[])
{
    WSADATA     wsa_data;
    HINSTANCE    icmp_lib;                    /* Handle DLL which contains ICMP functions */
    uint32_t    threads_count;                /* The maximum number of threads created by the program */
    HANDLE        threads[MAX_THREADS_COUNT]; /* Array of threads for ICMP senders */
    DWORD         th_id[MAX_THREADS_COUNT];    /* Array of threads identifiers */    
    uint32_t     i = 0;
    uint32_t     start_address;                /* Start of scanned IP addresses range */
    uint32_t     end_addr;                    /* End of scanned IP addresses range */
    int32_t        free_thread_handle_num;
    uint32_t     addr = 0;

    CRITICAL_SECTION host_incr;                /* Critical section, used for increment hosts_count 
                                                value from different threads */
    CRITICAL_SECTION unfin_echoes;            /* Critical section, used for increment unfinished_echoes 
                                                value from different threads */
    struct config config_params;            /* Various configuration settings. See pingscan.h */
    struct echo_params * ping_args;
    
    ZeroMemory(&config_params, sizeof(config_params));
    config_params.netmask_len = DEF_SUBNET_MASK_LEN;
    config_params.packets_count = DEF_PACKET_COUNT;
    config_params.wait_time = DEF_WAIT_TIME;
    config_params.packet_size = DEF_PACKET_SIZE;

    if(!parsing_cmd_arguments(arc, arv, &config_params)) 
        usage();

    if (WSAStartup(0x0101, &wsa_data) != 0)
    {
        fprintf(stderr, "WSAStartup failed: %ld\n", GetLastError());
        return -1;
    }

    /* Check WinSock version */
   if (0x0101 != wsa_data.wVersion)
   {
       fprintf(stderr,"\nWinSock version 1.1 not supported\n");
       WSACleanup();
       return -1;
   }    

    SetConsoleCtrlHandler(keyboard_interrupt_handler, 1);

    /* Calculate first IP address of scanning range */
    start_address = ntohl(inet_addr(config_params.subnet_ptr)) >> (32 - config_params.netmask_len);
    start_address = htonl( (start_address << (32 - config_params.netmask_len)) );

    /* Calculate last IP address of scanning range */
    if(config_params.netmask_len == 31) 
        end_addr = start_address + htonl(1);
    else if(config_params.netmask_len == 32) 
        end_addr = start_address;
    else 
        end_addr = htonl( ntohl(start_address) | 
                            ((0xFFFFFFFF << config_params.netmask_len) >> config_params.netmask_len) 
                        );

    start_scan_time = GetTickCount();
    if(config_params.flood)
    {    /* Ping-flood mode */
        flood_mode = 1;
        InitializeCriticalSection(&unfin_echoes);
        struct recving_echoes_params thread_recv_params;
        thread_recv_params.common_params = &config_params;
        thread_recv_params.dst_ip = start_address;
        thread_recv_params.critical_section = &unfin_echoes;
        thread_recv_params.unfinished_echoes = &unfinished_echoes_count;
        /* Start thread which receiving ICMP echoes */
        threads[0] = (HANDLE)_beginthreadex(NULL, 0, recving_echoes_threads, (void*) &thread_recv_params, 
                        0, (uint32_t *) th_id);
        /* Start sending flood */
        printf("Loss: ");
        send_echoes(start_address, config_params.packet_size, config_params.packets_count, 
                        &unfin_echoes, &unfinished_echoes_count);
        /* Waiting last packets in thread... */
        WaitForSingleObject(threads[0], (DWORD) config_params.wait_time * 2);
        /* Forcibly termitate recving thread */
        TerminateThread(threads[0], 0);
        DeleteCriticalSection(&unfin_echoes);
        printf(" (%u packets)\n", unfinished_echoes_count);
    }
    else
    {
        /* Starting multi-threads ping */
        
        /* Check for availability needed functions. 
         * Remark from http://msdn.microsoft.com/en-us/library/aa366045(VS.85).aspx:
         * 
         * The IcmpCreateFile function is exported from the Icmp.dll on Windows 2000. 
         * The IcmpCreateFile function is exported from the Iphlpapi.dll on Windows XP and later. 
         * OS version checking is not recommended to use this function. Applications requiring 
         * portability with this function across Windows 2000, Windows XP, Windows Server 2003 
         * and later Windows versions should not statically link to either the Icmp.lib or the 
         * Iphlpapi.lib file. Instead, the application should check for the presence of 
         * IcmpCreateFile in the Iphlpapi.dll with calls to LoadLibrary and GetProcAddress. 
         * Failing that, the application should check for the presence of IcmpCreateFile in the 
         * Icmp.dll with calls to LoadLibrary and GetProcAddress.
         */

        /* Trying used functions from Iphlpapi.dll */
        icmp_lib = LoadLibrary(ICMP_DLL_XP);
        if(!icmp_lib)
        {
            printf("LoadLibrary failed!\n");
            return -1;
        }
        
        icmp_create_file = (IcmpCreateFile) GetProcAddress(icmp_lib, "IcmpCreateFile");
        if(!(DWORD)icmp_create_file)
        {
            FreeLibrary(icmp_lib);
            /* Trying use functions from Iphlpapi.dll */
            icmp_lib = LoadLibrary(ICMP_DLL_2K);
            icmp_create_file = (IcmpCreateFile) GetProcAddress(icmp_lib, "IcmpCreateFile");
            if(!(DWORD)icmp_create_file)
            {
                printf("Failed get address IcmpCreateFile!\n");        
                return -1;
            }
        }
        icmp_echo = (IcmpSendEcho) GetProcAddress(icmp_lib, "IcmpSendEcho");
        if(!(DWORD)icmp_echo)
        {
            printf("Failed get address IcmpSendEcho!\n");
            return -1;
        }
        icmp_close_file = (IcmpCloseHandle) GetProcAddress(icmp_lib, "IcmpCloseHandle");
        if(!(DWORD)icmp_close_file)
        {
            printf("Failed get address IcmpCloseFile");
            return -1;
        }
        /* Calculate threads number for running multi-threads pinging */  
        if(end_addr == start_address)
            threads_count = 1;
        else
        {
            if( (ntohl(end_addr) - ntohl(start_address)) <= MAX_THREADS_COUNT)
                threads_count = ntohl(end_addr) - ntohl(start_address) + 1;
            else threads_count = MAX_THREADS_COUNT;
        }
    
        printf("Pingscan version %s started.\n", VERSION);
        InitializeCriticalSection(&host_incr); 
        
        for(i = 0, addr = start_address; i < threads_count; ++i)
        {
            ping_args = malloc(sizeof(struct echo_params));
            if(!ping_args)
            {
                fprintf(stderr, "Can't allocate memory for arguments of thread! Exit.\n");
                ExitProcess(-1);            
            }
            ping_args -> config_params = &config_params;
            ping_args -> dst_ip = addr;
            ping_args -> hosts_count = &hosts_count;
            ping_args -> host_incr = &host_incr; 
            ping_args -> release_mem_after_use = 1;
            threads[i] = (HANDLE)_beginthreadex(NULL, 0, one_ping, (void*) ping_args, 0, 
                                                    (uint32_t *) &th_id[i]);
            addr += htonl(1);
            
        }
        /* Waiting first finishing thread from array of threads and running new thread instead him */
        free_thread_handle_num = WaitForMultipleObjects(threads_count, (HANDLE*)&threads,
                                    0, INFINITE) - WAIT_OBJECT_0;
        while(ntohl(addr) <= ntohl(end_addr))
        {
            ping_args = malloc(sizeof(struct echo_params));
            if(!ping_args)
            {
                fprintf(stderr, "Can't allocate memory for arguments of thread! Exit.\n");
                ExitProcess(-1);            
            }
            ping_args -> config_params = &config_params;
            ping_args -> dst_ip = addr;
            ping_args -> hosts_count = &hosts_count;
            ping_args -> host_incr = &host_incr; 
            ping_args -> release_mem_after_use = 1;
            threads[free_thread_handle_num]=
                (HANDLE)_beginthreadex(NULL, 0, one_ping, (void*) ping_args, 0, (uint32_t *) &th_id[free_thread_handle_num]);
            /* Waiting finished next thread from array of threads and running new thread instead him */
            free_thread_handle_num = WaitForMultipleObjects(threads_count, (HANDLE*) &threads, 0, INFINITE) - WAIT_OBJECT_0;    
            addr = htonl(ntohl(addr) + 1);
        }
        /* Waiting finished all running ping-threads */
        WaitForMultipleObjects(threads_count, (HANDLE*) &threads, 1, INFINITE);
        DeleteCriticalSection(&host_incr);
    
        if(!FreeLibrary(icmp_lib))
        {
            printf("FreeLibrary failed!\n");
            return -1;
        }
    }
    end_scan_time = GetTickCount();
    WSACleanup();
    printf("Total hosts scanned: %ld in %g seconds.\n", ntohl(end_addr) - ntohl(start_address) + 1,
            (float)(end_scan_time - start_scan_time)/1000);
    if(flood_mode)
    {
        printf("Flood mode statistic: ");
        printf("%u packets transmitted, %u received, %.2f%% packets loss.\n",
                total_sended, total_sended - unfinished_echoes_count,
                ((float)unfinished_echoes_count / total_sended) * 100);
        printf("Pingscan finished.\n");
    }
    else
    printf("Pingscan finished - %d hosts found.\n", hosts_count);    
    return 0;
}