/* SAMHAIN file system integrity testing                                   */
/* Copyright (C) 2010 Rainer Wichmann                                      */
/*                                                                         */
/*  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 "config_xor.h"

#undef  FIL__
#define FIL__  _("sh_login_track.c")

#if defined(SH_USE_UTMP) && (defined(SH_WITH_CLIENT) || defined (SH_STANDALONE)) 

#include <string.h>

#include "samhain.h"
#include "sh_pthread.h"
#include "sh_utils.h"
#include "sh_unix.h"
#include "sh_string.h"
#include "sh_tools.h"
#include "sh_ipvx.h"
#include "sh_error_min.h"

#ifdef HAVE_UTMPX_H

#include <utmpx.h>
#define SH_UTMP_S utmpx
#undef  ut_name
#define ut_name ut_user
#ifdef HAVE_UTXTIME
#undef  ut_time
#define ut_time        ut_xtime
#else
#undef  ut_time
#define ut_time        ut_tv.tv_sec
#endif

#else

#include <utmp.h>
#define SH_UTMP_S utmp

#endif


#if HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <time.h>

#define SH_LTRACK_VERSION 1

#define SH_LTRACK_USIZE  32
#define SH_LTRACK_HSIZE 256
/* One hour    (15 deg)  */
#define SH_LTRACK_HTRES  24
/* Ten minutes (2.5 deg) */
#define SH_LTRACK_GTRES 144

/* Avoid compiling against lmath by including result tables for sin, cos
 */
