/* wtmpxcut */

/*----------------------------------------------------------------------*
 *        Name: wtmpxcut.c - trims the /var/adm/wtmpx file              *
 *       Usage: wtmpxcut [ days ]                                       *
 *     Options: days(int) - the number of days back to save             *
 *                                                                      *
 * Description: This program allows you to slim down your               *
 *              /var/adm/wtmpx. Every time someone logs into the        *
 *              system an entry goes into wtmpx.  This program          *
 *              removes all entries older than 30 days, or whatever     *
 *              you specify as the first arguement.                     *
 *                                                                      *
 *              This program was originally written in French.  I       *
 *              added a few helpful outputs, translated the             * 
 *              comments, added the removal of /tmp/wtmpx,              *
 *              added all the command line options, made fairly         *
 *              sure there were no memory leaks, and tested it          *
 *              to work on Solaris.                                     *
 *              The original author of this code is unknown.            *
 *                                                                      *
 *   Compiling: gcc -o wtmpxcut wtmpxcut.c [ -lgen ]                    *
 *              On Solaris 2.5 or before, you must include -lgen.       *
 *                                                                      *
 *              Peter Beckman <beckman@purplecow.com>                   *
 *              http://www.purplecow.com/sysadmin/                      *
 *              March 3, 1999                                           *
 *                                                                      *
 *----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/types.h>
#include <fcntl.h>
#include <utmpx.h>
#include <time.h>
#include <errno.h>
#include <libgen.h>

#define DELAIS  (time_t) (30)                           /* in days */
#define _PATH_CP "cp"

void usage(char **argv)
{
   fprintf(stdout,"Usage: %s [ -q ] [-f filepath ] [ -t filepath ] [ days ]\n",argv[0]);
   fprintf(stdout,"       -q            suppress messages\n");
   fprintf(stdout,"       -f filename   set alternative file to wtmpx\n");
   fprintf(stdout,"                      Default: /var/adm/wtmpx\n");
   fprintf(stdout,"       -t filename   set alternative file to temp location\n");
   fprintf(stdout,"                      Default: /tmp/wtmpx\n");
   fprintf(stdout,"       days          set number of days to save in wtmpx\n");
   fprintf(stdout,"                      Default: 30\n");
   exit(1);
}

char * errors(int num)
{
   switch(num)
   {
      case ENOENT:
        return("No such file or directory");
      case EINTR:
        return("interrupted system call");	
      case EIO:
        return("I/O error");
      case ENXIO:
        return("No such device or address");
      case EAGAIN:
        return("Resource temporarily unavailable");
      case ENOMEM:
        return("Not enough core");
      case EACCES:
        return("Permission denied");
      case EFAULT:
        return("Bad address");
      case EEXIST:
        return("File exists");
      case ENODEV:
        return("No such device");
      case ENOTDIR:
        return("Not a directory");
      case EISDIR:
        return("Is a directory");
      case EINVAL:
        return("Invalid argument");
      case ENFILE:
        return("File table overflow");
      case EMFILE:
        return("Too many open files");
      case ETXTBSY:
        return("Text file busy");
      case EFBIG:
        return("File too large");
      case ENOSPC:
        return("No space left on device");
      case ESPIPE:
        return("Illegal seek");
      case EROFS:
        return("Read only file system");
      case EPIPE:
        return("Broken pipe");
      case ERANGE:
        return("Math result not representable");
      case EDEADLK:
        return("Deadlock condition");
      case ENOLCK:
        return("No record locks available");
      case EDQUOT:
        return("Disc quota exceeded");
      case ENOSR:
        return("out of streams resources");
      case ENOLINK:
        return("the link has been severed");
      case EBADF:
        return("Bad file number");
      case EBADMSG:
        return("trying to read unreadable message");
      case EOVERFLOW:
        return("value too large to be stored in data type");
      case ELOOP:
        return("Symbolic link loop");
      case EMULTIHOP:
        return("multihop attempted");
      case ENAMETOOLONG:
        return("path name is too long");
      case EOPNOTSUPP:
        return("Operation not supported on socket");
      default:
        return("Error Code Unknown");
   }
}

