/*
 *  tcp.c - functions logging TCP connections
 *
 *  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 <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#if (__GNU_LIBRARY__ >= 6)
#include <netinet/tcp.h>
#else
#include <linux/tcp.h>
#endif
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>
#include <pthread.h>
#include <grp.h>

#include "defines.h"
#include "tcp.h"
#include "netutils.h"
#include "log.h"
#include "filter.h"
#include "configuration.h"
#include "ident.h"

/* Socket */
int tcp_socket;

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

/*
 * Structure of a TCP packet
 */

#define IPHDR (*((struct iphdr *) pkt))
#define TCPHDR (*((struct tcphdr *) tcppkt))
#define TCP_CAPTURE_LENGTH MAX_IPHDR_LENGTH + sizeof(struct tcphdr)

#ifdef _MULTITHREAD_
struct packet_descriptor {
  __u8 *pkt;
  __u8 *tcppkt;
};
#endif
/*
 * log_tcp_open & threaded_log_tcp_open
 *
 * Log TCP connections opened
 */

void *log_tcp_open(__u8 *pkt, __u8 *tcppkt) {
  char service[SERVICE_LENGTH];
  struct log_info info;

  /* Host filter */
  info = do_log(IPHDR.saddr,
                IPHDR.daddr,
                TCPHDR.dest,
		TCPHDR.source, IPPROTO_TCP);
  if (info.log) {
    char details[DETAILS_LENGTH];
    char remote_host[HOST_LENGTH];
    char * username = 0;
    *details ='\0';
    host_print(remote_host, IPHDR.saddr,
               info.resolve);
    service_lookup("tcp", service, TCPHDR.dest);
    if (info.logformat == LOGFORMAT_DETAILED) {
      get_details(details,
                  IPHDR.saddr,
                  TCPHDR.source,
                  IPHDR.daddr,
                  TCPHDR.dest);
    }
    if (info.ident && TCPHDR.dest != IDENT_PORT) {
      username = get_ident_info(TCPHDR.dest,
                                TCPHDR.source,
                                IPHDR.daddr,
                                IPHDR.saddr);
    }
    switch (info.logformat) {
    case LOGFORMAT_SHORT:
      if (info.ident && TCPHDR.dest != IDENT_PORT) {
        tcp_log.log(tcp_log.level_or_fd, "TCP %s%s - %s@%s",
                    service, IPOPTIONS(pkt), username, remote_host);
        free(username);
      } else {
        tcp_log.log(tcp_log.level_or_fd, "TCP %s%s - %s",
                    service, IPOPTIONS(pkt), remote_host);
      }
      break;
    case LOGFORMAT_NORMAL:
      if (info.ident && TCPHDR.dest != IDENT_PORT) {
        tcp_log.log(tcp_log.level_or_fd, "%s connection attempt%s from %s@%s",
                    service, IPOPTIONS(pkt), username, remote_host);
        free(username);
      } else {
        tcp_log.log(tcp_log.level_or_fd, "%s connection attempt%s from %s",
                    service, IPOPTIONS(pkt), remote_host);
      }
      break;
    case LOGFORMAT_DETAILED:
      if (info.ident && TCPHDR.dest != IDENT_PORT) {
        tcp_log.log(tcp_log.level_or_fd, "%s connection attempt%s from %s@%s%s",
                    service, IPOPTIONS(pkt), username, remote_host, details);
        free(username);
      } else {
        tcp_log.log(tcp_log.level_or_fd, "%s connection attempt%s from %s%s",
                    service, IPOPTIONS(pkt), remote_host, details);
      }
      break;
    }
  }
  return NULL;
}

#ifdef _MULTITHREAD_

void *t_log_tcp_open(__u8 *pkt_desc) {
  log_tcp_open(((struct packet_descriptor *) pkt_desc)->pkt,
               ((struct packet_descriptor *) pkt_desc)->tcppkt);
  return NULL;
}

void threaded_log_tcp_open(__u8 *pkt, __u8 *tcppkt) {
  pthread_attr_t attr_t;
  pthread_t t;
  __u8 *image;
  struct packet_descriptor *pkt_desc;

  pkt_desc = (struct packet_descriptor *) malloc(sizeof(struct packet_descriptor));
  image = (__u8 *) malloc(TCP_CAPTURE_LENGTH);
  memcpy(image, pkt, TCP_CAPTURE_LENGTH);
  pkt_desc->pkt = image;
  pkt_desc->tcppkt = image + (tcppkt - pkt);
  pthread_attr_setdetachstate(&attr_t, PTHREAD_CREATE_DETACHED);
  pthread_create(&t, &attr_t, t_log_tcp_open, pkt_desc);
  pthread_attr_destroy(&attr_t);
  free(image);
  free(pkt_desc);
}
#endif

