/*
 *  icmp.c - functions logging ICMP messages
 *
 *  Copyright (C) 1998-2000 Hugo Haas
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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 for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#if (__GNU_LIBRARY__ < 6)
#include "icmpfix.h"
#endif
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pwd.h>
#include <pthread.h>
#include <grp.h>

#include "defines.h"
#include "icmp.h"
#include "netutils.h"
#include "log.h"
#include "filter.h"
#include "configuration.h"

/* Socket */
int icmp_socket;

struct loginfo icmp_log;
extern struct loginfo log;
extern unsigned short resolve_protocols;

/*
 * Structure of an ICMP packet
 */

#define IPHDR (*((struct iphdr *) pkt))
#define ICMPHDR (*((struct icmphdr *) icmppkt))
#define ICMP_CAPTURE_LENGTH MAX_IPHDR_LENGTH + sizeof(struct icmphdr)

/*
 * icmp_type_lookup
 *
 * Gives the type of an ICMP message
 * Thanks to Arkadiusz Mi¶kiewicz.
 */

void icmp_type_lookup(char *string1, char *string2, __u8 type, __u8 code) {

  *string2 = '\0';

  switch(type) {
  /* Echo reply */
  case ICMP_ECHOREPLY:
    strcpy(string1,"echo reply");
    return;
  /* Destination unreachable */
  case ICMP_DEST_UNREACH:
    strcpy(string1,"destination unreachable");
    switch(code) {
    case ICMP_UNREACH_NET:
      strcpy(string2," - bad network");
      return;
    case ICMP_UNREACH_HOST:
      strcpy(string2," - bad host");
      return;
    case ICMP_UNREACH_PROTOCOL:
      strcpy(string2," - bad protocol");
      return;
    case ICMP_UNREACH_PORT:
      strcpy(string2," - bad port");
      return;
    case ICMP_UNREACH_NEEDFRAG:
      strcpy(string2," - IP_DF caused drop");
      return;
    case ICMP_UNREACH_SRCFAIL:
      strcpy(string2," - source route failed");
      return;
    case ICMP_UNREACH_NET_UNKNOWN:
      strcpy(string2," - unknown network");
      return;
    case ICMP_UNREACH_HOST_UNKNOWN:
      strcpy(string2," - unknown host");
      return;
    case ICMP_UNREACH_ISOLATED:
      strcpy(string2," - src host isolated");
      return;
    case ICMP_UNREACH_NET_PROHIB:
      strcpy(string2," - network denied");
      return;
    case ICMP_UNREACH_HOST_PROHIB:
      strcpy(string2," - host denied");
      return;
    case ICMP_UNREACH_TOSNET:
      strcpy(string2," - bad tos for network");
      return;
    case ICMP_UNREACH_TOSHOST:
      strcpy(string2," - bad tos for host");
      return;
    case ICMP_UNREACH_FILTER_PROHIB:
      strcpy(string2," - packet filtered");
      return;
    case ICMP_UNREACH_HOST_PRECEDENCE:
      strcpy(string2," - host precedence violation");
      return;
    case ICMP_UNREACH_PRECEDENCE_CUTOFF:
      strcpy(string2," - precedence cutoff");
      return;
    default:
      return;
    }
  
  /* Source quench */
  case ICMP_SOURCE_QUENCH:
    strcpy(string1,"source quench");
    return;

  /* Redirection */
  case ICMP_REDIRECT:
    strcpy(string1,"redirect");
    switch(code) {
      case ICMP_REDIRECT_NET:
        strcpy(string2," - for network");
	return;
      case ICMP_REDIRECT_HOST:
	strcpy(string2," - for host");
	return;
      case ICMP_REDIRECT_TOSNET:
	strcpy(string2," - for tos and net");
	return;
      case ICMP_REDIRECT_TOSHOST:
	strcpy(string2," - for tos and host");
	return;
      default:
        return;
    }

  /* Echo */
  case ICMP_ECHO:
    strcpy(string1,"echo request");
    return;

  /* Router advertisement */
  case ICMP_ROUTER_ADVERT:
    strcpy(string1,"router advertisement");
    return;

  /* Router solicitation */
  case ICMP_ROUTER_SOLICITATION:
    strcpy(string1,"router solicitation");
    return;

  /* Time exceeded */
  case ICMP_TIME_EXCEEDED:
    strcpy(string1,"time exceeded");
    return;

  /* Parameter problem */
  case ICMP_PARAMETERPROB:
    strcpy(string1,"parameter problem");
    return;

  /* Timestamp request */
  case ICMP_TIMESTAMP:
    strcpy(string1,"timestamp request");
    return;
  
  /* Timestamp reply */
  case ICMP_TIMESTAMPREPLY:
    strcpy(string1,"timestamp reply");
    return;
  
  /* Information request */
  case ICMP_INFO_REQUEST:
    strcpy(string1,"information request");
    return;

  /* Information reply */
  case ICMP_INFO_REPLY:
    strcpy(string1,"information reply");
    return;

  /* Address mask request */
  case ICMP_ADDRESS:
    strcpy(string1,"address mask request");
    return;

  /* Address mask reply */
  case ICMP_ADDRESSREPLY:
    strcpy(string1,"address mask reply");
    return;

  /* Default */
  default:
    strcpy(string1,"unknown");
  }
}