const double sintab_htres[SH_LTRACK_HTRES] = {
 0.13052619222005157340,  0.38268343236508978178,  0.60876142900872065589,  0.79335334029123516508,  0.92387953251128673848,  0.99144486137381038215, 
 0.99144486137381038215,  0.92387953251128673848,  0.79335334029123516508,  0.60876142900872087793,  0.38268343236508989280,  0.13052619222005157340, 
-0.13052619222005132360, -0.38268343236508967076, -0.60876142900872065589, -0.79335334029123494304, -0.92387953251128651644, -0.99144486137381038215, 
-0.99144486137381049318, -0.92387953251128662746, -0.79335334029123516508, -0.60876142900872087793, -0.38268343236509039240, -0.13052619222005168442, 
};
const double costab_htres[SH_LTRACK_HTRES] = {
 0.99144486137381038215,  0.92387953251128673848,  0.79335334029123516508,  0.60876142900872065589,  0.38268343236508983729,  0.13052619222005171218, 
-0.13052619222005160116, -0.38268343236508972627, -0.60876142900872065589, -0.79335334029123505406, -0.92387953251128673848, -0.99144486137381038215, 
-0.99144486137381049318, -0.92387953251128684951, -0.79335334029123516508, -0.60876142900872087793, -0.38268343236509033689, -0.13052619222005162891, 
 0.13052619222005126809,  0.38268343236509000382,  0.60876142900872054486,  0.79335334029123494304,  0.92387953251128651644,  0.99144486137381038215, 
};
const double sintab_gtres[SH_LTRACK_GTRES] = {
 0.02181488503456112046,  0.06540312923014306168,  0.10886687485196457070,  0.15212338618991669281,  0.19509032201612824808,  0.23768589232617309825, 
 0.27982901403099208482,  0.32143946530316158672,  0.36243803828370163567,  0.40274668985873718352,  0.44228869021900124592,  0.48098876891938763256, 
 0.51877325816052144436,  0.55557023301960217765,  0.59130964836358235193,  0.62592347218405908205,  0.65934581510006884386,  0.69151305578226940352, 
 0.72236396205975550444,  0.75183980747897738439,  0.77988448309288171956,  0.80644460426748254545,  0.83146961230254523567,  0.85491187067294649449, 
 0.87672675570750768781,  0.89687274153268836674,  0.91531147911944710227,  0.93200786928279844012,  0.94693012949510557696,  0.96004985438592871372, 
 0.97134206981326143282,  0.98078528040323043058,  0.98836151046776066220,  0.99405633822231964647,  0.99785892323860347908,  0.99976202707990913243, 
 0.99976202707990913243,  0.99785892323860347908,  0.99405633822231964647,  0.98836151046776066220,  0.98078528040323043058,  0.97134206981326143282, 
 0.96004985438592871372,  0.94693012949510568799,  0.93200786928279855115,  0.91531147911944721329,  0.89687274153268836674,  0.87672675570750779883, 
 0.85491187067294671653,  0.83146961230254545772,  0.80644460426748254545,  0.77988448309288183058,  0.75183980747897738439,  0.72236396205975561546, 
 0.69151305578226951454,  0.65934581510006895488,  0.62592347218405919307,  0.59130964836358257397,  0.55557023301960217765,  0.51877325816052133334, 
 0.48098876891938763256,  0.44228869021900130143,  0.40274668985873729454,  0.36243803828370174669,  0.32143946530316175325,  0.27982901403099230686, 
 0.23768589232617337581,  0.19509032201612860891,  0.15212338618991663730,  0.10886687485196457070,  0.06540312923014311719,  0.02181488503456121761, 
-0.02181488503456097475, -0.06540312923014286739, -0.10886687485196432090, -0.15212338618991641526, -0.19509032201612835911, -0.23768589232617312601, 
-0.27982901403099202930, -0.32143946530316153121, -0.36243803828370152464, -0.40274668985873707250, -0.44228869021900107938, -0.48098876891938741052, 
-0.51877325816052122232, -0.55557023301960195560, -0.59130964836358235193, -0.62592347218405908205, -0.65934581510006884386, -0.69151305578226929249, 
-0.72236396205975550444, -0.75183980747897727337, -0.77988448309288194160, -0.80644460426748265647, -0.83146961230254523567, -0.85491187067294660551, 
-0.87672675570750768781, -0.89687274153268825572, -0.91531147911944710227, -0.93200786928279844012, -0.94693012949510557696, -0.96004985438592860270, 
-0.97134206981326132180, -0.98078528040323031956, -0.98836151046776066220, -0.99405633822231953545, -0.99785892323860347908, -0.99976202707990913243, 
-0.99976202707990913243, -0.99785892323860347908, -0.99405633822231964647, -0.98836151046776066220, -0.98078528040323043058, -0.97134206981326143282, 
-0.96004985438592871372, -0.94693012949510568799, -0.93200786928279855115, -0.91531147911944721329, -0.89687274153268847776, -0.87672675570750790985, 
-0.85491187067294682755, -0.83146961230254545772, -0.80644460426748287851, -0.77988448309288216365, -0.75183980747897782848, -0.72236396205975605955, 
-0.69151305578226918147, -0.65934581510006873284, -0.62592347218405897102, -0.59130964836358235193, -0.55557023301960217765, -0.51877325816052144436, 
-0.48098876891938774358, -0.44228869021900141245, -0.40274668985873740557, -0.36243803828370185771, -0.32143946530316186427, -0.27982901403099241788, 
-0.23768589232617348683, -0.19509032201612871993, -0.15212338618991719241, -0.10886687485196513969, -0.06540312923014367230, -0.02181488503456178660
};
const double costab_gtres[SH_LTRACK_GTRES] = {
 0.99976202707990913243,  0.99785892323860347908,  0.99405633822231964647,  0.98836151046776066220,  0.98078528040323043058,  0.97134206981326143282, 
 0.96004985438592871372,  0.94693012949510568799,  0.93200786928279855115,  0.91531147911944721329,  0.89687274153268836674,  0.87672675570750768781, 
 0.85491187067294660551,  0.83146961230254523567,  0.80644460426748265647,  0.77988448309288183058,  0.75183980747897738439,  0.72236396205975561546, 
 0.69151305578226940352,  0.65934581510006884386,  0.62592347218405908205,  0.59130964836358235193,  0.55557023301960228867,  0.51877325816052155538, 
 0.48098876891938774358,  0.44228869021900124592,  0.40274668985873723903,  0.36243803828370169118,  0.32143946530316169774,  0.27982901403099202930, 
 0.23768589232617309825,  0.19509032201612833135,  0.15212338618991680383,  0.10886687485196473724,  0.06540312923014304780,  0.02181488503456115863, 
-0.02181488503456103373, -0.06540312923014292290, -0.10886687485196461234, -0.15212338618991669281, -0.19509032201612819257, -0.23768589232617298723, 
-0.27982901403099191828, -0.32143946530316158672, -0.36243803828370158016, -0.40274668985873712801, -0.44228869021900113490, -0.48098876891938746603, 
-0.51877325816052122232, -0.55557023301960195560, -0.59130964836358246295, -0.62592347218405908205, -0.65934581510006884386, -0.69151305578226929249, 
-0.72236396205975550444, -0.75183980747897727337, -0.77988448309288160853, -0.80644460426748243442, -0.83146961230254534669, -0.85491187067294660551, 
-0.87672675570750768781, -0.89687274153268825572, -0.91531147911944710227, -0.93200786928279844012, -0.94693012949510557696, -0.96004985438592860270, 
-0.97134206981326132180, -0.98078528040323043058, -0.98836151046776066220, -0.99405633822231964647, -0.99785892323860347908, -0.99976202707990913243, 
-0.99976202707990913243, -0.99785892323860347908, -0.99405633822231964647, -0.98836151046776077322, -0.98078528040323043058, -0.97134206981326143282, 
-0.96004985438592871372, -0.94693012949510568799, -0.93200786928279855115, -0.91531147911944721329, -0.89687274153268836674, -0.87672675570750779883, 
-0.85491187067294671653, -0.83146961230254545772, -0.80644460426748254545, -0.77988448309288183058, -0.75183980747897749541, -0.72236396205975561546, 
-0.69151305578226951454, -0.65934581510006906591, -0.62592347218405897102, -0.59130964836358224090, -0.55557023301960217765, -0.51877325816052144436, 
-0.48098876891938768807, -0.44228869021900135694, -0.40274668985873735005, -0.36243803828370180220, -0.32143946530316180876, -0.27982901403099236237, 
-0.23768589232617343132, -0.19509032201612866442, -0.15212338618991713690, -0.10886687485196507030, -0.06540312923014361679, -0.02181488503456172415, 
 0.02181488503456135639,  0.06540312923014325597,  0.10886687485196470948,  0.15212338618991677608,  0.19509032201612830359,  0.23768589232617307050, 
 0.27982901403099197379,  0.32143946530316147570,  0.36243803828370146913,  0.40274668985873701699,  0.44228869021900102387,  0.48098876891938735501, 
 0.51877325816052111129,  0.55557023301960184458,  0.59130964836358201886,  0.62592347218405863796,  0.65934581510006839977,  0.69151305578226895943, 
 0.72236396205975572649,  0.75183980747897749541,  0.77988448309288183058,  0.80644460426748254545,  0.83146961230254523567,  0.85491187067294660551, 
 0.87672675570750768781,  0.89687274153268825572,  0.91531147911944710227,  0.93200786928279844012,  0.94693012949510557696,  0.96004985438592860270, 
 0.97134206981326132180,  0.98078528040323031956,  0.98836151046776066220,  0.99405633822231953545,  0.99785892323860347908,  0.99976202707990913243
};

struct sh_track_entry_data {
  UINT64      last_login;
  UINT32      array[SH_LTRACK_HTRES]; /* 1 h resolution */
  char        hostname[SH_LTRACK_HSIZE];
};

struct sh_track_entry {
  struct sh_track_entry_data data;
  struct sh_track_entry * next;
};

struct sh_track_head {
  UINT32 version;
  UINT32 n_entries;
  UINT64 last_login;
  char   hostname[SH_LTRACK_HSIZE];
  UINT32 array[SH_LTRACK_GTRES]; /* 10 min resolution */
};

struct sh_track {
  struct sh_track_head head;
  struct sh_track_entry * list;
};


/* Returns zero/nonzero
 */
