/*
 * CAP - Console Access Protection
 *
 * This provides security from physical access to your conoles.
 * When you run this program, it will clear the screen and prompt
 * for a password. After so many failed attempts it will lock the
 * tty and not allow them to try anymore. While this program is
 * running, they can't abort this program, and they can not switch
 * consoles either. The only only way around this is to reboot the
 * computer, in which case it will be obvious that someone tried to
 * access your server's consoles. This will log the date and time
 * the person tried to get access into your console.
 *
 * To compile: [g]cc -o CAP CAP.c -ltermcap [-lcrypt]
 * If you *not* have shadow passwords compile with -DNO_USE_SHADOW
 * To compile in debug (or testing) mode, compile with -DDEBUG.
 *
 * Shok (Matt Conover), shok@dataforce.net
 */

#include <pwd.h>
#include <term.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <syslog.h>
#include <shadow.h>
#include <linux/vt.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/types.h>

#define ERROR -1
#define SAME   0
#define LOCKOUT 3 /* How long in minutes to lock out of the console.     */
#define MAXFAIL 3 /* Number of times they can enter an invalid password. */
                  /* before being locked out for LOCKOUT minutes.        */

/* Used to disable switching consoles. */
#define LOCKVT(x)     if ((ioctl(fd, VT_LOCKSWITCH, 1)) == ERROR) {    \
                         perror("locking console (/dev/tty/)");        \
                         exit(ERROR);                                  \
                      }

/* Used to reenable ability to switch consoles. */
#define UNLOCKVT(x)   if ((ioctl(fd, VT_UNLOCKSWITCH, 1)) == ERROR) {  \
                         perror("locking console (/dev/tty/)");        \
                         exit(ERROR);                                  \
                      }


int fd; /* Console fd. */

char *strip(char *str); /* Used to strip newlines from ctime().          */
#ifdef DEBUG
void sighandler(int signum);
#endif DEBUG

int main()
{
  int uid;
  int failed    = 0; /* Number of failed attempts out of MAXFAIL.      */
  int totfailed = 0; /* Number of total failed attempts (not reseted). */
  
  time_t tm;

  char curtime[64];

  /* Don't change passwd or realpasswd's length. This is the maximum */
  /* password length allow from getpass(). Any smaller can overflow. */
  char *pass, passwd[128], realpasswd[128]; 

  struct passwd *pwd;
#ifndef NO_USE_SHADOW
  struct spwd   *spwd;
#endif

  if ((fd = open("/dev/tty", O_RDWR)) == ERROR) {
	perror("opening console (/dev/tty)");
	exit(ERROR);
  }
  
  /* Disable signals (so attackers can't abort program). */

#ifndef DEBUG
   signal(SIGHUP,  SIG_IGN);
   signal(SIGINT,  SIG_IGN);
   signal(SIGTERM, SIG_IGN);
   signal(SIGQUIT, SIG_IGN);
   signal(SIGTSTP, SIG_IGN);
#else
   signal(SIGINT, sighandler);
   signal(SIGTERM, sighandler);
   signal(SIGQUIT, sighandler);
   signal(SIGTSTP, sighandler);
   signal(SIGSEGV, sighandler);
#endif

  LOCKVT(fd); /* Lock the VT. It can no longer switch. */

  uid = getuid();
  pwd = getpwuid(uid);
#ifndef NO_USE_SHADOW
  if ((spwd = getspnam(pwd->pw_name)) == NULL) {
     perror("getspnam");
     exit(ERROR);
  }

  strncpy(realpasswd, spwd->sp_pwdp, sizeof(realpasswd));  
#else
  strncpy(realpasswd, pwd->pw_passwd, sizeof(realpasswd)); 
#endif  

  clr();
  printf("w00w00!\n");
  printf("Console is now locked.\n");
  getchar();

  /* Used to log invalid password attempts. */
  openlog("CAP/conprot", LOG_CONS, LOG_AUTHPRIV); 

  while (1) {
     /* Get the password from the user. */
 
     /* For some reason I get warnings without the typecast (broken
        prototype?). */
     pass = getpass("Enter password: ");

     
     /* Encrypt the password from getpass(). 
     /* Note, we are using realpasswd for our salt. This is to allow a */
     /* salt of any size. This also saving us the trouble of getting   */
     /* the salt ourselves.					       */
     strncpy(passwd, crypt(pass, realpasswd), sizeof(passwd));

     passwd[128] = '\0'; /* NULL terminate passwd just to be safe.     */

#ifdef DEBUG
     printf("Encrypted password from user: %s\n", passwd);
     printf("The real encrypted password: %s\n", realpasswd);
#endif

     if ((strcmp(passwd, realpasswd)) == SAME) {
        /* Unlock the console, to allow it to switch. */
        UNLOCKVT(fd);

        closelog(); /* Close logging. */

	clr();

        printf("Everything is now restored.\n");

        if (totfailed == 0) printf("No one tried to access the console.\n");
        else printf("Total number of failed attempts to unlock console: %d\n",
	            totfailed);

        exit(0);
     } else {
	failed++, totfailed++; /* Increase number of failed attempts. */

        /* Log bad attempts to syslog. */
        tm = time(NULL);

        snprintf(curtime, sizeof(curtime), (char *)ctime(&tm));
        strip(curtime); /* Strip new lines out of the time. */
        syslog(LOG_WARNING, "Failed access attempt on: %s", curtime);

        printf("Invalid password.\n");

	if (failed >= MAXFAIL) {
	   printf("Maximum number of failed attempts.\n"
                  "Now locking for %d minutes.\n", LOCKOUT);

	   sleep(LOCKOUT * 60); /* Convert the minutes to seconds. */
	   failed = 0; /* Reset the number of failed attempts.     */
        }
     }
  }

  return 0;
}

char *strip(char *str)
{
  register int i;

  for (i = 0; str[i]; i++) 
      /* Strip newline out of string. */
      /* We do this because syslog appends the newline itself. */
      if (str[i] == '\n') str[i] = '\0'; 

  return str;
}

#ifdef DEBUG
void sighandler(int signum)
{
  if (signum == 11) printf("Received SIGSEGV.\n");
  printf("\nAborting and unlocking console.\n");

  UNLOCKVT(fd);

  if (signum == 11) kill(getpid(), 11);
  exit(0);
}
#endif

clr()
{
  char *clear;
  char clbuf[1024], *clbp = clbuf;

  if (tgetent(clbuf, getenv("TERM")) == ERROR) {
     perror("tgetent");
     system("clear");
     return;
  }

  if ((clear = tgetstr("cl", &clbp)) == NULL) {
     perror("tgetent");
     system("clear");
     return;
  }

  if (clear)
     tputs(clear, tgetnum("li"), putchar);
}
