/* $Id: isdnlog.c,v 1.24 1995/11/12 11:08:16 akool Exp akool $
 *
 * ISDN accounting for isdn4linux. (log-module)
 *
 * Copyright 1995 by Andreas Kool (akool@Kool.f.EUnet.de)
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Log: isdnlog.c,v $
 * Revision 1.24  1995/11/12  11:08:16  akool
 * Auch die "call reference" wird (ansatzweise) ausgewertet
 * Neue Option "-x" aktiviert X11-Popup
 * Date/Time wird ausgewertet
 * AOC-D wird korrekt ausgewertet
 * Neue Option "-t" setzt Systemzeit auf die von der VSt gemeldete
 * Die "-m" Option kann nun auch mehrfach (additiv) angegeben werden
 *
 * Revision 1.23  1995/11/06  18:03:16  akool
 * "-m16" zeigt die Cause im Klartext an
 * Auch Gebuehreneinheiten > 255 werden korrekt ausgewertet
 *
 * Revision 1.22  1995/10/22  14:43:16  akool
 * General cleanup
 * "isdn.log" um 'I' == dialin / 'O' == dialout erweitert
 * Auch nicht zustande gekommene Verbindungen werden (mit cause) protokolliert
 *
 * Revision 1.21  1995/10/18  21:25:16  akool
 * Option "-r" implementiert
 * Charging-Infos waehrend der Verbindung (FACILITY) werden ignoriert
 * "/etc/isdnlog.pid" wird erzeugt
 *
 * Revision 1.20  1995/10/15  17:23:16  akool
 * Volles D-Kanal Protokoll implementiert (fuer Teles-0.4d Treiber)
 *
 * Revision 1.13  1995/09/30  09:34:16  akool
 * Option "-m", Console-Meldung implementiert
 * Flush bei SIGTERM implementiert
 *
 * Revision 1.12  1995/09/29  17:21:13  akool
 * "isdn.log" um Zeiteintrag in UTC erweitert
 *
 * Revision 1.11  1995/09/28  18:51:17  akool
 * First public release
 *
 * Revision 1.1  1995/09/16  16:54:12  akool
 * Initial revision
 *
 */


#define  PUBLIC extern
#include "isdnlog.h"


static char  usage[]   = "%s: usage: %s [ -%s ] file\n";
static char  options[] = "vsi:pm:rtx";
static char  msg1[]    = "%s: Can't open %s (errno=%d)\n";


static FILE  *log;
static CALL   call[4];
static int    chan = 3;
static int    pid = -1;
static int    xinfo = 0;
static int    fifo = -1;
static time_t t;


#ifdef DEBUG
static void deb(int tei, int itei, int cref, char *msg)
{
  register char *p;
  auto char 	 s[BUFSIZ];


  strcpy(s, ctime(&t));

  if (p = strchr(s, '\n'))
    *p = 0;

  fprintf(stderr, "\t*** %s %3d/%d  %3d/%3d: %s\n", s + 4, tei, itei, cref, cref & 0x7f, msg);
} /* deb */
#endif


static void clear(int itei)
{
  *call[itei].num[CALLING] = *call[itei].num[CALLED] = 0;
  call[itei].tei = 0;
  call[itei].dialin = DIALOUT;
  call[itei].eh = 0;
  call[itei].embryonic = 0;
  call[itei].release = 0;
  call[itei].connect = (time_t)0;
  call[itei].disconnect = (time_t)0;
  call[itei].duration = (clock_t)0;
} /* clear */


static void logger(int itei, char *cause)
{
  register char  *p;
  auto 	   char   s[BUFSIZ];


  strcpy(s, ctime(&call[itei].connect));

  if (p = strchr(s, '\n'))
    *p = 0;

  fprintf(log, "%s|%-16s|%-16s|%5d|%10d|%10d|%5d|%c",
    s + 4, call[itei].num[CALLING], call[itei].num[CALLED],
    call[itei].disconnect - call[itei].connect, call[itei].duration,
    call[itei].connect, call[itei].eh, call[itei].dialin ? 'I' : 'O');

  if (*cause)
    fprintf(log, "|%s\n", cause);
  else
    fprintf(log, "\n");

  fflush(log);

  clear(itei);
} /* logger */


