/* iacctd.c -- Daemon to log inet accounting
 * (C) Ulrich Callmeier 10/1994
 *
 * 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.
 *
 */

/* 
 * This is based on klogd.c by Steve Lord et al.
 */

/*
 * This program still suffers from the change in the locking strategy
 * which I hacked in in a rush. There is still remaining code from 
 * the old strategy which is useless now.
 * This shouldn't matter, except if you try to understand this program ;-) 
 */

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <linux/if.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/time.h>
#include <linux/unistd.h>
#include <netinet/in.h>
#include <netinet/protocols.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>

static char rcsid[] = "$Id: iacctd.c,v 1.6 1995/03/13 22:30:43 uc Exp uc $";

#define __NR_klog __NR_syslog
#define INET_ACCT_READLOG 9
#define BUFSIZE 4096
/* Note this is not some magic limit it is just how much information
   is read at once.
   It just has to be large enough to fit one entry */

static inline _syscall3(int,klog,int,type,char *,b,int,len)

#define DEF_ACCTTAB "/etc/iaccttab"
#define PID_FILE "/var/run/iacctd.pid"

/* permissions of lockfile*/
#define PERMS 0666

struct config
{
    struct ipnetwork *ignorenet;
};

struct ipnetwork
{
    unsigned long netnumber, netmask;
    struct ipnetwork *next;
};

struct config *cfg;
char *cfgfname;

static char *progname;
FILE *outfile = NULL;

char *fname = NULL;
char *dname = NULL;

int peruser = 0;

void usage(void)
{
   fprintf( stderr, "Usage: %s [-c cfgfile] [-f filename] | [-u prefix]\n", progname );
}

static volatile sig_atomic_t may_write;
static volatile sig_atomic_t running;
static volatile sig_atomic_t have_lock;

void signal_handler(int sig)
{
    signal(sig,signal_handler);
    
    if(sig==SIGTSTP)
	{
	    syslog(LOG_DEBUG,"received SIGTSTP\n");
	    may_write = 0;
	}
    else if(sig==SIGCONT)
	{
	    syslog(LOG_DEBUG,"received SIGCONT\n");
	    may_write = 1;
	}
    else
	{
	    syslog(LOG_INFO,"signal_debug received signal %d, this can't happen\n");
	}
}

int do_pid_file(void)
/* return 1 if file could be created */
/* return 0 if daemon already running */
/* this is by no means clean of races, if we take it serious we should do it with
   some well thought out atomic operations */
{
    FILE *f;

    if(access(PID_FILE,F_OK)==0)
        {
            char buff[80];
            int pid;
            /* file exists */

            f = fopen(PID_FILE, "r");
            fgets(buff, sizeof(buff), f);
            fclose(f);

            pid = atoi(buff);

            syslog(LOG_INFO, "found pid-file with pid %d\n", pid);

            if(kill(pid, 0) == -1)
		{
                syslog(LOG_INFO, "process %d doesn't exist anymore\n", pid);
		}
            else
		{
                syslog(LOG_INFO, "process %d is still running.\n", pid);
                return 0;
		}

        }

    f = fopen(PID_FILE, "w");
    fprintf(f, "%d\n", getpid());
    fclose(f);

    return 1;
}

char *intoa(unsigned long addr)
{
      static char buff[18];
      char *p;

      p = (char *) &addr;
      sprintf(buff, "%d.%d.%d.%d",
              (p[0] & 255), (p[1] & 255), (p[2] & 255), (p[3] & 255));
      return(buff);
}

struct config *read_config(char *fname)
{
    char buff[1024];
    FILE *f;
    int line=0;
    struct config *cfg = malloc(sizeof(struct config));
    if(cfg == NULL) return cfg;

    cfg -> ignorenet = NULL;

    f=fopen(fname,"r");
    if(f == NULL) return NULL;

