/*
 *  Copyright (C) Karl Keyte, 1994
 *
 *  Program to hang-up a dip SLIP line after a specified number of
 *  seconds of inactivity.  This allows the line to be held open
 *  while things like news and mail transfer are going on.
 *
 *  Use: sliphangup -h        to get usage information.
 *
 *  Note that the program goes into the background automatically.
 *
 *  IMPORTANT
 *  ---------
 *  The normal use of this program would be to keep the line open
 *  indefinitely until inactivity is detected.  The author accepts
 *  no responsibility for whatever reason if the line does not hang
 *  up.  It may be best to guard the line with a crontab entry to
 *  hang-up the line anyway so that you can satisfy yourself that
 *  it works before risking any huge phone bills!
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>


/*
 *  Defaults
 */
const int   IDLE_TIME  = 60;
const char *INTERFACE  = "sl0";
const char *DIP_EXEC   = "/sbin/dip";
const char *ROUTE_INFO = "/proc/net/route";
const char *VERSION    = "1.1";


long getUseCount (char *);
void goDaemon    ();
void logError    (int, char *);
int  pollIdle    (char *, int);
void killDip     (char *);


int main(int argc, char *argv[])
{
   int        idleTime;
   char       dipExec[256],
              interface[10],
              msg[256];

/*
 *  Crude test for help request!
 */
   if (argv[1][0] == '-' && argv[1][1] == 'h')
      argc = 99;

/*
 *  Display help information
 */
   if (argc > 3)
   {
      fprintf(stderr, "sliphangup - Version %s\n", VERSION);
      fprintf(stderr, "usage: sliphangup [seconds-idle] [interface] [dip-program]\n");
      fprintf(stderr, "       Defaults are:  seconds-idle     = %d\n", IDLE_TIME);
      fprintf(stderr, "       Defaults are:  interface        = %s\n", INTERFACE);
      fprintf(stderr, "       Defaults are:  dip-program      = %s\n", DIP_EXEC);
      return -1;
   }

/*
 *  Set-up operational parameters
 */
   idleTime = (argc>1)? atoi(argv[1]) : IDLE_TIME;
   strcpy(interface, (argc>2)? argv[2] : INTERFACE);
   strcpy(dipExec, (argc>3)? argv[3] : DIP_EXEC);

/*
 *  Zero idle time is meaningless
 */
   if (!idleTime)
   {
      fprintf(stderr, "Idle time must be greater than zero seconds\n");
      return -1;
   }

/*
 *  Push process into the background
 */
   switch(fork())
   {
      case -1:     logError(LOG_ERR, "Can't fork process");
                   return -1;
      case 0:      break;
      default:     return 0;
   }

/*
 *  Become a daemon!
 */
   goDaemon();

   if (!pollIdle(interface, idleTime))
   {
      sprintf(msg, "line '%s' hanging up after %ld seconds inactivity",
              interface, idleTime);
      logError(LOG_INFO, msg);
      killDip(dipExec);
   }
   else
      return -1;

   return 0;
}


/*
 *  Routine to monitor the interface usage and return if the timeout
 *  period is reached without activity.
 */
int pollIdle(char *iface, int timeout)
{
   long      currentUse,
             newUse;
   int       pauseTime,
             passes = 0;

   if ((currentUse = getUseCount(iface)) == -1)
      return -1;
   pauseTime = (timeout < 5)? timeout : 5;

   do
   {
      sleep(pauseTime);
      if ((newUse = getUseCount(iface)) == -1)
         return -1;
      if (newUse == currentUse)
         passes++;
      else
      {
         currentUse = newUse;
         passes = 0;
      }
   } while (passes*pauseTime < timeout);

   return 0;
}


/*
 *  Kill active dip program using dip itself - Only safe way!
 */
void killDip(char *dipExec)
{
   execl(dipExec, "dip", "-k", NULL);
}


/*
 *  Calculate the interface usage by looking at the /proc filesystem
 */
long getUseCount(char *iface)
{
   FILE      * route;
   char        buf[256],
             * s;
   int         field,
               foundIface = 0,
               i;
   long        count;

/*
 *  Open the route information file
 */
   if ((route = fopen(ROUTE_INFO, "rb")) == NULL)
   {
      logError(LOG_WARNING, "Cannot locate /proc route info file");
      return -1;
   }

/*
 *  Calculate which field should be examined
 */
   fgets(buf, 256, route);
   s = strtok(buf, "\t");
   for (field=1; s; field++, s=strtok(NULL, "\t"))
      if (!strcasecmp(s, "use"))
         break;

/*
 *  Drop out if we can't find a usage field
 */
   if (s == NULL)
   {
      fclose(route);
      logError(LOG_WARNING, "Don't understand format of /proc route info file");
      return -1;
   }

   count = 0;

/*
 *  Add up all usage fields for the specified interface
 */
   while (fgets(buf, 256, route))
   {
      s = strtok(buf, "\t");
      if (strcasecmp(s, iface))
         continue;
      for (i=1; i<field; i++)
         s = strtok(NULL, "\t");
      count += atol(s);
      foundIface = 1;
   }

   fclose(route);

/*
 *  If no interface entries were actually found, drop out
 */
   if (!foundIface)
   {
      logError(LOG_WARNING, "Interface not found in active interface list");
      return -1;
   }

/*
 *  Return total usage count
 */
   return count;
}


/*
 *  Make daemon by closing file descriptors and releasing tty
 */
void goDaemon()
{
   int fd;

   close(0);
   close(1);
   close(2);

   if ((fd = open("/dev/tty", O_RDWR)) >= 0) 
   {
      ioctl(fd, TIOCNOTTY, NULL);
      close(fd);
   }
}


/*
 *  Log message to the syslog
 */
void logError(int severity, char *msg)
{
   openlog("sliphangup", LOG_PID, LOG_USER);
   syslog(severity, msg);
   closelog();
}
