/*
 *
 * Copyright (C) 2004, 2005 Rainer Wichmann, Patrice Bourgin, 
 *                    Yoann Vandoorselaere
 *
 * 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, 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
 *
 * 03/12/2004 : R.W.: 
 *       fix more memory leaks in get_file_infos()
 *       workaround (re-init) for file descriptor closing problem with GPG
 *
 * R.W.: fix missing treatment of alternative XML-style messages
 *       fix get_value (if terminating '>' or '"' is not found, may 
 *           overwrite the NULL terminator of the string ...)
 *       fix memory leaks in get_file_infos(), retrieve_time()
 *
 * 13/03/2004 : This file is modified by Patrice Bourgin 
 *              <pbourgin@xpconseil.com> 
 *
 * R.W.: Some problems with the patch by Patrice Bourgin fixed 
 *       (e.g. memory leak)
 *
 * Modifications (13/03/2004) by Patrice bourgin (pbourgin@xpconseil.com) : 
 * Comment : thanks for memory leak fix :p
 * 1 : remove translation of HTML tag
 * 2 : send detailled information about files to prelude (with two 
 *     functions : get_value and get_file_infos)
 *     these two functions were written by Yoann, but for future 
 *     version of prelude, I adapt them to work
 *     with version 0.8.10 of libprelude
 * 3 : send a heartbeat just after initialization, to alert prelude that 
 *     samhain is started
 * 4 : these modifications was tested successfully, and all informations are 
 *     correctly transmitted to prelude and displayed with piwi
 *
 * Modifications (7/03/2004) by Patrice bourgin (pbourgin@xpconseil.com) : 
 * 1 : translation of HTML tag <> to tag () in alert to permit 
 *     displaying alerts on piwi
 * 2 : add the address in the source and in the target for displaying on piwi
 * 3 : add information about the classification, because there was only 
 *     one classification and it was not enough
 * 4 : add impact field to classify alert in prelude, becuse impact is 
 *     needed to treat information
 * 5 : correct some errors with transmission to prelude  with libprelude
 */
 
#include "config_xor.h"

#include <stdio.h>
#include <string.h>
#include <sys/types.h>

#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#if HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif
#include <unistd.h>
#include <syslog.h>

#if defined(HAVE_LIBPRELUDE) && !defined(HAVE_LIBPRELUDE_9)

/*
 * _() macros are samhain specific; they are used to replace string
 * constants at runtime. This is part of the samhain stealth mode
 * (fill string constants with encoded strings, decode at runtime).
 */
#define FIL__  _("sh_prelude.c")

#if defined(__GNUC__)
extern char *strptime (const char * s,
                       const char * fmt, struct tm * tp);
#endif

#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/utsname.h>
#ifdef HAVE_LIBGEN_H
#include <libgen.h> 
#endif

#include <libprelude/list.h>
#include <libprelude/idmef-tree.h>
#include <libprelude/idmef-tree-func.h>
#include <libprelude/prelude-io.h>
#include <libprelude/prelude-message.h>
#include <libprelude/prelude-message-buffered.h>
#include <libprelude/idmef-msg-send.h>
#include <libprelude/idmef-message-id.h>
#include <libprelude/prelude-message-id.h>
#include <libprelude/sensor.h>

#ifndef HAVE_BASENAME
#define basename(a) ((NULL == strrchr(a, '/')) ? (a) : (strrchr(a, '/')))
#endif

/* 
 * includes for samhain-specific functions (sl_strstr, sh_error_handle)
 */
#include "slib.h"
#include "sh_mem.h"
#include "sh_cat.h"
#include "sh_error_min.h"
#include "sh_prelude.h"
#define SH_NEED_GETHOSTBYXXX
#include "sh_static.h"
#include "sh_pthread.h"

static char               programname[64];
static idmef_heartbeat_t  heartbeat;
static prelude_msgbuf_t * hb_msgbuf  = NULL; 
static struct utsname   * uname_data = NULL;

static char               hostname[256];
			  
