/* $Id: cleanup.c,v 2.13 1996/02/11 16:51:05 ghudson Exp $
 *
 * Cleanup program for stray processes
 *
 * by Mark Rosenstein <mar@mit.edu>, based on a program by 
 * John Carr <jfc@athena.mit.edu>
 *
 * 1. Create /etc/nologin, then snapshot (1) who is in the password file,
 *    (2) who is logged in, and (3) what process are running, then 
 *    re-enable logins.
 *
 * 2. Kill process owned by users who are not logged in, or are
 *    not in the password file (depending on command line options)
 *
 * 3. If the options indicate it, rebuild the password file from 
 *    logged in user list.
 */

#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <AL/AL.h>
#include "cleanup.h"

char *version = "$Id: cleanup.c,v 2.13 1996/02/11 16:51:05 ghudson Exp $";

#define MDEBUG 0
#define LOGGED_IN 1
#define PASSWD 2

#ifndef TEST_SUBROUTINES

static char *nologin_msg =
  "This machine is down for cleanup; try again in a few seconds.\n";

int force_begin_update(const char *filename, const char *lockfilename,
		       FILE **fp, FILE **tempfp, sigset_t *mask)
{
    if (ALbeginFileUpdate(filename, lockfilename, fp, tempfp, mask) < 0) {
	unlink(lockfilename);
	return ALbeginFileUpdate(filename, lockfilename, fp, tempfp, mask);
    }
    return 0;
}

int main(argc,argv)
int argc;
char *argv[];
{
    FILE *passwdfile, *tempfile;
    sigset_t mask;
    int fd, mode = LOGGED_IN, i, j, found, fail = 0;
    struct cl_user *users, *pword;
    struct cl_proc *procs;

    if (argc == 1)
      mode = LOGGED_IN;
    else if (argc == 2 && !strcmp(argv[1], "-loggedin"))
      mode = LOGGED_IN;
    else if (argc == 2 && !strcmp(argv[1], "-passwd"))
      mode = PASSWD;
    else if (argc == 2 && !strcmp(argv[1], "-debug"))
      mode = MDEBUG;
    else {
	fprintf(stderr, "usage: %s [-loggedin | -passwd | -debug]\n", argv[0]);
	exit(1);
    }

    /* First disable logins */
    fd = open(PATH_NOLOGIN ,O_RDWR | O_EXCL | O_CREAT, 0644);
    if (fd < 0) {
	if (errno == EEXIST) {
	    fprintf(stderr, "%s: %s already exists, not performing cleanup.\n",
		    argv[0], PATH_NOLOGIN);
	    exit(2);
	} else {
	    fprintf(stderr, "%s: Can't create ", argv[0]);
	    perror(PATH_NOLOGIN);
	    exit(3);
	}
    }
    (void)write(fd, nologin_msg, strlen(nologin_msg));
    (void)close(fd);

    /* wait a moment so that any login in progress when we disabled
     * logins is likely to complete
     */
    sleep(2);

    if (force_begin_update(PATH_PASSWD, PATH_PASSWD_LOCK, &passwdfile,
			   &tempfile, &mask) < 0) {
	fprintf(stdout, "cleanup: unable to lock passwd file.\n");
	unlink(PATH_NOLOGIN);
	exit(1);
    }
#ifdef BSD4_4
    unlink("/etc/ptmp.orig");
#endif

    /* snapshot info */
    pword = get_password_entries();
    users = get_logged_in();
    procs = get_processes();

    /* rewrite password file if necessary */
    if (users && mode == LOGGED_IN) {
	fail = rewrite_passwd(users, passwdfile, tempfile);
	if (!fail) {
	    fail = ALfinishFileUpdate(PATH_PASSWD, PATH_PASSWD_LOCK,
				      passwdfile, tempfile, &mask, 1);
	}
    }
    if (!users || mode != LOGGED_IN || fail) {
	ALabortFileUpdate(PATH_PASSWD, PATH_PASSWD_LOCK, passwdfile, tempfile,
			  &mask);
    }
    if (fail || pword == NULL || users == NULL || procs == NULL) {
	unlink(PATH_NOLOGIN);
	exit(1);
    }

    if (unlink(PATH_NOLOGIN) < 0)
      fprintf(stderr, "%s: Warning: unable to unlink %s.\n",
	      argv[0], PATH_NOLOGIN);

    /* First round of kill signals: HUP */
    found = 0;
    for (i = 0; procs[i].pid != -1; i++) {
	switch (mode) {
	case LOGGED_IN:
	    for (j = 0; users[j].uid != -1; j++)
	      if (users[j].uid == procs[i].uid)
		break;
	    if (users[j].uid == -1) {
#ifdef DEBUG
		printf("kill(%d, SIGHUP)\n", procs[i].pid);
#endif
		if (kill(procs[i].pid, SIGHUP) == 0)
		  found = 1;
	    }
	    break;
	case PASSWD:
	    for (j = 0; pword[j].uid != -1; j++)
	      if (pword[j].uid == procs[i].uid)
		break;
	    if (pword[j].uid == -1) {
#ifdef DEBUG
		printf("kill(%d, SIGHUP)\n", procs[i].pid);
#endif
		if (kill(procs[i].pid, SIGHUP) == 0)
		  found = 1;
	    }
	    break;
	default:
	    ;
	}
    }

    /* only do second round if found any */
    if (found) {
	sleep(5);
#ifdef DEBUG
	printf("Starting second pass\n");
#endif

	for (i = 0; procs[i].pid != -1; i++) {
	    switch (mode) {
	    case LOGGED_IN:
		for (j = 0; users[j].uid != -1; j++)
		  if (users[j].uid == procs[i].uid)
		    break;
		if (users[j].uid == -1) {
#ifdef DEBUG
		    printf("kill(%d, SIGKILL)\n", procs[i].pid);
#endif
		    if (kill(procs[i].pid, SIGKILL) == 0)
		      found = 1;
		}
		break;
	    case PASSWD:
		for (j = 0; pword[j].uid != -1; j++)
		  if (pword[j].uid == procs[i].uid)
		    break;
		if (pword[j].uid == -1) {
#ifdef DEBUG
		    printf("kill(%d, SIGKILL)\n", procs[i].pid);
#endif
		    if (kill(procs[i].pid, SIGKILL) == 0)
		      found = 1;
		}
		break;
	    default:
		;
	    }
	}
    }
    return(0);
}

#else /* TEST_SUBROUTINES */

int main ()
{
  struct cl_proc *procs;
  int *uids;

  procs = get_processes ();
  printf ("current processes:\n");
  if (! procs)
    printf ("oops!  cant get procs.\n");
  else
    while (procs->pid > 0) {
      printf ("process %5d owner %5d\n", procs->pid, procs->uid);
      procs++;
    }
  uids = get_logged_in ();
  printf ("\nlogged in users:\n");
  if (! uids)
    printf ("oops!  cant get logged in users.\n");
  else
    while (*uids >= 0) {
      printf ("user %d\n", *uids);
      uids++;
    }
  uids = get_password_entries ();
  printf ("\npasswd users:\n");
  if (! uids)
    printf ("oops!  cant get passwd users.\n");
  else
    while (*uids >= 0) {
      printf ("user %d\n", *uids);
      uids++;
    }
  return 0;
}

#endif /* TEST_SUBROUTINES */