static int get_bool(char *bitarray, unsigned int index)
{
  int my_bool;

  bitarray += index / 8; /* skip to char */
  my_bool = (*bitarray & (1 << (index % 8)));

  return my_bool;
}

static void set_bool(char *bitarray, unsigned int index, int my_bool)
{
  bitarray += index / 8; /* skip to char */
  if (my_bool)
    *bitarray |= 1 << (index % 8);
  else    
    *bitarray &= ~(1 << (index % 8));
  return;
}


static char * build_path (const char * user)
{
  char * ui;

  if (0 != sh_util_base64_enc_alloc (&ui, user, sl_strlen(user)))
    {
      char * path = sh_util_strconcat(DEFAULT_DATAROOT, "/", ui, NULL);

      SH_FREE(ui);
      return path;
    }
  return NULL;
}

static void destroy_loaded(struct sh_track * urecord)
{
  if (urecord)
    {
      struct sh_track_entry * entry = urecord->list;
      struct sh_track_entry * entry_old;

      while(entry)
	{
	  entry_old = entry;
	  entry = entry->next;
	  SH_FREE(entry_old);
	}
      SH_FREE(urecord);
    }
  return;
}

static struct sh_track * load_data_int (char * path)
{
  struct sh_track_head * uhead;
  struct sh_track * urecord;

  urecord = SH_ALLOC(sizeof(struct sh_track));
  memset(urecord, 0, sizeof(struct sh_track));

  uhead = &(urecord->head);
  uhead->version = SH_LTRACK_VERSION;

  if (path)
    {
      FILE * fp = fopen(path, "rb");
      
      if (fp)
	{
	  size_t n;
	  
	  n = fread(uhead, sizeof(struct sh_track_head), 1, fp);
	  
	  if (n == 1)
	    {
	      struct sh_track_entry_data entry_data;
	      struct sh_track_entry * entry;
	      
	      while (1 == fread(&entry_data, sizeof(entry_data), 1, fp))
		{
		  entry = SH_ALLOC(sizeof(struct sh_track_entry));
		  memcpy(&(entry->data), &entry_data, sizeof(entry_data));
		  entry->next   = urecord->list;
		  urecord->list = entry;
		}
	    }
	  fclose(fp);
	}
    }

  return urecord;
}

static struct sh_track * load_data (const char * user)
{
  char * path = build_path (user);
  struct sh_track * res = load_data_int (path);

  if (path)
    SH_FREE(path);
  return res;
}

static void save_data_int (struct sh_track * urecord, char * path)
{
  mode_t mask;
  FILE * fp;
  
  mask = umask(S_IWGRP | S_IWOTH);
  fp = fopen(path, "wb");
  (void) umask(mask);
  
  if (fp)
    {
      size_t n;
      
      n = fwrite(&(urecord->head), sizeof(struct sh_track_head), 1, fp);
      
      if (n == 1)
	{
	  struct sh_track_entry * entry = urecord->list;
	  
	  while (entry && (n > 0))
	    {
	      n = fwrite(&(entry->data), sizeof(struct sh_track_entry_data), 
			 1, fp);
	      entry = entry->next;
	    }
	}
      fclose(fp);
    }
  return;
}

static void save_data (struct sh_track * urecord, const char * user)
{
  char * path = build_path (user);

  if (path)
    {
      save_data_int (urecord, path);
      SH_FREE(path);
    }
  return;
}

/**************
 *
 * Configurable
 *
 **************/

enum significance { SIG00, SIG01, SIG05 };
enum checklevel   { CHECK_NONE, CHECK_HOST, CHECK_DOMAIN };
enum days         { WORKDAYS = 0, SATURDAY, SUNDAY };
#define LTRACK_NDAYS 3

static int sig_level    = SIG00;
static int check_level  = CHECK_NONE;
static int check_date   = S_FALSE;

/* We use a bit array of SH_LTRACK_GTRES bits for allowed times 
 * (10 min resolution)
 */
#define BITARRSIZ(a) ((a + 7) / 8)

static int global_init  = S_FALSE;
static char global_dates[LTRACK_NDAYS][BITARRSIZ(SH_LTRACK_GTRES)];

struct sh_track_dates {
  char user[SH_LTRACK_USIZE];
  char dates[LTRACK_NDAYS][BITARRSIZ(SH_LTRACK_GTRES)];
  struct sh_track_dates * next;
};
struct sh_track_dates * user_dates = NULL;

static int set_dates (char bitarray[][BITARRSIZ(SH_LTRACK_GTRES)], 
		      unsigned int size, const char * defstr);

void sh_login_reset (void)
{
  int i, j;
  struct sh_track_dates *u_old, *u;

  u          = user_dates;
  user_dates = NULL;

  while(u)
    {
      u_old = u;
      u     = u->next;
      SH_FREE(u_old);
    }

  for (j = 0; j < LTRACK_NDAYS; ++j)
    {
      for (i = 0; i < SH_LTRACK_GTRES; ++i)
	{ 
	  set_bool(global_dates[j], i, 0);
	}
    }
  global_init = S_FALSE;

  sig_level    = SIG00;
  check_level  = CHECK_NONE;
  check_date   = S_FALSE;

  return;
}

int sh_login_set_def_allow(const char * c)
{
  int res = set_dates(global_dates, SH_LTRACK_GTRES, c);

  if (res == 0)
    {
      check_date   = S_TRUE;
      global_init  = S_TRUE;
    }
  return res;
}

static struct sh_track_dates * find_user(const char * user)
{
  struct sh_track_dates * u = user_dates;

  while(u)
    {
      if (0 == sl_strcmp(user, u->user))
	{
	  return u;
	}
      u = u->next;
    }
  return NULL;
}