/*
 * log_icmppacket & threaded_log_icmppacket
 *
 * Log an ICMP message
 */

#define TYPE_LENGTH 50

void *log_icmppacket(__u8 *pkt) {
  char icmp_type[TYPE_LENGTH];
  char icmp_code[TYPE_LENGTH];
  struct log_info info;
  __u8 *icmppkt;

  icmppkt = pkt + (IPHDR.ihl << 2);

  /* Host filter */
  info = do_log(IPHDR.saddr,
                IPHDR.daddr,
                ICMPHDR.type, 0, IPPROTO_ICMP);
  if (info.log) {
    char details[DETAILS_LENGTH];
    char remote_host[HOST_LENGTH];
    *details = '\0';
    host_print(remote_host, IPHDR.saddr,
               info.resolve);
    icmp_type_lookup(icmp_type, icmp_code,
                     ICMPHDR.type,
                     ICMPHDR.code);
    if (info.logformat == LOGFORMAT_DETAILED) {
      get_details(details, IPHDR.saddr, 0,
                  IPHDR.daddr, 0);
    }
    switch (info.logformat) {
    case LOGFORMAT_SHORT:
      icmp_log.log(icmp_log.level_or_fd, "ICMP %s%s%s - %s",
                   icmp_type, icmp_code, IPOPTIONS(pkt), remote_host);
      break;
    case LOGFORMAT_NORMAL:
      icmp_log.log(icmp_log.level_or_fd, "ICMP message type %s%s%s from %s",
                   icmp_type, icmp_code, IPOPTIONS(pkt), remote_host);
      break;
    case LOGFORMAT_DETAILED:
      icmp_log.log(icmp_log.level_or_fd, "ICMP message type %s%s%s from %s%s",
                   icmp_type, icmp_code, IPOPTIONS(pkt), remote_host, details);
      break;
    }
  }

  return NULL;
}

#ifdef _MULTITHREAD_
void threaded_log_icmppacket(__u8 *pkt) {
  pthread_attr_t attr_t;
  pthread_t t;
  __u8 *image;

  image = (__u8 *) malloc(ICMP_CAPTURE_LENGTH);
  memcpy(image, pkt, ICMP_CAPTURE_LENGTH);
  pthread_attr_init(&attr_t);
  pthread_attr_setdetachstate(&attr_t, PTHREAD_CREATE_DETACHED);
  pthread_create(&t, &attr_t, log_icmppacket, image);
  pthread_attr_destroy(&attr_t);
  free(image);
}
#endif

/*
 * log_icmp
 *
 * Main thread logging ICMP messages
 *
 */

void *log_icmp(void *nobody) {
  __u8 pkt[ICMP_CAPTURE_LENGTH];

  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);

  icmp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  if (icmp_socket <= 0) {
	log.log(log.level_or_fd, "FATAL: Unable to open icmp raw socket");
    exit(1);
  }

  setgid(((struct passwd *)nobody)->pw_gid);
  initgroups(((struct passwd *)nobody)->pw_name,
	     ((struct passwd *)nobody)->pw_gid);
  setuid(((struct passwd *)nobody)->pw_uid);

  for(;;) {
    if (read(icmp_socket, (__u8 *) &pkt, ICMP_CAPTURE_LENGTH) == -1) {
	  log.log(log.level_or_fd, "FATAL: Unable to read icmp raw socket");
	  exit(1);
	}

#ifdef _MULTITHREAD_
    threaded_log_icmppacket((void *) &pkt);
#else
    log_icmppacket((void *) &pkt);
#endif
  }
  /*@NOTREACHED@*/ 
  return NULL;
}