    while(fgets(buff,sizeof(buff),f))
	{
	    /* remove trailing newline */
            char *cmt = strchr(buff,'\n');
            if(cmt) *cmt = '\0';
	    
            line++;

            /* remove comments */
            cmt = strchr(buff,'#');
            if(cmt) *cmt = '\0';

            /* remove leading whitespace */
            while(isspace(*buff))
                {
                    memmove(buff,buff+1,strlen(buff));
                }
	    
            /* remove trailing whitespace */
            cmt = strchr(buff,'\0');
            cmt --;
            while(isspace(*cmt))
                {
                    *cmt = '\0';
                    cmt --;
                }
	    
            /* process nonempty lines */
            if(*buff)
                {
                    char *kwd = buff;
                    char *value = buff + strcspn(buff," \t");
                    *value++ = '\0';
                    while(isspace(*value)) value++;
		    
/*                  printf("key: \"%s\" value: \"%s\" \n",kwd, value);*/

		    if(strcasecmp(kwd, "ignorenet")==0)
			{
			    struct ipnetwork *tmp;
			    char *mask;
			    
			    mask  = value + strcspn(value," \t");
			    *mask++ = '\0';
			    while(isspace(*mask)) mask++;
	
			    tmp = malloc(sizeof(struct ipnetwork));
	
			    if(tmp != NULL)
				{
				    tmp -> netnumber = inet_addr(value);
				    tmp -> netmask = inet_addr(mask);
				    tmp -> next = cfg -> ignorenet;
				    cfg -> ignorenet = tmp;
				    syslog(LOG_DEBUG,
					   "config: added ignore network (netnumber %s)\n",
					   intoa(cfg -> ignorenet -> netnumber));
				    syslog(LOG_DEBUG,
					   "config: added ignore network (netmask %s)\n",
					   intoa(cfg -> ignorenet -> netmask));
				}
			}
		    else
			{
                            syslog(LOG_ERR, "config file: unknown keyword %s in
line %d\n",kwd,line);
                            return NULL;
			}
		}
	}
    fclose(f);
    return cfg;
}

int netignore(unsigned long int addr)
{
    struct ipnetwork *tmp;
    tmp = cfg->ignorenet;

    while(tmp!=NULL)
        {
            if((addr & tmp -> netmask) == tmp -> netnumber)
                {
                    return 1;
                }
            tmp = tmp -> next;
        }
    return 0;
}

char *in_ntoa(unsigned long in)
{
    static char buff[18];
    char *p;
    
    p = (char *) &in;
    sprintf(buff, "%d.%d.%d.%d",
	    (p[0] & 255), (p[1] & 255), (p[2] & 255), (p[3] & 255));
    return(buff);
}

void get_lock()
{
    int tmpfd;
    while((tmpfd = open(LOCKFILE, O_RDWR | O_CREAT | O_EXCL, PERMS))<0)
	{
	    if(errno != EEXIST)
		{
		    syslog(LOG_ERR,"error making lock: %m");
		}
	    else
		{
		    syslog(LOG_INFO,"already locked, sleeping...");
		}
	    sleep(2);
	}
    have_lock = 1;
    close(tmpfd);
}

void release_lock()
{
    if(unlink(LOCKFILE)<0)
	syslog(LOG_ERR,"error removing lockfile: %m");

    have_lock = 0;
}

void process_line(char *line)
{
    char *p_from, *p_to, *p_size, *p_uid, *p_what;
    unsigned long int i_from, i_to;
    char a_from[18], a_to[18];

    p_from = line;

    p_to = strchr(p_from, '\t');
    *p_to++ = '\0';

    p_size = strchr(p_to, '\t');
    *p_size++ = '\0';

    p_uid = strchr(p_size, '\t');
    *p_uid++ = '\0';

    p_what = strchr(p_uid, '\t');
    *p_what++ = '\0';

    i_from = strtoul(p_from, NULL, 10);
    i_to = strtoul(p_to, NULL, 10);

    if(netignore(i_from) || netignore(i_to))
	return;

    strcpy(a_from,in_ntoa(i_from));
    strcpy(a_to,in_ntoa(i_to));
    
    if(peruser)
	{
	    strcpy(fname, dname);
	    strcat(fname, p_uid);
	}
    
    get_lock();
    
    outfile = fopen (fname, "a");
    
    if(outfile == NULL)
	{
	    syslog(LOG_ERR, "error opening file %s: %m", fname);
	    /* data is lost ! */
	}
    else
	{
	    fprintf(outfile, "%lu\t%s\t%s\t%s\t%s\t%s",time(NULL),a_from, a_to, p_size, p_uid, p_what);
	    fclose(outfile);
	}
    release_lock();
    
}

int daemon_init(void)
{
    int i;
    pid_t pid;
    
    if( (pid = fork()) < 0)
	return(-1);
    else if (pid!=0)
	exit(0);

    for(i=0; i<FD_SETSIZE; i++)
	close(i);

    setsid();

    return 0;
}

void stop_daemon(int sig)
{
    unlink(PID_FILE);
    syslog(LOG_INFO, "Inet accounting log daemon terminating.");
    closelog();
    exit(1);
}

int main( int argc, char *argv[] )
{
   int i;
   int n;
   int c;
   char buf[BUFSIZE];
   char linebuf[BUFSIZE];
   char *lineend;

   may_write = 0;
   have_lock = 0;

   cfg = NULL;

   progname = argv[0];

   cfgfname = strdup(DEF_ACCTTAB);

   while ((c = getopt( argc, argv, "oc:df:u:" )) != EOF)
       {
	   switch (c)
	       {
	       case 'f':
		   fname = strdup(optarg);
		   break;
	       case 'u':
		   peruser = 1;
		   dname = strdup(optarg);
		   fname = malloc(strlen(dname)+40);
		   break;
               case 'c':
                   free(cfgfname);
                   cfgfname = strdup(optarg);
                   break;
      	       case '?':
	       default:
		   usage();
		   exit(1);
	       }
       }

   argc -= optind;
   argv += optind;
   
   if (argc > 1)
       {
	   usage();
	   exit(1);
       }

   if((peruser == 0) && (fname == NULL))
       {
	   fprintf(stderr,"Need output filename\n");
	   exit(2);
       }

   openlog("iacctd", 0, LOG_DAEMON);

   /* read config file */
   cfg = read_config(cfgfname);
   if(cfg == NULL)
       {
	   syslog(LOG_ERR, "error reading config file\n");
	   exit(1);
       }

   /* We don't have any files open after daemon_init */
   if(daemon_init()<0)
       {
	   openlog("iacctd", 0, LOG_DAEMON);
	   syslog(LOG_ERR, "daemon_init: %s", strerror(errno));
	   closelog();
	   exit(2);
       }
   else
       {
	   openlog("iacctd", 0, LOG_DAEMON);
	   syslog(LOG_INFO, "Inet accounting daemon started.");
	   may_write = 1;
       }

   /* check and create /var/run/iacctd.pid */
    if(!do_pid_file())
        {
            syslog(LOG_ERR, "daemon already running or stale pid-file\n");
            exit(1);
        }

   /* Signal setups. */
  
   for (i= 1; i < NSIG; ++i)
       signal(i, SIG_IGN);
   signal(SIGINT, stop_daemon);
   signal(SIGKILL, stop_daemon);
   signal(SIGTERM, stop_daemon);
   signal(SIGHUP, stop_daemon);
   signal(SIGTSTP, signal_handler); /* stop writing to file */
   signal(SIGCONT, signal_handler); /* continue writing to file */

   running = 1;

   do 
       {
	   n = klog( INET_ACCT_READLOG, buf, sizeof(buf) );
	   
	   if (n < 0)
	       {
		   syslog(LOG_INFO, "klog: %s", strerror(errno));
		   sleep(1);
                   /* exit(1); */
	       }
	   
	   lineend = buf;
	   
	   while( (lineend-buf) < n)
	       {
		   
		   strcpy(linebuf, lineend);
		   lineend += strlen(lineend);	/* skip line */
		   lineend++;	/* skip terminating NULL */
		   
		   if(!may_write)
		       {
			   while(!may_write)
			       {
				   syslog(LOG_INFO, "writing disabled, sleep(10)...\n");
				   sleep(10);
			       }
		       }

		   process_line(linebuf);
	       }
       } while(running);

   unlink(PID_FILE);

   return 0;
}