static void info(int itei, char *facts)
{
  register char *p, c;
  auto char   	 s[BUFSIZ];
  auto XINFO	 x;


  strcpy(s, ctime(&t));

  if (p = strchr(s, '\n')) {
    p -= 5;
    *p = 0;
  } /* if */

  switch (itei) {
    case 0 : c = '1'; break;
    case 1 : c = '2'; break;
   default : c = '?'; break;
  } /* switch */

  x.dialin = call[itei].dialin;
  x.channel = itei;
  strcpy(x.calling, num2nam(call[itei].num[CALLING]));
  strcpy(x.called, num2nam(call[itei].num[CALLED]));
  strcpy(x.info, facts);

  fprintf(stderr, "%s-%c %s %14s->%-14s %s\n",
    call[itei].dialin ? " IN" : "OUT", c,
    s + 11, x.calling, x.called, facts);

  if (xinfo)
    (void)write(fifo, (char *)&x, sizeof(XINFO));
} /* info */


static void waitchild(int isig)
{
  signal(SIGCHLD, SIG_DFL);

  if (pid != -1) {
    (void)kill(pid, SIGTERM);
    (void)waitpid(pid, NULL, 0);

    pid = -1;
  } /* if */
} /* waitchild */


static void tell(char *infocmd, char *arg)
{
  register char *p;
  auto 	   int   fd;


#ifdef DEBUG
  fprintf(stderr, "\t### exec(%s,%s)\n", infocmd, arg);
#else
  if (pid != -1)
    waitchild(SIGCHLD);

  pid = fork();

  if (!pid) { /* child */
    fd = open("/dev/null", O_RDWR);
    close(0);
    dup2(fd, 0);
    close(fd);
    fd = open("/dev/console", O_RDWR);
    close(1);
    close(2);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);

    setpgrp();

    if (p = strchr(infocmd, '$')) {
      *p = 0;
      (void)execl(infocmd, infocmd, arg, p + 2, NULL);
    }
    else
      (void)execl(infocmd, infocmd, arg, NULL);

    _exit(1);
  }
  else if (pid > 0) /* parent */
    signal(SIGCHLD, waitchild);
#endif
} /* tell */


static void termin()
{
  exit(0);
} /* termin */


static long int AOC(char *s)
{
  register char    *p = s;
  register int	    i;
  auto	   long int x, l, ll, mode = 0L, tick = 0L, EH = 0L, y = 0L;


  x = strtol(p, (char **)NULL, 16);             /* unknown, but always "2" */

  l = strtol(p += 3, (char **)NULL, 16);        /* length */

  while (l--) {
    tick = tick << 8;
    tick += strtol(p += 3, (char **)NULL, 16);
  } /* while */

  x = strtol(p += 3, (char **)NULL, 16); 	/* unknown */
  x = strtol(p += 3, (char **)NULL, 16); 	/* unknown */
  mode = strtol(p += 3, (char **)NULL, 16); 	/* steuert wohl AOC-D / AOC-E */
  x = strtol(p += 3, (char **)NULL, 16); 	/* unknown */

  ll = strtol(p += 3, (char **)NULL, 16);  	/* es folgen noch `ll' Bytes */

  if (mode == 0x22) {
    for (i = 0; i < 5; i++)            	        /* 5 Byte unknown */
      x = strtol(p += 3, (char **)NULL, 16); 	/* unknown */

    l = strtol(p += 3, (char **)NULL, 16);      /* length */

    while (l--) {
      EH = EH << 8;
      EH += strtol(p += 3, (char **)NULL, 16);
    } /* while */

    x = strtol(p += 3, (char **)NULL, 16); 	/* unknown */

    l = strtol(p += 3, (char **)NULL, 16);      /* length */

    while (l--) {
      y = y << 8;
      y += strtol(p += 3, (char **)NULL, 16);
    } /* while */

    if (y)       /* AOC-E */
      EH = EH;
    else         /* AOC-D */
      EH = -EH;
  }
  else if (mode == 0x24) {
    for (i = 0; i < 7; i++)            	        /* 7 Byte unknown */
      x = strtol(p += 3, (char **)NULL, 16); 	/* unknown */

    l = strtol(p += 3, (char **)NULL, 16);      /* length */

    while (l--) {
      EH = EH << 8;
      EH += strtol(p += 3, (char **)NULL, 16);
    } /* while */

  } /* else */

  return(EH);
} /* AOC */


