/* Hostloop -- the Hostloop Library
 * Copyright (C) 1998 Renaud Deraison
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * The 'ping' part was somewhat inspired by the source of 'fping'
 */
 
#include <includes.h>
#ifndef NESSUSNT
#ifdef HAVE_NETINET_IP_H
#include <netinet/ip.h>
#endif
#ifdef HAVE_NETINET_IP_ICMP_H
#include <netinet/ip_icmp.h>
#endif
#endif
#include "hosts_loop.h"
#include "icmp.h"



extern short is_host_present(char *hostname, struct host_in_loop * hil, short num_hil);
extern short is_ip_tested(char * hostname, struct host_in_loop * hil, short num_hil);
struct in_addr get_first_ip(struct hls_globals_t *, char * hostname);
struct in_addr get_next_ip(struct hls_globals_t *, struct in_addr ip);
unsigned char get_ip_number(struct in_addr ip, int pos);
char * get_name_from_ip(struct in_addr ip);

static struct in_addr get_next_ip_class_a(struct in_addr ip);
static struct in_addr get_next_ip_class_b(struct in_addr ip);
static struct in_addr get_next_ip_class_c(struct in_addr ip);

int init_ping();
void send_ping(int soc, struct in_addr addr, int index);
int ping_all_ip(struct in_addr * ips, struct in_addr * ips_ok, int num);
int in_cksum(u_short *, int);


unsigned char get_ip_number(struct in_addr ip, int pos)
{
 unsigned char *l_ip = (char *)(&ip);
 if((pos < 0) || (pos > 3))return(0);
 else return(l_ip[pos]);
}

void set_ip_number(struct in_addr *ip, unsigned char value, int pos)
{
 unsigned char *l_ip = (char *)(ip);
 if((pos<0) || (pos >3))return;
 l_ip[pos]=value;
}

struct in_addr get_first_ip(struct hls_globals_t * hls_globals, char * hostname)
{
 struct hostent * he = NULL;
 struct in_addr ret;
#ifdef USE_PTHREADS
 struct hostent * res = malloc(sizeof(struct hostent));
 char * buf = malloc(4096);
 int h_err;

 gethostbyname_r(hostname, res, buf, 4096, &he, &h_err);
#else
 he = gethostbyname(hostname);
#endif
  ret.s_addr = INADDR_ANY;
 if(!he){
#ifdef USE_PTHREADS
 free(res);
 free(buf);
#endif
 return(ret);
 }
 bzero(&ret, sizeof(struct in_addr));
 bcopy((char *)he->h_addr, (char *)&ret, he->h_length);
#ifdef USE_PTHREADS
 free(res);
 free(buf);
#endif
 switch(hls_globals->subnet_class)
 {
  case HL_SUBNET_CLASS_A :
      set_ip_number(&ret, 0, 1);
  case HL_SUBNET_CLASS_B :
      set_ip_number(&ret, 0, 2);
  case HL_SUBNET_CLASS_C :
      set_ip_number(&ret, 0,3);
      break;
 }
 return(ret);
}

struct in_addr get_next_ip(struct hls_globals_t * hls_globals, 
			   struct in_addr ip)
{
 switch(hls_globals->subnet_class)
 {
 case HL_SUBNET_CLASS_C :
   return(get_next_ip_class_c(ip));
   break;
 case HL_SUBNET_CLASS_B :
   return(get_next_ip_class_b(ip));
   break;
 case HL_SUBNET_CLASS_A :
   return(get_next_ip_class_a(ip));
   break;
  }
 ip.s_addr = INADDR_ANY;
 return(ip);
}

static struct in_addr get_next_ip_class_c(struct in_addr ip)
{
 unsigned char v = get_ip_number(ip, 3);
 if(v==254)ip.s_addr = INADDR_ANY;
 else set_ip_number(&ip, (unsigned char)(v+1), (int)3);
 return(ip);
}

static struct in_addr get_next_ip_class_b(struct in_addr ip)
{
 unsigned char v = get_ip_number(ip, 3);
 unsigned char w = get_ip_number(ip, 2);
 
 if(v < 254)return(get_next_ip_class_c(ip));
 if(w < 254){
   set_ip_number(&ip, (unsigned char)(w+1), (int)2);
   set_ip_number(&ip, (unsigned char)1, (int)3);
   return(ip);
   }
 ip.s_addr = INADDR_ANY;
 return(ip);
}