/*
 * log_tcp_close & threaded_log_tcp_close
 *
 * Log TCP connections closed
 */

void *log_tcp_close(__u8 *pkt, __u8 *tcppkt) {
  char service[SERVICE_LENGTH];
  struct log_info info;

  /* Host filter */
  info = do_log(IPHDR.saddr,
                IPHDR.daddr,
                TCPHDR.dest,
		TCPHDR.source, IPPROTO_TCP);
  if (info.log && info.logclosing) {
    char details[DETAILS_LENGTH];
    char remote_host[HOST_LENGTH];
    *details ='\0';
    host_print(remote_host, IPHDR.saddr,
               info.resolve);
    service_lookup("tcp", service, TCPHDR.dest);
    if (info.logformat == LOGFORMAT_DETAILED) {
      get_details(details,
                  IPHDR.saddr,
                  TCPHDR.source,
                  IPHDR.daddr,
                  TCPHDR.dest);
    }
    switch (info.logformat) {
    case LOGFORMAT_SHORT:
      tcp_log.log(tcp_log.level_or_fd, "TCP %s closed%s - %s", service, IPOPTIONS(pkt), remote_host);
      break;
    case LOGFORMAT_NORMAL:
      tcp_log.log(tcp_log.level_or_fd, "%s connection closed%s from %s", service, IPOPTIONS(pkt), remote_host);
      break;
    case LOGFORMAT_DETAILED:
      tcp_log.log(tcp_log.level_or_fd, "%s connection closed%s from %s%s", service, IPOPTIONS(pkt), remote_host, details);
      break;
    }
  }
  return NULL;
}

#ifdef _MULTITHREAD_

void *t_log_tcp_close(__u8 *pkt_desc) {
  log_tcp_close(((struct packet_descriptor *) pkt_desc)->pkt,
                ((struct packet_descriptor *) pkt_desc)->tcppkt);
  return NULL;
}

void threaded_log_tcp_close(__u8 *pkt, __u8 *tcppkt) {
  pthread_attr_t attr_t;
  pthread_t t;
  __u8 *image;
  struct packet_descriptor *pkt_desc;

  pkt_desc = (struct packet_descriptor *) malloc(sizeof(struct packet_descriptor));
  image = (__u8 *) malloc(TCP_CAPTURE_LENGTH);
  memcpy(image, pkt, TCP_CAPTURE_LENGTH);
  pkt_desc->pkt = image;
  pkt_desc->tcppkt = image + (tcppkt - pkt);
  pthread_attr_init(&attr_t);
  pthread_attr_setdetachstate(&attr_t, PTHREAD_CREATE_DETACHED);
  pthread_create(&t, &attr_t, t_log_tcp_close, pkt_desc);
  pthread_attr_destroy(&attr_t);
  free(image);
  free(pkt_desc);
}
#endif

/*
 * log_tcp
 *
 * Main thread logging TCP connections
 *
 */

void *log_tcp(void *nobody) {
  sigset_t newmask;
  __u8 pkt[TCP_CAPTURE_LENGTH];
  __u8 *tcppkt = NULL;

  sigemptyset(&newmask);
  sigaddset(&newmask, SIGALRM);
  pthread_sigmask(SIG_UNBLOCK, &newmask, NULL);

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

  tcp_socket = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
  if (tcp_socket <= 0) {
	log.log(log.level_or_fd, "FATAL: Unable to open tcp 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(tcp_socket, (__u8 *) &pkt, TCP_CAPTURE_LENGTH) == -1) {
	  log.log(log.level_or_fd, "FATAL: Unable to read tcp raw socket");
      exit(1);
	}

    tcppkt = (__u8 *) &pkt + (((struct iphdr *) &pkt)->ihl << 2);

    if (TCPHDR.syn == 1 && TCPHDR.ack == 0) {
#ifdef _MULTITHREAD_
      threaded_log_tcp_open(pkt, tcppkt);
#else
      log_tcp_open(pkt, tcppkt);
#endif
      continue;
    }

    if (TCPHDR.fin == 1) {
#ifdef _MULTITHREAD_
      threaded_log_tcp_close(pkt, tcppkt);
#else
      log_tcp_close(pkt, tcppkt);
#endif
      continue;
    }
  }
  /*@NOTREACHED@*/ 
  return NULL;
}