static char               model[64];
static char               class[64];
static char               version[8];
static char               manufacturer[64];
			  
static char               name[16];
static char               url[32];
			  
static char               meaning[64];
static char               description[128];

static char             * path_basename = NULL;
static char             * path_fullname = NULL;

/* safe string duplication function
 */
static char * xstrdup (const char * str)
{
  size_t len;
  char * ret;

  if (!str)
    return NULL;
  len = sl_strlen(str);
  ret = SH_ALLOC(len+1);
  sl_strlcpy (ret, str, len+1);
  return (ret);
}

/* Get the value for a key. The key is built from toktmp + toksuffix.
 * msg is modified temporarily, so it should not be declared 'const'.
 */
static char *get_value (char *msg, const char *toktmp, 
			const char *toksuffix)
{
  char * ret = NULL, *ptr, tok[128];

  snprintf(tok, sizeof(tok), "%s%s", toktmp, (toksuffix) ? toksuffix : "");

  ptr = strstr(msg, tok);
  if ( ! ptr )
    return NULL;

#ifdef SH_USE_XML
  while (*ptr && *ptr != '"') ptr++;
  if (*ptr) { 
    ret = ptr + 1; ptr = ret; 
  }
  while (*ptr && *ptr != '"') ptr++;
  if (*ptr)
    {
      *ptr = '\0';
      if (ret) ret = xstrdup(ret);
      *ptr = '"';
    }
  else
    {
      if (ret) ret = xstrdup(ret);
    }
#else
  while (*ptr && *ptr != '<') ptr++;
  if (*ptr) ret = ptr + 1;
  while (*ptr && *ptr != '>') ptr++;
  if (*ptr)
    {
      *ptr = '\0';
      if (ret) ret = xstrdup(ret);
      *ptr = '>';
    }
  else
    {
      if (ret) ret = xstrdup(ret);
    }
#endif

  return ret;
}

int sh_prelude_init ()
{
  int ret = -1;

  if (uname_data != NULL) {
    SH_FREE(uname_data);
    uname_data = NULL;
  }
  uname_data = SH_ALLOC(sizeof(struct utsname));    /* only once */
  if (!uname_data) {
    return -1; 
  }
  ret = uname(uname_data);
  if (ret < 0) {
    uname_data = NULL;
    return -1;
  }

  /* ------- LibPrelude Init ------- 
   */
  strncpy(programname, _("Samhain"), 64);
  programname[63] = '\0';

  if ( prelude_sensor_init(programname, NULL, 0, NULL) < 0)
    {
      sh_error_handle((-1), FIL__, __LINE__, -1, MSG_E_SUBGEN,
		      _("Failed to initialize Prelude"), 
		      _("sh_prelude_init"));
      return -1;
    }

  strncpy(model, _("Samhain"), 64);
  model[63] = '\0';
  strncpy(class, _("Samhain Host Intrusion Detection System"), 64);
  class[63] = '\0';
  strncpy(version, VERSION, 8); 
  version[7] = '\0';
  strncpy(manufacturer, _("Samhain by Rainer Wichmann"), 64);
  manufacturer[63] = '\0';

  /*
  if (sh.host.name[0] != '\0') {
    strncpy(hostname, sh.host.name, 256); hostname[255] = '\0';
  } else {
    gethostname (hostname, 256); hostname[255] = '\0';
  }
  */

  /* According to the manpage, if gethostname returns a truncated hostname,
   * it may or may not be NULL terminated. So we terminate explicitely.
   */
  gethostname (hostname, 256); hostname[255] = '\0';

  strncpy (name, _("Samhain HIDS"), 16);
  name[15] = '\0';
  strncpy (url, _("http://www.la-samhna.de/samhain/"), 32);
  url[31] = '\0';

  strncpy (meaning, _("Message generated by Samhain"), 64);
  meaning[63] = '\0';

  /* analyzer information */
  idmef_string_set (&heartbeat.analyzer.model,   model);
  idmef_string_set (&heartbeat.analyzer.class,   class);
  idmef_string_set (&heartbeat.analyzer.version, version);
  
  /* analyzer address */
  idmef_analyzer_node_new(&heartbeat.analyzer);
  idmef_string_set (&heartbeat.analyzer.node->name, hostname);
  
  /* analyzer type */
  idmef_string_set(&heartbeat.analyzer.ostype,    uname_data->sysname);
  idmef_string_set(&heartbeat.analyzer.osversion, uname_data->release);

  
  INIT_LIST_HEAD(&heartbeat.additional_data_list);
  
  if (hb_msgbuf != NULL)
    {
      prelude_msgbuf_close (hb_msgbuf);
      hb_msgbuf = NULL;
    }
  hb_msgbuf = prelude_msgbuf_new(0);
  if (!hb_msgbuf) {
    return -1;
  }

  /* prelude_heartbeat_register_cb(&SendHeartbeat, NULL); */
  return 1;
}