static struct in_addr get_next_ip_class_a(struct in_addr ip)
{
 unsigned char v = get_ip_number(ip, 3);
 unsigned char w = get_ip_number(ip, 2);
 unsigned char x = get_ip_number(ip, 1);
 
 if(v < 254)return(get_next_ip_class_c(ip));
 if(w < 254)return(get_next_ip_class_b(ip));
 if(x < 254){
  set_ip_number(&ip, (unsigned char)(x+1), (int)1);
  set_ip_number(&ip, 1,2);
  set_ip_number(&ip, 1,3);
  return(ip);
  }
 ip.s_addr = INADDR_ANY;
 return(ip);
}
    
  
char * get_name_from_ip(struct in_addr ip)
{
 char * ret = NULL;
 struct hostent * he;
#ifdef USE_PTHREADS
 struct hostent * result;
 char * buf;
 int h_err;

 buf = malloc(4096);bzero(buf, 4096);
 result = malloc(sizeof(struct hostent));bzero(result, sizeof(struct hostent));

 gethostbyaddr_r((char *)&ip, sizeof(long), AF_INET, result, buf, 4096, &he, &h_err);
#else
 he = gethostbyaddr((char *)&ip, sizeof(long), AF_INET);
#endif
 if(he)
 {
 ret = malloc(strlen(he->h_name)+1);
 bzero(ret, strlen(he->h_name)+1);
 strncpy(ret, he->h_name, strlen(he->h_name));
 }
#ifdef USE_PTHREADS
 free(result);
 free(buf);
#endif
 return(ret);
}


void add_ip_hosts(struct hls_globals_t * hls_globals, 
		  struct host_in_loop * hosts, 
                  short host_id, short * num_hosts)
{
 struct in_addr *ips;
 struct in_addr *ips_ok;
 struct host_in_loop * hil_orig = hosts;
 int i = 0;
 short c = (*num_hosts) - 1;
 char * hostname;
 int num;
 while(hosts->id != host_id)
 {
  if(hosts && hosts->next)hosts = hosts->next;
   else return;
 }
                            
 hostname = hosts->hostname;
 /* go to the end of the queue */
 while(hosts->next)hosts = hosts->next;
  
 /* ping the whole subnet and retrieve the IPs that
    are alive. Yes, this means we have to be root */    
  
 num = 255;
 if(hls_globals->subnet_class == HL_SUBNET_CLASS_B)num*=num;
 else if(hls_globals->subnet_class == HL_SUBNET_CLASS_A)num = num*num*num;             
 ips = malloc(sizeof(struct in_addr)*num);
 ips_ok = malloc(sizeof(struct in_addr)*num);
 
 bzero(ips, sizeof(struct in_addr)*num);
 bzero(ips_ok, sizeof(struct in_addr)*num);
 ips[0] = get_first_ip(hls_globals, hostname);
 ips[0] = get_next_ip(hls_globals, ips[0]); /* we don't want *.0 */
 while(ips[i++].s_addr!=INADDR_ANY)ips[i]=get_next_ip(hls_globals, ips[i-1]);
       
 i--;
 if(hls_globals->ping)ping_all_ip(ips, ips_ok, i);
 else memcpy(ips_ok,ips, sizeof(struct in_addr)*num);
 free(ips);
 /* add the IP which are ok in the global table */
 i = 0;
 while(ips_ok[i].s_addr)
 { 
  char * name = get_name_from_ip(ips_ok[i]);
  if(!name){
    name = malloc(strlen(inet_ntoa(ips_ok[i]))+1);
    strncpy(name, inet_ntoa(ips_ok[i]), strlen(inet_ntoa(ips_ok[i])));
    }
  if(!is_host_present(name, hil_orig, c))
  {
   hosts->next = malloc(sizeof(struct host_in_loop));
   bzero(hosts->next, sizeof(struct host_in_loop));
   hosts = hosts->next;
    
   hosts->hostname = malloc(strlen(name)+1);
   bzero(hosts->hostname, strlen(name)+1);
   strncpy(hosts->hostname, name, strlen(name));
    
   hosts->hostip = malloc(strlen(inet_ntoa(ips_ok[i]))+1);
   bzero(hosts->hostip, strlen(inet_ntoa(ips_ok[i]))+1);
   strncpy(hosts->hostip, inet_ntoa(ips_ok[i]), strlen(inet_ntoa(ips_ok[i])));
   hosts->addr = ips_ok[i];
   hosts->id = ++c;
   hosts->trusted_by = host_id;
   free(name);
    }
   i++;
  }
 *num_hosts = c;
 free(ips_ok);
}


