/* pingerThread.cpp */
/* DEBUG: section 42    ICMP Pinger program                     */
/* Squid/2 pinger internal thread implementation Evgeny Kotsuba */
#include "squidDeclaration.h"

int CheckChangeDir(void);

#include "squid.h"

#ifdef _SQUID_OS2VAC_
#if USE_ICMP

 #define INCL_DOS
 #define INCL_DOSERRORS
 #include <os2.h>

 #include <direct.h>
 #include <float.h>
 #include <io.h>
 #include <sys\stat.h>

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>

int startPingerThread(char *PingerQueueR,char *PingerQueueW);
void _Optlink Pinger_Work(void *param);
int PingerMain(void);

#define STACK_SIZE      32000+sizeof(fd_set)

static volatile int PingerThreadSts = 0;
static char *PingerThreadQueueNameR;
static char *PingerThreadQueueNameW;
static HQUEUE pingerHqR;
static HQUEUE pingerHqW;
static PID pidOwnerW;

int startPingerThread(char *PingerQueueR,char *PingerQueueW)
{  int id, rc;
   if(PingerThreadSts)
                 return 1;
   PingerThreadQueueNameW = PingerQueueR;  /* inverse read-write with respect to ICMP.cpp */
   PingerThreadQueueNameR = PingerQueueW;

    rc = DosOpenQueue (&pidOwnerW,         /* PID of queue owner              */
                       &pingerHqW,     /* Handle for created queue        */
                        PingerThreadQueueNameW);      /* Name of the queue to open       */
    if (rc!= NO_ERROR)
    {   debug(42, 0) ("WARNING:startPingerThread DosOpenQueue error rc=%u\n",rc);
       return 1;
    }


   rc = DosCreateQueue(&pingerHqR,QUE_FIFO|QUE_CONVERT_ADDRESS,PingerThreadQueueNameR);
   if (rc)
   {   debug(42, 0) ("WARNING:startPingerThread DosCreateQueue error rc=%u\n",rc);
       return 1;
   }


   PingerThreadSts = 1;
   id = _beginthread(Pinger_Work,NULL, STACK_SIZE,NULL);

   do
   { DosSleep(1);
   } while(PingerThreadSts == 1);

   return 0;
}

void _Optlink Pinger_Work(void *param)
{   int i,rc, threadNum,id,idd;
    int ncmd,data,l;
//    char str[512];
//    char buf[MAX_BUF], bufout[MAX_BUF];

    _control87(EM_UNDERFLOW,EM_UNDERFLOW);

//    debug(42, 2) ("Pinger_Work: started\n");
    PingerThreadSts = 2;
   DosSleep(10);

     PingerMain();

//   __lxchg(&LSthreads.semaphore, UNLOCKED);
//   do
//   { DosSleep(1);
//   } while(ThreadStart != 3);

    if(pingerHqR)
    {
       rc = DosCloseQueue(pingerHqR);         /* Close the queue */
       if (rc) {
           debug(42, 0) ("WARNING:Pinger_Work:DosCloseQueue error: return code = %u\n", rc);
       }
      pingerHqR = NULL;
    }

//    debug(0, 2) ("Pinger_Work: ended\n");

}
/***************************************************/

#ifndef _SQUID_LINUX_
#ifndef _SQUID_CYGWIN_
#define icmphdr icmp
#define iphdr ip
#endif
#endif

#if ALLOW_SOURCE_PING
#define MAX_PKT_SZ 8192
#define MAX_PAYLOAD (MAX_PKT_SZ - sizeof(struct icmphdr) - sizeof (char) - sizeof(struct timeval) - 1)
#else
#define MAX_PAYLOAD SQUIDHOSTNAMELEN
#define MAX_PKT_SZ (MAX_PAYLOAD + sizeof(struct timeval) + sizeof (char) + sizeof(struct icmphdr) + 1)
#endif

typedef struct {
    struct timeval tv;
    unsigned char opcode;
    char payload[MAX_PAYLOAD];
} icmpEchoData;

static int icmp_ident = -1;
static int icmp_pkts_sent = 0;

static int icmp_sock_pinger = -1;
//static int icmpIPCsock = -1;
//static int icmpIPCsockW = -1;
static const char *hello_string = "hi there\n";

static const char *icmpPktStr[] =
{
    "Echo Reply",
    "ICMP 1",
    "ICMP 2",
    "Destination Unreachable",
    "Source Quench",
    "Redirect",
    "ICMP 6",
    "ICMP 7",
    "Echo",
    "ICMP 9",
    "ICMP 10",
    "Time Exceeded",
    "Parameter Problem",
    "Timestamp",
    "Timestamp Reply",
    "Info Request",
    "Info Reply",
    "Out of Range Type"
};