/* Retrieve the content of "msg=" for adding informations on 
 * impact tag with prelude 
 */
char *RetrieveImpact(const char *msg)
{
  char *tmp1;
  char *tmp2;
  char *tmp0;
  char *ret = NULL;
  
  tmp2 = xstrdup(msg);
  /* 
   * don't use strtok - strtok (str, delim) is 'one of the chars 
   * in delim', not the full 'delim' string
   */
  if (tmp2)
    tmp1 = sl_strstr (tmp2, _("msg="));
  else
    return NULL;

  if (tmp1)
    {
      tmp1 += 5;
#ifdef SH_USE_XML
      tmp0 = strchr(tmp1, '"');
#else
      tmp0 = strchr(tmp1, '>');
#endif
      if (tmp0)
	*tmp0 = '\0';
      ret = xstrdup(tmp1);
    }
  SH_FREE(tmp2); /* fix memory leak */

  return ret;
}

/* Transform the string time from the event into time 
 */
time_t retrieve_time(char *stime)
{
#ifdef HAVE_STRPTIME
  struct tm tmptime;
  time_t rettime = -1;
  char *tmp0, *tmp1, *tmp2;

  /* fix more memory leaks
   */
  if ( stime )
    {
      tmp0 = xstrdup(stime);
      tmp1 = tmp0;
      tmp2 = tmp1 + 1;
        	
      while (*tmp1 && *tmp1 != ']') tmp1++;
      if (*tmp1)
	{
	  *tmp1 = '\0';
	  tmp2 = xstrdup(tmp2);  
	  SH_FREE (tmp0);
	}
      else
	{
	  tmp2 = xstrdup(tmp2);
	  SH_FREE (tmp0);
	}
      
      memset (&tmptime, '\0', sizeof(struct tm));
      strptime(tmp2,"%Y-%m-%dT%H:%M:%S", &tmptime);
      rettime = mktime(&tmptime);

      SH_FREE(tmp2);

      if ( rettime != -1 )
	return rettime;
      else
	return 0;
    } 
#endif
  return 0;
}

/* msg is modified temporarily in get_value(), 
 * so it should not be declared 'const'.
 */