int sh_login_set_user_allow(const char * c)
{
  unsigned int i = 0;
  const char *p = c;
  char user[SH_LTRACK_USIZE];
  
  struct sh_track_dates * u;

  while (p && *p && *p != ':' && *p != ' ' && *p != '\t')
    {
      user[i] = *p; ++p; ++i;

      if (i == SH_LTRACK_USIZE)
	return -1;
    }

  while (p && *p && (*p == ' ' || *p == '\t')) ++p;

  if (p && *p && (i < SH_LTRACK_USIZE) && (*p == ':'))
    {
      user[i] = '\0';

      ++p; while (*p && (*p == ' ' || *p == '\t')) ++p;

      if (*p)
	{
	  int res;
	  int flag = 0;

	  u = find_user(user);

	  if (!u)
	    {
	      u = SH_ALLOC(sizeof(struct sh_track_dates));
	      memset(u, 0, sizeof(struct sh_track_dates));
	      sl_strlcpy(u->user, user, SH_LTRACK_USIZE);
	      flag = 1;
	    }

	  res = set_dates(u->dates, SH_LTRACK_GTRES, p);
	  if (res != 0)
	    {
	      if (flag == 1)
		SH_FREE(u);
	      return -1;
	    }

	  if (flag == 1)
	    {
	      u->next    = user_dates;
	      user_dates = u;
	    }

	  check_date = S_TRUE;
	  return 0;
	}
    }
  return -1;
}

int sh_login_set_siglevel(const char * c)
{
  int ret = sh_util_flagval(c, &sig_level);

  if (ret == 0)
    {
      sig_level = (sig_level == S_FALSE) ? SIG00 : SIG01;
      return 0;
    }
  else
    {
      if (0 == strcmp(c, _("paranoid")))
	{
	  sig_level = SIG05;
	  return 0;
	}
    }
  sig_level = SIG00;
  return -1;
}

int sh_login_set_checklevel(const char * c)
{
  int ret = sh_util_flagval(c, &check_level);

  if (ret == 0)
    {
      check_level = (check_level == S_FALSE) ? CHECK_NONE : CHECK_HOST;
      return 0;
    }
  else
    {
      if (0 == strcmp(c, _("domain")))
	{
	  check_level = CHECK_DOMAIN;
	  return 0;
	}
    }
  check_level = CHECK_NONE;
  return -1;
}

static int eval_range(char * bitarray, unsigned int size, char * def)
{
  unsigned int h1, m1, h2, m2;

  int res = sscanf(def, "%d:%d - %d:%d", &h1, &m1, &h2, &m2);

  if (res == 4)
    {
      unsigned int t1 = 3600*h1 + 60*m1;
      unsigned int t2 = 3600*h2 + 60*m2;
      int hres        = (60*60*24)/size;
      unsigned int i;

      if (t1 > t2 || t1 > 86340 || t2 > 86340)
	return -1;

      t1  = t1 / hres;
      t2  = t2 / hres;
      t1  = (t1 < size) ? t1 : (size-1);
      t2  = (t2 < size) ? t2 : (size-1);

      for (i = t1; i <= t2; ++i)
	{
	  set_bool(bitarray, i, 1);
	}
      return 0;
    }
  return -1;
}

static int set_ranges(char * bitarray, unsigned int size, 
		      char ** splits, unsigned int nfields)
{
  unsigned int i;
  int retval = 0;

  for (i = 0; i < nfields; ++i)
    {
      char * range = &(splits[i][0]);

      if (0 != eval_range(bitarray, size, range))
	retval = -1;
    }
  return retval;
}

/* 'always', 'never', workdays(list of ranges), (sun|satur)day(list of ranges)
 */
static int set_dates (char bitarray[][BITARRSIZ(SH_LTRACK_GTRES)], 
		      unsigned int size, 
		      const char * defstr)
{
  unsigned int i, j;
  int retval = -1;

  if (0 == strcmp(_("always"), defstr))
    {
      for (j = 0; j < LTRACK_NDAYS; ++j)
	for (i = 0; i < size; ++i)
	  set_bool(bitarray[j], i, 1);
      retval = 0;
    }
  else if (0 == strcmp(_("never"), defstr))
    {
      for (j = 0; j < LTRACK_NDAYS; ++j)
	for (i = 0; i < size; ++i)
	  set_bool(bitarray[j], i, 0);
      retval = 0;
    }
  else
    {
      unsigned int nfields = 24; /* list of ranges */
      size_t       lengths[24];
      char *       new    = NULL;
      char **      splits = NULL;

      if      (0 == strncmp(_("workdays"), defstr, 7))
	{
	  new    = sh_util_strdup(defstr);
	  splits = split_array_braced(new, _("workdays"), 
				      &nfields, lengths);
	  j = WORKDAYS;
	}
      else if (0 == strncmp(_("saturday"), defstr, 8))
	{
	  new    = sh_util_strdup(defstr);
	  splits = split_array_braced(new, _("saturday"), 
				      &nfields, lengths);
	  j = SATURDAY;
	}
      else if (0 == strncmp(_("sunday"), defstr, 6))
	{
	  new    = sh_util_strdup(defstr);
	  splits = split_array_braced(new, _("sunday"), 
				      &nfields, lengths);
	  j = SUNDAY;
	}
      else
	{
	  return -1;
	}

      if (new && splits && nfields > 0)
	{
	  retval = set_ranges(bitarray[j], size, splits, nfields);
	}

      if (new) SH_FREE(new);
    }
  return retval;
}



/**************
 *
 * Report
 *
 **************/

void report_generic(char * file, int line, 
		    const char * user, time_t time, const char * host, int what)
{
  char   ttt[TIM_MAX];

  SH_MUTEX_LOCK(mutex_thread_nolog);
  (void) sh_unix_time (time, ttt, TIM_MAX);
  sh_error_handle ((-1), file, line, 0, what,
		   user, host, ttt);
  SH_MUTEX_UNLOCK(mutex_thread_nolog);
  return;
}

void report_bad_date(char * file, int line, 
		     const char *user, time_t time, const char * host)
{
  report_generic(file, line, user, time, host, MSG_UT_BAD);
}

void report_first(char * file, int line, 
		  const char *user, time_t time, const char * host)
{
  report_generic(file, line, user, time, host, MSG_UT_FIRST);
}