static int in_cksum(unsigned short *ptr, int size);
static void pingerRecv(void);
static void pingerLog(struct icmphdr *, struct in_addr, int, int);
static int ipHops(int ttl);
static void pingerSendtoSquid(pingerReplyData * preply);


void pingerOpen(void)
{
    struct protoent *proto = NULL;
    int rc;

    if ((proto = getprotobyname("icmp")) == 0) {
       debug(42, 0) ("WARNING:pingerOpen: unknown protocol: icmp\n");
       exit(1);
    }
    icmp_sock_pinger = socket(PF_INET, SOCK_RAW, proto->p_proto);
    if (icmp_sock_pinger < 0) {
       debug(42, 0) ("WARNING:pingerOpen: icmp_sock: %s\n", xstrerror_a());
       exit(1);
    }
    icmp_ident = getpid() & 0xffff;

    rc = squid_fd_table.open(icmp_sock_index,icmp_sock_pinger, FD_SOCKET, "Pinger Socket");
    debug(42, 1) ("pinger: ICMP socket opened %i\n",icmp_sock_pinger);
}

void _Optlink pingerClose(void)
{   if(icmp_sock_pinger > 0)
    { if(icmp_sock_index >= 0)
      {  squid_fd_table.close(icmp_sock_index);
      }
//       soclose(icmp_sock_pinger);
    }
    icmp_sock_pinger = -1;
    icmp_sock_index = -1;
    icmp_ident = 0;
}

int recvICMP_thread(char *buf, int buflen)
{
   REQUESTDATA      Request;
   ULONG            ulDataLen, numMsg;
   BYTE             bPriority;
   char *buffer;
   int rc, rc0;

    rc = DosQueryQueue(pingerHqR, &numMsg);
    if(numMsg == 0)
        return 0;
    rc = DosReadQueue (pingerHqR,          /* Queue to read from          */
                       &Request,              /* Request data from write     */
                       &ulDataLen,            /* Length of data returned     */
                       (PPVOID) &buffer,   /* The data                    */
                       0L,                    /* Remove first element        */
                       DCWW_WAIT,             /* Wait for available data     */
                       &bPriority,             /* Priority of data (returned) */
                       0L);             /* Semaphore to use when not waiting */

    if(ulDataLen >  buflen)  ulDataLen = buflen;
    memcpy(buf,buffer,ulDataLen);

    if (rc)
    {   debug(42, 1) ("WARNING:recvICMP DosReadQueue error rc=%u\n",rc);
        rc0 = -1;
    } else {
        rc0 = ulDataLen;
    }
    return rc0;
}

int sendICMP_thread(char *buf, int buflen)
{   int rc;
    int rc0 = buflen;

    rc = DosWriteQueue (pingerHqW,        /* Queue to write to           */
                        12345L,              /* Request data                */
                        buflen,  /* Length of data to write     */
                        buf,          /* Pointer to data             */
                        0L);              /* Priority (not applicable here) */

    if (rc)
    {   debug(42, 1) ("WARNING:sendICMP DosReadQueue error rc=%u\n",rc);
        rc0 = -1;
    }
    return rc0;
}


static void
pingerSendEcho(struct in_addr to, int opcode, char *payload, int len)
{
    LOCAL_ARRAY(char, pkt, MAX_PKT_SZ);
    struct icmphdr *icmp = NULL;
    icmpEchoData *echo;
    int icmp_pktsize = sizeof(struct icmphdr);
    struct sockaddr_in S;
    memset(pkt, '\0', MAX_PKT_SZ);
    icmp = (struct icmphdr *) (void *) pkt;

    /*
     * cevans - beware signed/unsigned issues in untrusted data from
     * the network!!
     */
    if (len < 0) {
       len = 0;
    }
    icmp->icmp_type = ICMP_ECHO;
    icmp->icmp_code = 0;
    icmp->icmp_cksum = 0;
    icmp->icmp_id = icmp_ident;
    icmp->icmp_seq = (u_short) icmp_pkts_sent++;
    echo = (icmpEchoData *) (icmp + 1);
    echo->opcode = (unsigned char) opcode;
    echo->tv = current_time;
    icmp_pktsize += sizeof(icmpEchoData) - MAX_PAYLOAD;
    if (payload) {
       if (len > MAX_PAYLOAD)
           len = MAX_PAYLOAD;
       xmemcpy(echo->payload, payload, len);
       icmp_pktsize += len;
    }
    icmp->icmp_cksum = in_cksum((u_short *) icmp, icmp_pktsize);
    S.sin_family = AF_INET;
    /*
     * cevans: alert: trusting to-host, was supplied in network packet
     */
    S.sin_addr = to;
    S.sin_port = 0;
    assert(icmp_pktsize <= MAX_PKT_SZ);
    sendto(icmp_sock_pinger,
       pkt,
       icmp_pktsize,
       0,
       (struct sockaddr *) &S,
       sizeof(struct sockaddr_in));
    pingerLog(icmp, to, 0, 0);
}

