/*
 * Copyright (C) 1994,1995 Lars Fenneberg
 *
 * 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.
 *
 */

/* Parts extracted from Net-3, Authors Fred N. van Kempen, Alan Cox,
 * and from route.c, Authors Fred N. van Kempen, Johannes Stille, Linus 
 * Torvalds, Alan Cox. 
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <ctype.h>
#include <pwd.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <syslog.h>
#include <netinet/in.h>

#include "pathnames.h"
#include <linux/ip_acct_user.h>

char *pname, *quota_prog;
int intervall = 300;	/* every five minutes */
int skfd = -1;

void usage(void)
{
  fprintf(stderr,"Usage: %s [-i <seconds>] <ip-policy-program>\n",pname);
  exit(1);
}

void version(void)
{
  fprintf(stderr,"%s: version %s\n",pname, VERSION);
  exit(1);
}

void log(char *format, ...)
{
	char buff[1024];
	va_list ap;

        va_start(ap,format); 
        vsprintf(buff, format, ap); 
        va_end(ap);

	openlog(pname, LOG_PID, LOG_DAEMON);
	syslog(LOG_ERR, buff);
	closelog();
}


char *my_perror(char *what)
{
	static char message[255];
        if (what == NULL || *what == '\0')
        	sprintf(message,"%s", strerror(errno));
        else
        	sprintf(message,"%s: %s", what, strerror(errno));
        return message;
}                                                                

int lock(char *lockfile)
{
        char linkfile[1024];
        FILE *f;
                                        
        sprintf(linkfile,"%s.%i", lockfile, getpid());
                                                        
        if ((f = fopen(linkfile,"w")) == NULL)
        {
		log(my_perror(linkfile));
        	exit(1);
        }
        fprintf(f,"%i\n", getpid());
        fclose(f);
                
        if (link(linkfile, lockfile) != 0)
        {
		remove(linkfile);
        	return -1;
        }
        remove(linkfile);        
	return 0;                                                                                                                                                                                                                                                                
}

int unlock(char *filename)
{
	return remove(filename);	
}

#ifndef IPAU_HAS_RESET_BY_RW
void clear_all(void)
{
	int uid = IPAU_CLEAR_ALL;

        if (ioctl(skfd, SIOCRSTUT, &uid) < 0) {
        	log(my_perror("SIOCRSTUT"));
		exit(1);
        }
}                                                
#endif

void account(void)
{
  
  char buff[1024];
  int num, uid, sent, recv, flags;
  FILE *proc, *quotas;

#ifdef IPAU_HAS_RESET_BY_RW  
  if ((proc = fopen(_PATH_PROCNET_IP_ACCT_USER, "r+")) == NULL) 
#else
  if ((proc = fopen(_PATH_PROCNET_IP_ACCT_USER, "r")) == NULL)
#endif
  {
     log(my_perror(_PATH_PROCNET_IP_ACCT_USER));
     exit(1);
  }
  
  if ((quotas = popen(quota_prog,"w")) == NULL)
  {
  	log(my_perror(quota_prog));
     	exit(1);
  }
  
  while (fgets(buff,1023,proc)) 
  {
     num = sscanf(buff, "%i %i %i %x\n",&uid,&sent,&recv,&flags);
     if ((num != 4) || (sent+recv+flags == 0)) continue;
     fprintf(quotas,"%i %i %i %i\n",uid, sent, recv, flags);
     fflush(quotas);
  }
  fclose(proc);
  pclose(quotas);

#ifndef IPAU_HAS_RESET_BY_RW
  clear_all();
#endif

}

int scan_stats(void)
{
  int remaining;

  while(1) 
  {
    account();

    remaining=intervall;
    do remaining=sleep(remaining); while (remaining>0);
  }
}

void catch_sig(int sig)
{
	account();
  	log("terminating (signal %i)", sig);
	if (unlock(_PATH_VAR_RUN_IPACCTD_PID) != 0)
	{
		log("can't remove lockfile %s: %s", 
		_PATH_VAR_RUN_IPACCTD_PID, my_perror(""));
	}	
  	exit(0);
}

int main(int argc, char **argv)
{
  extern int optind; 
  extern char *optarg;
  int c,i;

  if (strrchr(argv[0],'/') == NULL) 
  {
    pname = argv[0];
  }
  else
  {
    pname = strrchr(argv[0],'/')+1;
  }

  while ((c = getopt(argc,argv,"vhi:")) > 0) 
  {
  	switch (c)
  	{
		case 'v':
				version();
				break;
		case 'i':
				intervall = atoi(optarg);
				break;
  		case 'h':
  		default:
  				usage();
  				break; /* never reached */
  	}
  }
  
  if (optind < argc)
  {
  	quota_prog = argv[optind++];
  	if (access(quota_prog,F_OK|X_OK) != 0)
  	{
  		log(my_perror(quota_prog));
		return 1;
  	} 
  }
  else
  {
  	usage();
  }

  for(i=0;close(i) == 0; i++)
  	;
  setsid();
  
  switch(fork())
  {
  
	case -1: /* complete failure */
	
		  log(my_perror("can't fork"));
  		  return 1;
  		  break;

  	case  0: /* i am the child */


		  log("ipacctd started (version %s)", VERSION);
		  
		  if (open("/dev/null",O_RDONLY) != 0) 
		  {
		  	log(my_perror("/dev/null"));
		  	return 1;
		  } 
		  if (open("/dev/null",O_WRONLY) != 1) 
		  {
			log(my_perror("/dev/null"));
		  	return 1;
		  }
		  if (open("/dev/null",O_WRONLY) != 2) 
		  {
		  	log(my_perror("/dev/null"));
		  	return 1;
		  }

		  
		  if ((skfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
  		  {
    			log(my_perror("can't create socket"));
    			return 1;
  		  }

		  signal(SIGINT, catch_sig);
		  signal(SIGTERM, catch_sig);
		  signal(SIGQUIT, catch_sig);  		  
		  signal(SIGHUP, catch_sig);

		  if (lock(_PATH_VAR_RUN_IPACCTD_PID) != 0)
		  {
			log("ipacctd is already running (lockfile %s)", 
				_PATH_VAR_RUN_IPACCTD_PID);
			return 1;
		  }

		  return (scan_stats());
		  
		  break;
  	
   }

  return 0;
}