void get_file_infos(idmef_target_t *target, char *msg, 
		    idmef_file_category_t category)
{
  char *ptr;
  idmef_file_t *file;
  idmef_time_t *temps;
  idmef_inode_t *inode;
  const char *suffix = (category == current) ? "_new" : "_old";
  
  file = idmef_target_file_new(target);
  if ( ! file )
    return;
  
  file->category = category;
  /* 
   * Fix memory leak - the pointer to  get_value(msg, "path", NULL) is lost
   * libprelude does not strdup, only sets a pointer, so need a global pointer
   * to keep track of this :(
   */
  if (category == original)
    path_fullname = get_value(msg, "path", NULL);
  idmef_string_set (&file->path, path_fullname);

  if (category == original)
    path_basename = get_value(msg, "path", NULL);
  if (path_basename) {
    idmef_string_set (&file->name, basename(path_basename));
  }
  
  ptr = get_value(msg, "size", suffix);
  if ( ptr ) {
    file->data_size = strtoul(ptr, NULL, 10);
    SH_FREE(ptr);
  }
  
  ptr = get_value(msg, "mtime", suffix);
  if ( ptr ) {
    temps = idmef_file_modify_time_new(file);
    temps->sec = retrieve_time(ptr);
    SH_FREE(ptr);
  }
  
  ptr = get_value(msg, "ctime", suffix);
  if ( ptr ) {
    temps = idmef_file_create_time_new(file);
    temps->sec = retrieve_time(ptr);
    SH_FREE(ptr);
  }
  
  ptr = get_value(msg, "atime", suffix);
  if ( ptr ) {
    temps = idmef_file_access_time_new(file);
    temps->sec = retrieve_time(ptr);
    SH_FREE(ptr);
  }
  
  ptr = get_value(msg, "inode", suffix);
  if ( ptr ) {
    inode = idmef_file_inode_new(file);
    inode->number = strtoul(ptr, NULL, 10);
    SH_FREE(ptr);
  }
}

void sh_prelude_reset()
{
  (void) sh_prelude_alert (0, 0, NULL, 0, 0);
  return;
}