static void
pingerRecv(void)
{
    int n;
    socklen_t fromlen;
    struct sockaddr_in from;
    int iphdrlen = 20;
    struct iphdr *ip = NULL;
    struct icmphdr *icmp = NULL;
    static char *pkt = NULL;
    struct timeval now;
    icmpEchoData *echo;
    static pingerReplyData preply;
//    int i,raz;

    if (pkt == NULL)
       pkt = (char *) xmalloc(MAX_PKT_SZ);

// memset(pkt,0,MAX_PKT_SZ);

    fromlen = sizeof(from);
    n = recvfrom(icmp_sock_pinger,
       pkt,
       MAX_PKT_SZ,
       0,
       (struct sockaddr *) &from,
       &fromlen);
#if GETTIMEOFDAY_NO_TZP
    gettimeofday(&now);
#else
    gettimeofday(&now, NULL);
#endif

//    debug(42,1 /* 9 */) ("pingerRecv: %d bytes from %s\n", n, inet_ntoa(from.sin_addr));

    ip = (struct iphdr *) (void *) pkt;
/*
Internet STD5, Internet Protocol
  IHL: 4 bits
    Internet Header Length is the length of the internet header in 32
    bit words, and thus points to the beginning of the data. Note that
    the minimum value for a correct header is 5.
*/
    if(ip->ip_hl < 5) ip->ip_hl = 5;
#if HAVE_IP_HL
    iphdrlen = ip->ip_hl << 2;
#else /* HAVE_IP_HL */
#if WORDS_BIGENDIAN
    iphdrlen = (ip->ip_vhl >> 4) << 2;
#else
    iphdrlen = (ip->ip_vhl & 0xF) << 2;
#endif
#endif /* HAVE_IP_HL */
//    raz = 0;
    icmp = (struct icmphdr *) (void *) (pkt + iphdrlen);
    if (icmp->icmp_type != ICMP_ECHOREPLY)
    { //raz++;
      //iphdrlen += 4; //EK HACK ?????
      //icmp = (struct icmphdr *) (void *) (pkt + iphdrlen);
      //  if (icmp->icmp_type != ICMP_ECHOREPLY)
                   return;
    }
//    debug(42,8 ) ("pingerRecv: %d bytes from %s, icmp_type=%x, icmp_id=%x %x, raz=%i\n",
//              n, inet_ntoa(from.sin_addr),icmp->icmp_type,icmp->icmp_id,icmp_ident, raz);
    if (icmp->icmp_id != icmp_ident)
       return;
    echo = (icmpEchoData *) (void *) (icmp + 1);
    preply.from = from.sin_addr;
    preply.opcode = echo->opcode;
    preply.hops = ipHops(ip->ip_ttl);
    preply.rtt = tvSubMsec(echo->tv, now);
    preply.psize = n - iphdrlen - (sizeof(icmpEchoData) - MAX_PKT_SZ);
    pingerSendtoSquid(&preply);
    pingerLog(icmp, from.sin_addr, preply.rtt, preply.hops);
}


static int
in_cksum(unsigned short *ptr, int size)
{
    long sum;
    unsigned short oddbyte;
    unsigned short answer;
    sum = 0;
    while (size > 1) {
       sum += *ptr++;
       size -= 2;
    }
    if (size == 1) {
       oddbyte = 0;
       *((unsigned char *) &oddbyte) = *(unsigned char *) ptr;
       sum += oddbyte;
    }
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    answer = ~sum;
    return (answer);
}

static void
pingerLog(struct icmphdr *icmp, struct in_addr addr, int rtt, int hops)
{
    debug(42, 2) ("pingerLog: %9d.%06d %-16s %d %-15.15s %dms %d hops\n",
       (int) current_time.tv_sec,
   (int) current_time.tv_usec,
   inet_ntoa(addr),
   (int) icmp->icmp_type,
   icmpPktStr[icmp->icmp_type],
   rtt,
   hops);
}

static int
ipHops(int ttl)
{
    if (ttl < 33)
       return 33 - ttl;
    if (ttl < 63)
       return 63 - ttl;        /* 62 = (64+60)/2 */
    if (ttl < 65)
       return 65 - ttl;        /* 62 = (64+60)/2 */
    if (ttl < 129)
       return 129 - ttl;
    if (ttl < 193)
       return 193 - ttl;
    return 256 - ttl;
}