main(int argc, char *argv[], char *envp[])
{
  register char      *p;
  register int	      i;
  auto	   char	     *p1;
  auto 	   int        verbose = 0, sync = 0, pipe = 0, message = 0, replay = 0;
  auto	   int	      settime = 0;
  auto 	   int        next = 0, itei = 0, tei = 0, c, hit, cref = -1;
  auto 	   char       s[BUFSIZ], s1[BUFSIZ], infocmd[BUFSIZ], fn2[BUFSIZ];
  auto 	   time_t     tt;
  auto 	   FILE      *fin = (FILE *)NULL, *log1, *fp;
  auto 	   struct tm  tm;
  auto 	   struct tms tms;
  auto	   long int   aoc;
  auto     double     einheit = 0.0;
#ifdef DEBUG
  auto 	   char       sx[BUFSIZ];
#endif


  *infocmd = 0;

  while ((c = getopt(argc, argv, options)) != EOF)
    switch (c) {
      case 'v' : verbose++;
      	       	 break;

      case 's' : sync++;
      	       	 break;

      case 'i' : strcpy(infocmd, optarg);
      	       	 break;

      case 'p' : pipe++;
      	       	 break;

      case 'm' : message += atoi(optarg);

      	       	 if (!message) {
                   message = SHOWNUMBERS;
  	      	   fprintf(stderr, "%s: WARNING: \"-m\" Option now requires numeric Argument\n", argv[0]);
                 } /* if */
      	       	 break;

      case 'r' : replay++;
      	       	 break;

      case 't' : settime++;
      	       	 break;

      case 'x' : xinfo++;
      	       	 break;

      case '?' : fprintf(stderr, usage, argv[0], argv[0], options);
	         return(1);
    } /* switch */

  if (optind == argc) {
    fprintf(stderr, usage, argv[0], argv[0], options);
    return(1);
  } /* if */

  signal(SIGTERM, termin);


  if (!strcmp(argv[optind], "-"))
    fin = stdin;

  if ((fin != (FILE *)NULL) || ((fin = fopen(argv[optind], "r")) != (FILE *)NULL)) {
    if ((log = fopen(LOGFILE, "a")) != (FILE *)NULL) {

      if (verbose) {
        if (p = strrchr(argv[optind], '/'))
	  p++;
        else
          p = argv[optind];

        sprintf(fn2, "%s/%s", TMPDIR, p);
      } /* if */

      if (!verbose || ((log1 = fopen(fn2, "a")) != (FILE *)NULL)) {

        memset(call, 0, sizeof(call));

	if (*infocmd)
    	  readconfig(argv[0]);

      	if (!xinfo || ((fifo = open(IFIFO, O_WRONLY | O_NONBLOCK)) >= 0)) {

#ifndef DEBUG
          if (!replay)
    	    if ((fp = fopen(PIDFILE, "w")) != (FILE *)NULL) {
	      fprintf(fp, "%d\n", (int)getpid());
  	      fclose(fp);
    	      (void)chmod(PIDFILE, 0644);
            } /* if */
#endif


          while (1) {

            if (fgets(s, BUFSIZ, fin) && (*s != '\n')) {

#ifdef DEBUG
              if (replay) {
                if (!*s || (*s == '#'))
                  continue;

                t = atom(s + 4);
                memmove(s, s + 26, 100);
              } /* if */
#else
	      time(&t);
#endif

              if (p = strchr(s, '\n')) {
                *p = 0;

                while (*--p == ' ')
                  *p = 0;
              } /* if */

              if (verbose) {
      	        strcpy(s1, ctime(&t));

                if (p = strchr(s1, '\n'))
                  *p = 0;

                fprintf(log1, "%s  %s\n", s1, s);

      	        if (sync)
                  fflush(log1);
              } /* if */

              if (!memcmp(s, "call reference", 14))
                cref = atoi(s + 15);

              if (p = strstr(s, "tei")) {
                tei = atoi(p + 4);

                if (tei == call[0].tei)
                  itei = 0;
                else if (tei == call[1].tei)
                  itei = 1;
                else
                  itei = 2;
              } /* if */

              if (!memcmp(s, "  Calling", 9))
                next = CALLING;
              else if (!memcmp(s, "  Called", 8))
                next = CALLED;
              else if (!memcmp(s, "  Date/Time", 11))
                next = DATETIME;
              else if (strstr(s, "frame network->user broadcast")) {
                call[chan].dialin = 1;
                call[chan].connect = t;
                call[chan].duration = replay ? t : times(&tms);

#ifdef DEBUG
                deb(tei, itei, cref, "DIALIN");
#endif
              }
              else if (strstr(s, " CONNECT")) {
                if (itei == 2) {
                  if (!call[0].tei)
                    itei = 0;
                  else
                    itei = 1;

                  call[itei].tei = tei;

                  call[itei].embryonic = strstr(s, " CONNECT ACKNOWLEDGE") ? 1 : 0;
                  call[itei].release = 0;
                  call[itei].connect = t;
                  call[itei].duration = replay ? t : times(&tms);

                  strcpy(call[itei].num[CALLING], call[chan].num[CALLING]);
                  strcpy(call[itei].num[CALLED], call[chan].num[CALLED]);
                  call[itei].dialin = call[chan].dialin;

                  clear(chan);

#ifdef DEBUG
                  sprintf(sx, "CONNECT%s", call[itei].embryonic ? " (EMBRYONIC)" : "");
                  deb(tei, itei, cref, sx);
#endif

		  if (message & SHOWCONNECT)
       		    info(itei, "CONNECT");

                  if (*infocmd) {

                    if (!*call[itei].num[CALLING] && !*call[itei].num[CALLED]) {
                      if (*known[0]->infoarg)
                        tell(infocmd, known[0]->infoarg);
                    }
                    else {
       		      hit = 0;

                      for (i = mymsns; i < knowns; i++)
                        if (*known[i]->infoarg && match(call[itei].num[call[itei].dialin ? CALLING : CALLED], known[i]->num)) {
                          tell(infocmd, known[i]->infoarg);
                          hit++;
                          break;
                        } /* if */

                      if (!hit)
                        for (i = 0; i < mymsns; i++)
                          if (*known[i]->infoarg && match(call[itei].num[call[itei].dialin ? CALLED : CALLING], known[i]->num)) {
                            tell(infocmd, known[i]->infoarg);
                            break;
                          } /* if */
                    } /* else */
                  } /* if */
                } /* if */
              }
              else if (strstr(s, "RELEASE")) {
                if (itei < 2) {
                  if (strstr(s, "RELEASE COMPLETE"))
                    call[itei].release = 2;
                  else {
                    call[itei].release++;

                    if (call[itei].embryonic)
                      call[itei].release = 2;
                  } /* else */

                  if (call[itei].release > 1) {
  finish:           call[itei].disconnect = t;

                    if (replay)
    	              call[itei].duration = (t - call[itei].duration) * 100;
                    else
    	              call[itei].duration = times(&tms) - call[itei].duration;

#ifdef DEBUG
  	            sprintf(sx, "LOG(%s -> %s, %d/%d sec, %d EH)",
  	              call[itei].num[CALLING],
  	              call[itei].num[CALLED],
  	              call[itei].disconnect - call[itei].connect,
  	              call[itei].duration,
  	              call[itei].eh);
                    deb(tei, itei, cref, sx);
#endif

                    if (message & SHOWHANGUP) {
                      sprintf(s1, "HANGUP (%d EH = DM %s = %d s)",
  	                call[itei].eh,
  	                double2str(call[itei].eh * einheit, 6, 2, DEB),
                        call[itei].disconnect - call[itei].connect);
		      info(itei, s1);
       		    } /* if */

                    if (*infocmd && hangup)
                      tell(infocmd, hangup);

                    logger(itei, "");

                    itei = 2;
	          } /* if */
	        } /* if */
              }
              else if (!memcmp(s, "   contents", 11)) {
                aoc = AOC(s + 12);

                if (aoc < 0) {
                  if (message & SHOWAOCD) {
                    sprintf(s1, "%d.EH = DM %s (%d s)",
                        -aoc,
  	                double2str(-aoc * einheit, 6, 2, DEB),
                        t - call[itei].connect);

                    info(itei, s1);
                  } /* if */
                }
                else {
                  if (itei < 2) {
                    call[itei].eh = (int)aoc;
#ifdef DEBUG
                    sprintf(sx, "AOC-E=%d", call[itei].eh);
                    deb(tei, itei, cref, sx);
#endif
                    goto finish;
                  } /* if */
		} /* else */
              }
              else if (!memcmp(s, "    number digits", 17)) {
                strcpy(call[chan].num[next], s + 18);

	        if ((message & SHOWNUMBERS) && (next == CALLED))
	          info(chan, "DIALOUT");

#ifdef DEBUG
                if (next == CALLED) {
                  sprintf(sx, "NUMBERS: %s -> %s", call[chan].num[CALLING], call[chan].num[CALLED]);
                  deb(tei, itei, cref, sx);
                } /* if */
#endif
              }
              else if (p = strstr(s, "cause value")) {
                if ((itei == 2) && (call[chan].dialin)) {

#ifdef DEBUG
                  sprintf(sx, "EMBRYONIC %s -> %s (%s)", call[chan].num[CALLING], call[chan].num[CALLED], p + 17);
                  deb(tei, itei, cref, sx);
#endif

		  call[chan].disconnect = t;

           	  if (replay)
    	            call[chan].duration = (t - call[chan].duration) * 100;
                  else
    	            call[chan].duration = times(&tms) - call[chan].duration;

                  logger(chan, p + 17);
                } /* if */

                if (message & SHOWCAUSE)
                  info(itei, p + 17);
              }
              else if ((next == DATETIME) && !memcmp(s, "    octet 3", 11)) {
                i = (int)strtol(s + 13, (char **)NULL, 2);

                switch (s[11]) {
                  case ' ' : tm.tm_year = i;
                             einheit = (tm.tm_year > 95) ? 0.12 : 0.23;
                       	     break;
                  case 'a' : tm.tm_mon 	= i - 1; break;
                  case 'b' : tm.tm_mday = i;  	 break;
                  case 'c' : tm.tm_hour = i; 	 break;
                  case 'd' : tm.tm_min 	= i;

  		       	     tm.tm_sec = 0;
  			     tm.tm_wday = tm.tm_yday = 0;
  			     tm.tm_isdst = -1;

                             if (settime) {
  			       tt = mktime(&tm);
#ifdef DEBUG
       			       strcpy(s1, ctime(&tt));

  			       if (p = strchr(s1, '\n'))
    			         *p = 0;

  			       sprintf(sx, "SETCLOCK(%s)", s1);
  			       deb(tei, itei, cref, sx);
#else
	      		       time(&t);

                               if (labs(t - tt) > 61) {
       			         (void)stime(&tt);

                                 /* Nicht gerade sauber, sollte aber all zu
                                    grosse Spruenge verhindern! */
                	         call[chan].connect = tt;
                               } /* if */
#endif
			     } /* if */

		       	     if (message & SHOWTIME) {
		       	       sprintf(s1, "TIME: %02d%s%02d  %02d:%02d",
		       	           tm.tm_mday,
		       	           Months[tm.tm_mon],
		       	           tm.tm_year,
		       	           tm.tm_hour,
		       	           tm.tm_min);
                               info(itei, s1);
		       	     } /* if */

			     break;
                } /* switch */
              } /* else */

              if (pipe)
                fprintf(stdout, "%s\n", s);

	    }
            else if (replay)
              break;
          } /* while */

          if (xinfo)
            close(fifo);
	}
        else {
          fprintf(stderr, msg1, argv[0], IFIFO, errno);
          return(1);
        } /* else */

        if (verbose)
          fclose(log1);

      }
      else {
        fprintf(stderr, msg1, argv[0], fn2, errno);
        return(1);
      } /* else */

      fclose(log);
    }
    else {
      fprintf(stderr, msg1, argv[0], LOGFILE, errno);
      return(1);
    } /* else */

    fclose(fin);
  }
  else {
    fprintf(stderr, msg1, argv[0], argv[optind], errno);
    return(1);
  } /* else */

  return(0);
} /* main */