int main(argc, argv)
int argc;
char **argv;
{
  char    *texterror, *wtmpxloc, *tmploc, *bp;
  struct  utmpx wtmpx_rec;
  int     old_fd, new_fd;
  int     p, i=0, verbose=1;
  size_t  len;
  int     wtmpx_rec_sz = sizeof(wtmpx_rec);
  time_t  periode, temps_limite;

  argv[0] = (char *)basename(argv[0]);

  fprintf(stdout,"%s 1.1 (March 3, 1999) by Peter Beckman \n",argv[0]);
  fprintf(stdout,"%s:   More Solaris Tools at http://www.purplecow.com/sysadmin/\n\n",argv[0]);

  /* DO NOT MODIFY THIS STUFF! */
  wtmpxloc = malloc(20);
  tmploc = malloc(20);
  strcpy(wtmpxloc, "/var/adm/wtmpx");
  strcpy(tmploc, "/tmp/wtmpx");

#define OPTIONS        "f:qt:d:"

  while ((p = getopt(argc, argv, OPTIONS)) != -1) {
   switch (p)
   {
      case 'q':
            /* supress output */
            verbose=0;
            break;
      case 'f':
            /* alternative location of wtmpx */
            free(wtmpxloc);
            len = strlen(optarg) + 5;
            wtmpxloc = malloc(len);
            strcpy(wtmpxloc, optarg);
            if (verbose) 
               fprintf(stdout,"%s: Using %s for original wtmpx location.\n",
                    argv[0], wtmpxloc);
            break;
      case 't':
            /* alternative location of temp file */
            free(tmploc);
            len = strlen(optarg) + 5;
            tmploc = malloc(len);
            strcpy(tmploc, optarg);
            if (verbose) 
               fprintf(stdout,"%s: Using %s for temporary wtmpx location.\n",
                    argv[0], tmploc);
            break;
            /*
      case 'd':
            /* days to save 
            periode = (time_t) atoi(optarg);
            if (verbose) 
               fprintf(stdout,"%s: Saving the last %s days in wtmpx.\n",
                    argv[0], optarg);
               */
      default:
           usage(argv);
           break;
   }
  }

  if (argc > 1)
      periode = (time_t) atoi(argv[argc-1]);
  else 
      periode = DELAIS;

  /*
  * Copy /var/adm/wtmpx to /tmp/wtmpx
  */
  putenv("IFS=' \t\n'");                        /* Security */
  putenv("PATH=/usr/bin");                      /* Security */
  if (verbose) 
   fprintf(stdout,"%s: Copying wtmpx...",argv[0]);

  len = strlen(wtmpxloc) + strlen(tmploc) + strlen(_PATH_CP) + 20;
  bp = malloc(len);

  (void)sprintf(bp, "%s %s %s", _PATH_CP, wtmpxloc, tmploc);
  if (system(bp)) {
    fprintf(stderr,"\n%s: Error in copying %s to %s.\nGoodBye.\n",
            argv[0], wtmpxloc, tmploc );
    exit(1);
  }
  free(bp);

  if (verbose) 
   fprintf(stdout,"done.\n");

  /*
   *  Calculate the time limit using time_t
   */
  temps_limite = time(NULL) - (periode * 24 * 3600);
  if (verbose) 
   fprintf(stdout, "%s: Removing entries older than %s", argv[0], 
        asctime(localtime(&temps_limite)));

  /*
   *  Truncate and open a new wtmpx for writing
   */
  if ((new_fd = open(wtmpxloc, O_WRONLY | O_TRUNC)) < 0) {
    texterror = errors(errno);
    fprintf(stderr, "%s: Error opening %s: %s.\n",
            argv[0], wtmpxloc, texterror);
    fprintf(stderr, "%s: Removing %s...", argv[0], tmploc);
    unlink(tmploc);
    fprintf(stderr, "done.\n%s: GoodBye.\n", argv[0]);
    exit(1);
  } /* if */

  /*
   *  Open the original copy of wtmpx for reading
   */
  if ((old_fd = open(tmploc, O_RDONLY)) < 0) {
    texterror = errors(errno);
    fprintf(stderr, "%s: Error opening %s: %s.\nGoodBye.\n",
            argv[0], tmploc, texterror);
    fprintf(stderr, "%s: Removing %s...", argv[0], tmploc);
    unlink(tmploc);
    fprintf(stderr, "done.\n%s: GoodBye.\n", argv[0]);
    exit(1);
  } /* if */

  /*
   * To separate and remove the old entries, we compare the dates
   * and if it is more than x days old, we forget it.
   * If the entry is during the set period, then we write it to
   * the new wtmpx.
   */
  while (read(old_fd, &wtmpx_rec, wtmpx_rec_sz) == wtmpx_rec_sz) {
    if (wtmpx_rec.ut_xtime >= temps_limite) {
      do {
        if (write(new_fd, &wtmpx_rec, wtmpx_rec_sz) != wtmpx_rec_sz) {
          texterror = errors(errno);
          fprintf(stderr, "%s: Error opening record: %s.\nGoodBye.\n", 
                  argv[0], texterror);
          exit(1);
        } /* if */
      } while (read(old_fd, &wtmpx_rec, wtmpx_rec_sz) == wtmpx_rec_sz );
    } else { i++; } /* if */
  } /* while */

  close(new_fd);
  close(old_fd);
  free(wtmpxloc);
  unlink(tmploc);
  free(tmploc);
  if (verbose)
   fprintf(stdout, "%s: %d entries truncated.\nGoodBye.\n", argv[0], i);
  exit(0);                                      /* exit nicely */

} /* main() */