void report_outlier(char * file, int line, 
		    const char *user, time_t time, const char * host)
{
  report_generic(file, line, user, time, host, MSG_UT_OUTLIER);
}

/**************
 *
 * Dates
 *
 **************/

static int check_login_date(const char * user, unsigned int index, int wday)
{
  unsigned int i, j;
  struct sh_track_dates * allowed = NULL;
  int day;

  /* Use an intermediate array 'char* b[m]' to cast 'char a[m][n]' to 'char** c' */
  char * aux[LTRACK_NDAYS];
  char **good = (char **) aux;

  for (i = 0; i < LTRACK_NDAYS; ++i)
    {
      aux[i] = (char *) &global_dates[i][0];
      /* + i * BITARRSIZ(SH_LTRACK_GTRES); */
    }

  if (wday > 0 && wday < 6)
    day = WORKDAYS;
  else if (wday == 6)
    day = SATURDAY;
  else
    day = SUNDAY;

  if (check_date != S_FALSE)
    {
      if (S_FALSE == global_init)
	{
	  for (j = 0; j < LTRACK_NDAYS; ++j)
	    {
	      for (i = 0; i < SH_LTRACK_GTRES; ++i) 
		set_bool(global_dates[j], i, 1);
	    }
	  global_init = S_TRUE;
	}

      if (user) {
	allowed = find_user(user);
      }

      if (allowed)
	{
	  for (i = 0; i < LTRACK_NDAYS; ++i)
	    {
	      aux[i] = (char *)&(allowed->dates)[i][0]; 
	      /* + i*BITARRSIZ(SH_LTRACK_GTRES); */
	    }
	}
      
      if (0 == get_bool(good[day], index))
	{
	  return -1;
	}
    }
  return 0;
} 

/**************
 *
 * Statistics
 *
 **************/

/* Compute sqrt(s) using the babylonian algorithm
 * (to avoid linking with -lm).
 */ 
static double sh_sqrt(double s)
{
  double eps = 1.0e-6;
  double x0  = 1.0;
  double xs  = s;

  double diff = xs - x0;
  diff = (diff > 0.0) ? diff : -diff;

  while (diff > eps)
    {
      xs = x0;
      x0 = 0.5 * (x0 + (s/x0));
      diff = xs - x0;
      diff = (diff > 0.0) ? diff : -diff;
    }
  return x0;
}

static double M_crit(int n, int flag)
{
#define SH_MCSIZE 10
  const double M_05[SH_MCSIZE] = { 0.975, 0.918, 0.855, 0.794, 0.739, 0.690, 0.647, 0.577, 0.497, 0.406 };
  const double M_01[SH_MCSIZE] = { 0.995, 0.970, 0.934, 0.891, 0.845, 0.799, 0.760, 0.688, 0.603, 0.498 };
  const int    M_nn[SH_MCSIZE] = {     4,     5,     6,     7,     8,     9,    10,    12,    15,    20 };

  if (n > M_nn[SH_MCSIZE-1])
    {
      return ((flag == SIG05) ? M_05[SH_MCSIZE-1] : M_01[SH_MCSIZE-1]);
    }
  else
    {
      unsigned int i;

      for (i = 1; i < SH_MCSIZE; ++i)
	{
	  if (n < M_nn[i])
	    {
	      return ((flag == SIG05) ? M_05[i-1] : M_01[i-1]);
	    }
	}
    }

  return ((flag == SIG05) ? M_05[SH_MCSIZE-1] : M_01[SH_MCSIZE-1]);
}

static int check_statistics (unsigned int index, UINT32 * array, unsigned int size,
			     const double * costab, const double * sintab)
{
  double C = 0.0;
  double S = 0.0;
  double R, Rk, M;

  unsigned int i, n = 0;

  if (sig_level != SIG00)
    {
      for (i = 0; i < size; ++i)
	{
	  n += array[i];
	  C += (array[i] * costab[i]);
	  S += (array[i] * sintab[i]);
	}
      
      if (n > 2) /* current is at least 4th datapoint */
	{
	  R = sh_sqrt(S*S + C*C);
	  
	  C += array[index] * costab[index];
	  S += array[index] * sintab[index];
	  Rk = sh_sqrt(S*S + C*C);
	  ++n;
	  
	  M  = (Rk - R + 1.0)/((double)n - R);
	  
	  if (M > M_crit(n, sig_level))
	    {
	      return -1;
	    }
	}
    }
  return 0;
}

static char * stripped_hostname (const char * host)
{
  char *p, *q;
  
  if (sh_ipvx_is_numeric(host))
    {
      p = sh_util_strdup(host);
      q = strrchr(p, '.');
      if (q) 
	{
	  *q = '\0';
	  q = strrchr(p, '.');
	  if (q)
	    {
	      *q = '\0';
	    }
	}
    }
  else
    {
      char * tmp = sh_util_strdup(host);
      q = strchr(tmp, '.'); 
      if (q && *q)
	{
	  ++q;
	  p = sh_util_strdup(q);
	  SH_FREE(tmp);
	}
      else
	{
	  p = tmp;
	}
    }
  return p;
}

static unsigned int time_to_index(struct tm * tp, int nbin)
{
  int hres  = (60*60*24)/nbin;
  int index = tp->tm_hour * 3600 + tp->tm_min * 60 + tp->tm_sec;
  index  = index / hres;
  index  = (index < nbin) ? index : (nbin-1);

  return index;
}

static struct sh_track_entry * check_host(struct sh_track_entry * list, 
					  const char * user, time_t time, const char * host,
					  struct tm * tp)
{
  unsigned int    index = time_to_index(tp, SH_LTRACK_HTRES);
  struct sh_track_entry * entry = list;

  char * p = NULL;
  const char * q;

  if (check_level == CHECK_DOMAIN)
    {
      p = stripped_hostname(host);
      q = p;
    }
  else
    {
      q = host;
    }

  while (entry)
    {
      if (0 == strncmp(q, (entry->data).hostname, SH_LTRACK_HSIZE))
	break;
      entry = entry->next;
    }