int sh_prelude_alert (int priority, int sh_class, char * message,
		      long msgflags, unsigned long msgid)
{
  static int                initialized = 0;
  struct timeval            tv;

  idmef_alert_t           * alert;
  idmef_message_t         * idmef;
  prelude_msgbuf_t        * msgbuf;
  idmef_classification_t  * classification;        
  idmef_target_t          * target;
  idmef_address_t 	  * taddr;
  idmef_source_t          * source;
  idmef_assessment_t      * assessment;
  idmef_additional_data_t * data;
  /* To store impact */
  char			  * impactmsg = NULL; 
  struct hostent 	  * myhost;
  char 			  * src_ip = NULL;

  static int                some_error = 0;

  (void) msgflags;
  (void) msgid;

  /* Workaround for the file closing bug
   */
  if (message == NULL && priority == 0 && sh_class == 0)
    {
      initialized = 0;
      return 0;
    }

  if (initialized == 0)
    {
      /* initialize
       */
      initialized = sh_prelude_init();

     /* send a heartbeat after initialization to say to prelude "I'm alive" */
     
      gettimeofday(&tv, NULL);
      heartbeat.create_time.sec  = tv.tv_sec;
      heartbeat.create_time.usec = tv.tv_usec;
  
      /*
       * we could use additional data to send stats.
       */
      prelude_msgbuf_set_header(hb_msgbuf, PRELUDE_MSG_IDMEF, 0);
      idmef_send_heartbeat(hb_msgbuf, &heartbeat);
      prelude_msgbuf_mark_end(hb_msgbuf);
    }
  if (initialized == -1)
    {
      /* init failed
       */
      if (some_error == 0)
	{
	  sh_error_handle((-1), FIL__, __LINE__, -1, MSG_E_SUBGEN,
			  _("Problem with prelude-ids support: init failed"), 
			  _("sh_prelude_alert"));
	}
      some_error = 1;
      return -1; 
    }

  if (sh_class == STAMP)
    {
      gettimeofday(&tv, NULL);
      heartbeat.create_time.sec  = tv.tv_sec;
      heartbeat.create_time.usec = tv.tv_usec;
  
      /*
       * we could use additional data to send stats.
       */
      prelude_msgbuf_set_header(hb_msgbuf, PRELUDE_MSG_IDMEF, 0);
      idmef_send_heartbeat(hb_msgbuf, &heartbeat);
      prelude_msgbuf_mark_end(hb_msgbuf);
      return 0;
    }

  /* This function serves to initialize a message structure.
   * The returned idmef_message_t structure is a static variable
   * declared in idmef_message_new().
   */
  idmef = idmef_message_new();
  if ( ! idmef )
    goto err;
        
  /* 'alert' is a static variable that gets initialized and
   * associated with the idmef_message_t idmef_alert_t member. 
   * -> no new memory allocated; not signal-safe or thread-safe.
   */
  idmef_alert_new(idmef);
  alert = idmef->message.alert;
      
  /* Set the 'detect time'. idmef_alert_new() will already set the
   * 'create time', whatever the difference is supposed to be.
   */
  gettimeofday(&tv, NULL);
  idmef_alert_detect_time_new(alert);
  alert->detect_time->sec  = tv.tv_sec;
  alert->detect_time->usec = tv.tv_usec;
       
  /* ------- Analyzer. -------
   * 
   * This apparently is supposed to provide some information
   * about the sensor to the server (what sensor process ? where ?).
   *
   * idmef_string_set (x, y) is a macro that will make x
   * a pointer to y. Therefore the caller must guarantee that y will
   * never be overwritten until the alert is sent.
   * With the samhain _() macros, this means we must copy to another
   * storage region.
   *
   * N.B.: with constant strings, you can use idmef_string_set_constant() 
   * instead.
   */
  idmef_string_set (&alert->analyzer.model,        model);
  idmef_string_set (&alert->analyzer.class,        class);
  idmef_string_set (&alert->analyzer.version,      version);
  idmef_string_set (&alert->analyzer.manufacturer, manufacturer);

  /* Here we add some information on the host OS.
   */
  idmef_string_set (&alert->analyzer.ostype,    uname_data->sysname);
  idmef_string_set (&alert->analyzer.osversion, uname_data->release);

  /* ------- Analyzer / Process ------- 
   *
   * Fill in minimal info about the process. Apparently one could also
   * supply things like path, argv, env (?).
   */
  idmef_analyzer_process_new (&alert->analyzer);
  alert->analyzer.process->pid = getpid();

  /* ------- Analyzer / Node ------- 
   *
   * Provide the name of this node, i.e. host.
   */
  idmef_analyzer_node_new (&alert->analyzer);
  idmef_string_set (&alert->analyzer.node->name, hostname);



  /* ------- Classification -------
   *
   * Apparently 'classification' provides details about the sensor
   * program.
   *
   * For reasons unbeknown to me (did not care to investigate),
   * this function does allocate memory, instead of using a static variable. 
   *
   */
  classification = idmef_alert_classification_new(alert);
  if ( ! classification )
    goto err;


  impactmsg = RetrieveImpact(message);
  if (impactmsg)
    idmef_string_set (&classification->name, impactmsg);
  idmef_string_set (&classification->url,  url);

  classification->origin = vendor_specific;
         
  /* Get information about ip address */
  
  SH_MUTEX_LOCK(mutex_resolv);
  myhost = sh_gethostbyname(hostname);
  src_ip = xstrdup(inet_ntoa(*((struct in_addr *)myhost->h_addr_list[0])));
  SH_MUTEX_UNLOCK(mutex_resolv);
              

  /* ------- Target -------
   *
   * Purpose ? To provide informations about destination of alert
   *
   * Allocates memory.
   *
   */
  target = idmef_alert_target_new(alert);
  if ( ! target )
    goto err;
  idmef_target_node_new(target);
  idmef_string_set(&target->node->name, hostname);

  if ( strstr(message, "path=") ) {
    get_file_infos(target, message, original);
    get_file_infos(target, message, current);
  }

  if (src_ip)
    {
      taddr = idmef_node_address_new(target->node);
      if (!taddr) 
	goto err;
      
      taddr->category = ipv4_addr;
      idmef_string_set(&taddr->address, src_ip);
    }

  /* ------- Source -------
   *
   * Purpose ? To provide informations about source of alert
   *
   * Allocates memory.
   *
   */
  source = idmef_alert_source_new(alert);
  if ( ! source )
    goto err;

  /* ------- Impact ------- 
   */
  idmef_alert_assessment_new(alert);
  assessment = alert->assessment;
  idmef_assessment_impact_new(assessment);

  if ((priority == SH_ERR_SEVERE) || (priority == SH_ERR_FATAL))
    {
      assessment->impact->severity   = impact_high;
    }
  else if ((priority == SH_ERR_ALL) || (priority == SH_ERR_INFO) ||
      (priority == SH_ERR_NOTICE))
    {
      assessment->impact->severity   = impact_low;
    }
  else
    {
      assessment->impact->severity   = impact_medium;
    }

  if (NULL != sl_strstr(message, _("POLICY")))
    {
      if (NULL != sl_strstr(message, _("POLICY KERNEL")))
	{
	  assessment->impact->severity   = impact_high;
	  assessment->impact->completion = succeeded;
	  assessment->impact->type       = other;
	  strncpy(description, 
		  _("Kernel modification detected by Samhain."),
		  128);
	  description[127] = '\0';
	}
      else
	{
	  assessment->impact->severity   = impact_high;
	  assessment->impact->completion = succeeded;
	  assessment->impact->type       = file;
	  strncpy(description, 
		  _("File system modification detected by Samhain."),
		  128);
	  description[127] = '\0';
	}
    }
  else
    {
      if ( ((NULL != sl_strstr(message, _("Login"))) ||
	    (NULL != sl_strstr(message, _("Multiple login"))) ||
	    (NULL != sl_strstr(message, _("Logout")))) &&
	   (NULL == sl_strstr(message, _("Checking"))))
	{
	  assessment->impact->completion = succeeded;
	  assessment->impact->type       = user;
	  strncpy(description, 
		  _("Login/logout detected by Samhain."),
		  128);
	  description[127] = '\0';
	}
      else
	{
	  /* assessment->impact->severity   = impact_low; */
	  assessment->impact->completion = succeeded;
	  assessment->impact->type       = other;
	  strncpy(description, 
		  _("Message by Samhain."),
		  128);
	  description[127] = '\0';
	}
    }
  idmef_string_set (&assessment->impact->description, description); 
  idmef_assessment_confidence_new(assessment);
  assessment->confidence->rating = high;

  /* ------- Additional Data ------- 
   * 
   * Here we supply the log message.
   *
   */
  data = idmef_alert_additional_data_new(alert);
  if ( ! data )
    goto err;

  data->type = string;
  idmef_string_set (&data->meaning, meaning);
  if (message)
    idmef_additional_data_set_data (data, string, message, 
				    strlen(message) + 1);
  
  /* ------- Send ------- 
   *
   * Finally, the preparated message is sent.
   */      
  msgbuf = prelude_msgbuf_new(0);
  if ( ! msgbuf )
    goto err;

  /* Always return 0 (in libprelude 0.8.10); i.e. no useful
   * exit status
   */
  idmef_msg_send(msgbuf, idmef, PRELUDE_MSG_PRIORITY_HIGH);

  /* Cleanup
   */
  idmef_message_free(idmef);
  prelude_msgbuf_close(msgbuf);
  if (path_basename)
    {
      SH_FREE(path_basename);
      path_basename = NULL;
    }
  if (path_fullname)
    {
      SH_FREE(path_fullname);
      path_fullname = NULL;
    }
  if (impactmsg)
    SH_FREE(impactmsg);
  if (src_ip)
    SH_FREE(src_ip);

  some_error = 0;

  return 0;
        
 err:
  /* Cleanup
   */
  idmef_message_free(idmef);
  if (0 == some_error)
    {
      sh_error_handle((-1), FIL__, __LINE__, -1, MSG_E_SUBGEN,
		      _("Problem with IDMEF for prelude-ids support: alert lost"), 
		      _("sh_prelude_alert"));
    }
  if (path_basename)
    {
      SH_FREE(path_basename);
      path_basename = NULL;
    }
  if (path_fullname)
    {
      SH_FREE(path_fullname);
      path_fullname = NULL;
    }
  if (impactmsg)
    SH_FREE(impactmsg);
  if (src_ip)
    SH_FREE(src_ip);
  some_error = 1;
  return -1;

}

/* HAVE_LIBPRELUDE */
#endif