int init_ping()
{
 int soc;
 struct protoent * ip_proto;
 
 ip_proto = getprotobyname("icmp");
 if(!ip_proto)return(-1);
 
 soc = socket(AF_INET, SOCK_RAW, ip_proto->p_proto);
 if(soc < 0)return(-2);
 else return(soc);
}

void send_ping(int soc,struct in_addr addr, int index)
{
 struct icmp * packet;
 char * buf;
#ifndef NESSUSNT
 struct timezone tz;
 struct timeval tv;
#endif
 int count = 1;
 int len;
 struct sockaddr_in saddr;
 
 
 bzero(&saddr, sizeof(struct sockaddr_in));
 buf = (char *)malloc(32);
 packet = (struct icmp *)buf;
 bzero(buf, 32);
#ifndef NESSUSNT
 gettimeofday(&tv, &tz);
#endif
 packet->icmp_type = ICMP_ECHO;
 packet->icmp_code = 0;
 packet->icmp_cksum = 0;
 packet->icmp_seq = index;
 /*SET_ICMP_LIFETIME(*packet, (1024+index));*/
 packet->icmp_id = getpid() & 0xFFFF;
#ifndef NESSUSNT
 bcopy(&tv, &buf[8], sizeof(struct timeval));
 len = 8 + sizeof(struct timeval)+sizeof(int);
#else
 len = 8 + sizeof(int);
#endif

 saddr.sin_family = AF_INET;
 saddr.sin_addr = addr;
 saddr.sin_port = 0;
 bcopy(&count, &buf[8+sizeof(struct timeval)], sizeof(int));
 packet->icmp_cksum = in_cksum((u_short *)packet, len);
 sendto(soc, buf, len, 0, (struct sockaddr *)(&saddr), sizeof(struct sockaddr_in));
 sendto(soc, buf, len, 0, (struct sockaddr *)(&saddr), sizeof(struct sockaddr_in));
 sendto(soc, buf, len, 0, (struct sockaddr *)(&saddr), sizeof(struct sockaddr_in));
 free(buf);
}


int recv_ping(int soc, int * ok_table)
{
 char * buf;
 struct ip * ip;
 struct icmp * packet;
 struct timeval timeout;
 fd_set r,w;
 int n;
 int  slen;
 struct sockaddr_in saddr;
 
 timeout.tv_sec = 3;
 timeout.tv_usec = 0;
 FD_ZERO(&r);
 FD_ZERO(&w);
 FD_SET(soc, &r);
 n = select(soc+1,&r, &w, NULL, &timeout);
 if(n<=0)return(1);
 
 buf = malloc(4096);
 bzero(buf, 4096);
 ip = (struct ip *)buf;
 slen = sizeof(struct sockaddr);
 bzero(&saddr, sizeof(struct sockaddr_in));
 recvfrom(soc, buf,  4096, 0,(struct sockaddr *)&saddr, &slen);
 slen = ip->ip_hl << 2;            
 packet = (struct icmp *)(buf + slen);
 if((packet->icmp_type == ICMP_ECHOREPLY)&&
    (packet->icmp_id == (getpid() & 0xFFFF)))
 {
     ok_table[packet->icmp_seq] = 1;
     
      }
 free(buf);
 return(0);
}
 
 

int ping_all_ip(struct in_addr * ips, struct in_addr * ips_ok, int num)
{
 int *ip_ok = malloc(sizeof(int)*(num+1));
 int i,j=0,k,l=0;
 int soc;
 int c, o=num;
 
 c = (num < 255) ? num:255;
 
 bzero(ip_ok, sizeof(int)*(num+1));
 soc = init_ping();
 if(soc < 0) return(soc);
 for(k=0;k<3;k++)		/* make three attempts before we give up */
 {
  while(o)	/* ping by packets of 255 hosts */		
  {
   for(i=0;i<c;i++)send_ping(soc, ips[l+i],l+i);  
   while(!recv_ping(soc, ip_ok));
   for(i=0,j=0;i<c;i++)if(ip_ok[l+i])ips_ok[l+j++]=ips[l+i];
   o -= c;
   l += c;
  }
 }
 free(ip_ok);
 shutdown(soc, 2);
 close(soc);
 return(0);
}

 