  if (entry)
    {
      int isAlert;

      (entry->data).last_login    = time;

      /* Check host statistics here 
       */
      isAlert = check_statistics (index, (entry->data).array, SH_LTRACK_HTRES, 
				  costab_htres, sintab_htres); 

      if (isAlert != 0) 
	{
	  report_outlier(FIL__, __LINE__, user, time, host);
	}

      /* Update array afterwards
       */
      (entry->data).array[index] += 1;
    }
  else
    {
      entry = SH_ALLOC(sizeof(struct sh_track_entry));
      memset(entry, 0, sizeof(struct sh_track_entry));
      (entry->data).last_login    = time;
      (entry->data).array[index]  = 1;
      sl_strlcpy((entry->data).hostname, q, SH_LTRACK_HSIZE);

      /* Report first login from this host 
       */
      if (check_level != CHECK_NONE) 
	{ 
	  report_first (FIL__, __LINE__, user, time, host);
	}
      
      if (p)
	SH_FREE(p);
      return entry;
    }

  if (p)
    SH_FREE(p);
  return NULL;
}

/********************************************************
 *
 * Public Function
 *
 ********************************************************/

void sh_ltrack_check(struct SH_UTMP_S * ut)
{
  int gres;
  const char * user;
  time_t time;
#if defined(HAVE_UTHOST)
  const char * host;
#else
  const char * host;
#endif
  struct sh_track * urecord;
  time_t last_login;

  /* Just return if we are not supposed to do anything
   */
  if (sig_level == SIG00 && check_level == CHECK_NONE && check_date == S_FALSE)
    return;


#if defined(HAVE_UTHOST)
  host = ut->ut_host;
#else
  host = sh_util_strdup(_("unknown"));
#endif
  time = ut->ut_time;
  user = ut->ut_name;

  gres  = (60*60*24)/SH_LTRACK_GTRES;

  urecord    = load_data(user);
  last_login = (urecord->head).last_login;

  if (   last_login < time &&
	 ( (time - last_login) >= gres || 
	   0 != strcmp(host, (urecord->head).hostname)
	   )
	 )
    {
      struct tm ts;
      unsigned int  index;
      int isAlert;
      struct sh_track_entry * entry;

      (urecord->head).last_login = time;
      sl_strlcpy((urecord->head).hostname, host, SH_LTRACK_HSIZE);
      (urecord->head).n_entries += 1;

      memcpy(&ts, localtime(&time), sizeof(struct tm));
      index = time_to_index(&ts, SH_LTRACK_GTRES);
      
      /* Check global statistics here 
       */
      isAlert = check_statistics (index, (urecord->head).array, 
				  SH_LTRACK_GTRES, 
				  costab_gtres, sintab_gtres);
      
      if (isAlert != 0) 
	{
	  report_outlier(FIL__, __LINE__, user, time, host);
	}
      

      if (check_date != S_FALSE)
	{
	  int isBad = check_login_date(user, index, ts.tm_wday);

	  if (isBad != 0)
	    {
	      report_bad_date(FIL__, __LINE__, user, time, host);
	    }
	}

      /* Update array afterwards 
       */
      (urecord->head).array[index] += 1;

      entry = check_host(urecord->list, user, time, host, &ts);
      if (entry)
	{
	  entry->next   = urecord->list;
	  urecord->list = entry;
	}

      save_data(urecord, user);
    } 

  destroy_loaded(urecord);

#if !defined(HAVE_UTHOST)
  SH_FREE(host);
#endif
  return;
}

#ifdef SH_CUTEST
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

#include "CuTest.h"