static int
pingerReadRequest(void)
{
    static pingerEchoData pecho;
    int n,rc,ncmd,data;
    int guess_size;


    memset(&pecho, '\0', sizeof(pecho));
//    n = recv(icmpIPCsock, (char *) &pecho, sizeof(pingerEchoData), 0);
    n = recvICMP_thread((char *) &pecho, sizeof(pingerEchoData));

    if (n < 0)
       return n;
    if (0 == n) {
       /* EOF indicator */
       fprintf(stderr, "EOF encountered\n");
       errno = 0;
       return -1;
    }
    if(pecho.cmd == 1)  /* Shutdown cmd  */
            return -2;
    guess_size = n - (sizeof(pingerEchoData) - PINGER_PAYLOAD_SZ);
    if (guess_size != pecho.psize) {
       fprintf(stderr, "size mismatch, guess=%d psize=%d\n",
           guess_size, pecho.psize);

       /* don't process this message, but keep running */
       return 0;
    }
    pingerSendEcho(pecho.to,
       pecho.opcode,
       pecho.payload,
       pecho.psize);
    return n;
}

static void
pingerSendtoSquid(pingerReplyData * preply)
{
//  int len = sizeof(pingerReplyData) - MAX_PKT_SZ + preply->psize;
    int len = sizeof(pingerReplyData) - PINGER_PAYLOAD_SZ + preply->psize;
    int rc;
//debug(42, 1) ("pingerSendtoSquid sending %i bytes, sizof=%i, MAX_PKT_SZ=%i, psize=%i, \n",
//     len, sizeof(pingerReplyData), MAX_PKT_SZ,  preply->psize);

    rc = sendICMP_thread((char *) preply, len);

//    if (send(icmpIPCsockW, (char *) preply, len, 0) < 0)
    if(rc < 0)
    {  printf("pinger: send: %s\n", xstrerror_a());
       exit(1);
    }
}


int PingerMain(void)
{
    fd_set R;
    int sockList[4];
    int x;
    int ier;
    struct timeval tv;
    char *t;
    time_t last_check_time = 0;
    int rep = 0,rc, ncmd,data;

    int cfd = -1;

    struct sockaddr_un PS;
    int fd_os2[2];
    int fd_select[2];
    ULONG numMsg;
    char buff[80];

/* send and recv hello */
   x = sendICMP_thread((char *)hello_string , strlen(hello_string)+1);
        if (x <0 ) {
            printf("sendICMP_thread: send failed:\n");
            DosBeep(1000,1000);
            exit(1);
        }
//       DosBeep(1000,10);
   do
   {   DosSleep(10);
       x = recvICMP_thread((char *)buff, 12);
   } while(!x);

    if(x > 0)
    {  if(strcmp(buff,"hi hi"))
       { debug(42, 0) ("WARNING:PingerMain Hello string wrong\n");
         return;
       }
    }

/*
 * cevans - do this first. It grabs a raw socket. After this we can
 * drop privs
 */
    pingerOpen();
    atexit(pingerClose);
//   DosBeep(2000,10);
//   DosSleep(100);
//   DosBeep(2000,10);
//return  0;
    getCurrentTime();

    for (;;)
    {
       tv.tv_sec = 10;
       tv.tv_usec = 0;
//       FD_ZERO(&R);
//       FD_SET(0, &R);
//       FD_SET(icmp_sock_pinger, &R);
       sockList[0] = icmp_sock_pinger;
//       sockList[1] = icmpIPCsock;
#ifdef TCPV40HDRS
       x = select(sockList, 1,0,0, 10000);
#else
       x = os2_select(sockList, 1,0,0, 10000);
#endif
       if (x < 0)
       {    debug(42, 1) ("WARNING: $pinger: select return %i\n", x);
                break;
       }

       getCurrentTime();
//       if( x == 0)
//              continue;
       rc = DosQueryQueue(pingerHqR, &numMsg);
       if(numMsg)
       {
         rc = pingerReadRequest();
         if(rc < 0)
         {      printf("Pinger exiting.\n");
                break;
               // exit(1);
         }

//       rc = SQUIDpipe.RecvCmdFromClient(&ncmd,&data);
       }

    if(x > 0)
       if(sockList[0] > 0)
       {   // DosBeep(500,30);   DosBeep(1000,60);
           // DosBeep(500,30);   DosBeep(1000,60);
//   debug(42, 1) ("$pinger: select %d socket ready\n", sockList[0]);

           pingerRecv();
       }
       if (10 + last_check_time < squid_curtime) {
         //  if (send(icmpIPCsockW, (char *) &tv, 0, 0) < 0)
         //      exit(1);
           last_check_time = squid_curtime;
       }
    }
    /* NOTREACHED */
}

#endif // USE_ICMP
#endif //_SQUID_OS2VAC_