void Test_login (CuTest *tc) {
  char bitarr[10] = { 0,0,0,0,0,0,0,0,0,128 };
  unsigned int i;
  int j, k;
  char buf[1024];
  char *p, *q;
  size_t l1, l2;

  /* Check bitarray */

  for (i = 0; i < 72; ++i)
    {
      set_bool(bitarr, i, 1);
    }
  for (i = 72; i < 80; ++i)
    {
      set_bool(bitarr, i, 0);
    }
  for (i = 0; i < 80; ++i)
    {
      j = get_bool(bitarr, i);
      if (i < 72)
	CuAssertTrue(tc, j > 0);
      else
	CuAssertIntEquals(tc, 0, j);
    }

  /* check build_path */

  j = sl_strlcpy(buf, DEFAULT_DATAROOT, sizeof(buf));
  CuAssertIntEquals(tc, 0, j);

  p = build_path("rainer");
  q = sh_util_dirname(p);
  j = strncmp(buf, q, strlen(buf));
  l1 = strlen(buf); l2 = strlen(q);
  CuAssertTrue(tc, l2 >= l1);
  CuAssertIntEquals(tc, 0, j);

  q = sh_util_basename(p);
  CuAssertStrEquals(tc, q, "cmFpbmVy");

  { /* Check load/save of user data */
    struct sh_track urecord, *precord;
    struct sh_track_entry uentry0, *pentry;
    struct sh_track_entry uentry1;

    urecord.head.version   = 40;
    urecord.head.n_entries = 41;
    urecord.head.last_login = 42;
    for (i = 0; i < SH_LTRACK_GTRES; ++i)
      urecord.head.array[i] = 0;
    urecord.head.array[30] = 30;

    urecord.list = &uentry0;
    uentry0.next = &uentry1;
    uentry1.next = NULL;

    uentry0.data.last_login = 52;
    strcpy(uentry0.data.hostname, "host0");
    for (i = 0; i < SH_LTRACK_HTRES; ++i)
      uentry0.data.array[i] = 0;
    uentry0.data.array[5] = 50;

    uentry1.data.last_login = 62;
    strcpy(uentry1.data.hostname, "host1");
    for (i = 0; i < SH_LTRACK_HTRES; ++i)
      uentry1.data.array[i] = 0;
    uentry1.data.array[6] = 60;

    snprintf(buf, sizeof(buf), "cutest_%06d", (int) getpid());

    save_data_int(&urecord, buf);

    precord = load_data_int(buf);

    CuAssertIntEquals(tc, urecord.head.version, (precord->head).version);
    CuAssertIntEquals(tc, urecord.head.n_entries, (precord->head).n_entries);
    CuAssertIntEquals(tc, urecord.head.last_login, (precord->head).last_login);
    for (i = 0; i < SH_LTRACK_GTRES; ++i)
      CuAssertIntEquals(tc, urecord.head.array[i], (precord->head).array[i]);

    CuAssertPtrNotNull(tc, precord->list);
    pentry = precord->list;
    CuAssertIntEquals(tc, uentry1.data.last_login, (pentry->data).last_login);
    CuAssertStrEquals(tc, uentry1.data.hostname, (pentry->data).hostname);
    for (i = 0; i < SH_LTRACK_HTRES; ++i)
      CuAssertIntEquals(tc, uentry1.data.array[i], (pentry->data).array[i]);

    CuAssertPtrNotNull(tc, pentry->next);
    pentry = pentry->next;
    CuAssertIntEquals(tc, uentry0.data.last_login, (pentry->data).last_login);
    CuAssertStrEquals(tc, uentry0.data.hostname, (pentry->data).hostname);
    for (i = 0; i < SH_LTRACK_HTRES; ++i)
      CuAssertIntEquals(tc, uentry0.data.array[i], (pentry->data).array[i]);

    CuAssertPtrEquals(tc, pentry->next, NULL);
    destroy_loaded(precord);
    unlink(buf);

    precord = load_data_int("supacalifragilistic");
    CuAssertPtrNotNull(tc, precord);
    CuAssertPtrEquals(tc, precord->list, NULL);
    CuAssertIntEquals(tc, SH_LTRACK_VERSION, (precord->head).version);
    CuAssertIntEquals(tc, 0, (precord->head).n_entries);
    CuAssertIntEquals(tc, 0, (precord->head).last_login);
    for (i = 0; i < SH_LTRACK_GTRES; ++i)
      CuAssertIntEquals(tc, 0, (precord->head).array[i]);
    destroy_loaded(precord);

    precord = load_data_int(NULL);
    CuAssertPtrNotNull(tc, precord);
    CuAssertPtrEquals(tc, precord->list, NULL);
    CuAssertIntEquals(tc, SH_LTRACK_VERSION, (precord->head).version);
    CuAssertIntEquals(tc, 0, (precord->head).n_entries);
    CuAssertIntEquals(tc, 0, (precord->head).last_login);
    for (i = 0; i < SH_LTRACK_GTRES; ++i)
      CuAssertIntEquals(tc, 0, (precord->head).array[i]);
    destroy_loaded(precord);
  }

  /* check configuration */

  j = sh_login_set_siglevel("duh");
  CuAssertIntEquals(tc, -1, j);
  CuAssertIntEquals(tc, SIG00, sig_level);

  j = sh_login_set_siglevel("yes");
  CuAssertIntEquals(tc, 0, j);
  CuAssertIntEquals(tc, SIG01, sig_level);
  j = sh_login_set_siglevel("no");
  CuAssertIntEquals(tc, 0, j);
  CuAssertIntEquals(tc, SIG00, sig_level);
  j = sh_login_set_siglevel("paranoid");
  CuAssertIntEquals(tc, 0, j);
  CuAssertIntEquals(tc, SIG05, sig_level);

  j = sh_login_set_checklevel("duh");
  CuAssertIntEquals(tc, -1, j);
  CuAssertIntEquals(tc, CHECK_NONE, check_level);

  j = sh_login_set_checklevel("yes");
  CuAssertIntEquals(tc, 0, j);
  CuAssertIntEquals(tc, CHECK_HOST, check_level);
  j = sh_login_set_checklevel("no");
  CuAssertIntEquals(tc, 0, j);
  CuAssertIntEquals(tc, CHECK_NONE, check_level);
  j = sh_login_set_checklevel("domain");
  CuAssertIntEquals(tc, 0, j);
  CuAssertIntEquals(tc, CHECK_DOMAIN, check_level);

  j = sh_login_set_def_allow("always");
  CuAssertIntEquals(tc, 0, j);
  for (j = 0; j < LTRACK_NDAYS; ++j)
    {
      for (i = 0; i < SH_LTRACK_GTRES; ++i)
	{
	  k = get_bool(global_dates[j], i);
	  CuAssertTrue(tc, k > 0);
	}
    }

  j = sh_login_set_def_allow("never");
  CuAssertIntEquals(tc, 0, j);
  for (j = 0; j < LTRACK_NDAYS; ++j)
    {
      for (i = 0; i < SH_LTRACK_GTRES; ++i)
	{
	  k = get_bool(global_dates[j], i);
	  CuAssertIntEquals(tc, 0, k);
	}
    }

  j = sh_login_set_def_allow("workdays( 0:12-1:30, 07:30-18:29,23:30-23:59)");
  CuAssertIntEquals(tc, 0, j);
  for (j = 0; j < LTRACK_NDAYS; ++j)
    {
      for (i = 0; i < SH_LTRACK_GTRES; ++i)
	{
	  k = get_bool(global_dates[j], i);
	  // fprintf(stderr, "%d: %d: %d\n", j, i, k);
	  if (j == WORKDAYS)
	    {
	      if ( (i>=1 && i<=9) || (i>=45 && i <=110) || (i>=141 && i<=143))
		CuAssertTrue(tc, k > 0);
	      else
		CuAssertIntEquals(tc, 0, k);
	    }
	  else
	    {
	      CuAssertIntEquals(tc, 0, k);
	    }
	}
    }

  j = sh_login_set_user_allow("rainer :workdays( 0:12-1:30, 07:30-18:29,23:30-23:59)");
  CuAssertIntEquals(tc, 0, j);
  j = sh_login_set_user_allow("rainer :saturday( 0:0-23:59)");
  CuAssertIntEquals(tc, 0, j);
  j = sh_login_set_user_allow("rain : workdays(0:12-1:30, 07:30-18:29,23:30-23:59)");
  CuAssertIntEquals(tc, 0, j);
  j = sh_login_set_user_allow("cat: workdays( 0:12-1:30, 07:30-18:29,23:30-23:59 )");
  CuAssertIntEquals(tc, 0, j);
  j = sh_login_set_user_allow("cat: sunday(0:00-23:59)");
  CuAssertIntEquals(tc, 0, j);

  {
    int count = 0;
    struct sh_track_dates * u = user_dates;
    
    CuAssertPtrNotNull(tc, u);

    do {

      if (count == 0) {
	CuAssertStrEquals(tc, u->user, "cat");
	CuAssertPtrNotNull(tc, u->next);
      }
      else if (count == 1) {
	CuAssertStrEquals(tc, u->user, "rain");
	CuAssertPtrNotNull(tc, u->next);
      }
      else if (count == 2) {
	CuAssertStrEquals(tc, u->user, "rainer");
	CuAssertPtrEquals(tc, u->next, NULL);
      }

      for (j = 0; j < LTRACK_NDAYS; ++j)
	{
	  for (i = 0; i < SH_LTRACK_GTRES; ++i)
	    {
	      k = get_bool(u->dates[j], i);
	      // fprintf(stderr, "%d: %d: %d\n", j, i, k);
	      if (j == WORKDAYS)
		{
		  if ( (i>=1 && i<=9) || (i>=45 && i <=110) || 
		       (i>=141 && i<=143) )
		    {
		      CuAssertTrue(tc, k > 0);
		    }
		  else
		    {
		      CuAssertIntEquals(tc, 0, k);
		    }
		}
	      else
		{
		  if ((count == 0 && j == SUNDAY) || 
		      (count == 2 && j == SATURDAY))
		    CuAssertTrue(tc, k > 0);
		  else
		    CuAssertIntEquals(tc, 0, k);
		}
	    }
	}

      if (u->next == NULL)
	break;

      u = u->next; ++count;

    } while (1 == 1);
  }

  sh_login_reset();
  CuAssertIntEquals(tc, SIG00, sig_level);
  CuAssertIntEquals(tc, CHECK_NONE, check_level);

  /* check dates */

  j = sh_login_set_def_allow("workdays( 0:12-1:30, 07:30-18:29,23:30-23:59)");
  CuAssertIntEquals(tc, 0, j);

  j = check_login_date("rainer", 0, 2);
  CuAssertIntEquals(tc, -1, j);
  j = check_login_date("rainer", 1, 2);
  CuAssertIntEquals(tc,  0, j);
  j = check_login_date("rainer",50, 3);
  CuAssertIntEquals(tc,  0, j);
  j = check_login_date("rainer",142, 5);
  CuAssertIntEquals(tc,  0, j);
  j = check_login_date("rainer", 1, 0);
  CuAssertIntEquals(tc, -1, j);
  j = check_login_date("rainer", 1, 6);
  CuAssertIntEquals(tc, -1, j);
  j = sh_login_set_user_allow("rainer :saturday( 0:0-23:59)");
  CuAssertIntEquals(tc, 0, j);
  j = check_login_date("rainer", 1, 6);
  CuAssertIntEquals(tc,  0, j);
  j = sh_login_set_user_allow("mouse :sunday( 0:0-23:59)");
  CuAssertIntEquals(tc, 0, j);
  j = sh_login_set_user_allow("cat :saturday(0:0-23:59)");
  CuAssertIntEquals(tc, 0, j);
  j = check_login_date("rainer", 1, 6);
  CuAssertIntEquals(tc,  0, j);
  j = check_login_date("mouse", 1, 6);
  CuAssertIntEquals(tc, -1, j);
  j = check_login_date("mouse", 1, 0);
  CuAssertIntEquals(tc,  0, j);
  j = check_login_date("cat", 1, 6);
  CuAssertIntEquals(tc,  0, j);
  j = check_login_date("dog", 1, 6);
  CuAssertIntEquals(tc, -1, j);

  sh_login_reset();

  /* statistics, critical values */
  {
    double f;

    f = M_crit(1, SIG05);
    CuAssertTrue(tc, f > 0.974 && f < 0.976);
    f = M_crit(13, SIG05);
    CuAssertTrue(tc, f > 0.576 && f < 0.578);
    f = M_crit(22, SIG05);
    CuAssertTrue(tc, f > 0.405 && f < 0.407);
    f = M_crit(10, SIG05);
    CuAssertTrue(tc, f > 0.646 && f < 0.648);
    f = M_crit(10, SIG01);
    CuAssertTrue(tc, f > 0.759 && f < 0.761);
  }

  /* stripped hostname */
  p = stripped_hostname("127.20.120.100");
  CuAssertStrEquals(tc, "127.20", p);

  p = stripped_hostname("foo.www.example.com");
  CuAssertStrEquals(tc, p, "www.example.com");

  p = stripped_hostname("www.example.com");
  CuAssertStrEquals(tc, p, "example.com");

  p = stripped_hostname("localhost");
  CuAssertStrEquals(tc, p, "localhost");

  {
    struct tm tt;

    tt.tm_hour =  0;
    tt.tm_min  = 30;
    tt.tm_sec  =  0;

    for (i = 0; i < 24; ++i)
      {
	tt.tm_hour =  i;
	j = time_to_index(&tt, SH_LTRACK_HTRES);
	CuAssertIntEquals(tc, j, i);
      }

    tt.tm_min  = 10;

    for (i = 0; i < 24; ++i)
      {
	tt.tm_hour =  i;
	j = time_to_index(&tt, SH_LTRACK_GTRES);
	CuAssertIntEquals(tc, 1+i*6, j);
      }
  } 
}
/* #ifdef SH_CUTEST */
#endif

#else

#ifdef SH_CUTEST
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

#include "CuTest.h"

void Test_login (CuTest *tc) {
  (void) tc;
}

/* #ifdef SH_CUTEST */
#endif

#endif
