/*
 * pwfix - scan password and shadow files, delete bad entries
 *
 * This program will scan the /etc/passwd and /etc/shadow files.
 * if any lines are found which cannot be parsed by the library
 * routines they will be flagged as errors.
 *
 * With no arguments the program operates interactively and allows
 * the user to delete the bad entries.
 *
 * Options:
 *
 *	-f	Force deletion of bad entries, non-interactive.
 *	-q	Quiet operation, no summary information.
 *	-v	Verbose operation, show summary (default if interactive).
 *
 */

#ident	"$Header: pwfix.c 1.2 89/08/18 $"

/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred.
 *
 */

/*
 * This program is designed to use the library routines which
 * read password and shadow entries.  The intent is that this
 * program does not need to know the file format, library routines
 * will track changes to file format.
 */


#include <stdio.h>
#include <signal.h>
#include <pwd.h>
#include <shadow.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>


#define PRIVILEGED	0	/* priviledged id */

/* exit/return codes */

#define SUCCESS	0	/* Succceeded */
#define NOPERM	1	/* No permission */
#define	FBUSY	2	/* Lock file busy */
#define BADSYN	3	/* Incorrect syntax - args */
#define FMERR	4	/* File manipulation error */
#define CHANGED	5	/* No error, changed files. */

char *progname;
int f_flag;
int v_flag;
int q_flag;
extern int errno;


main(argc, argv)
	int argc;
	char *argv[];
{
	int i, c, fd;
	int p_file = 0;
	int s_file = 0;
	extern char *optarg;
	extern int optind;
	extern uid_t getuid();
	extern void exit();

	progname = argv[0];

	if (getuid() != (uid_t) PRIVILEGED) {
		(void) fprintf(stderr, "%s: Permission denied.\n", progname);
		exit(NOPERM);
	}

	if (lckpwdf() < 0) {
		(void) fprintf(stderr, "%s: Password file(s) busy.  Try again later.\n",
			progname);
		exit(FBUSY);
	}

	while ((c = getopt(argc, argv, "fvq")) != -1) {
		switch (c) {
		case 'f':
			f_flag++;
			break;
		case 'v':
			v_flag++;
			break;
		case 'q':
			q_flag++;
			break;
		case '?':
			(void) fprintf(stderr, "Usage: %s [-fvq]\n", progname);
			(void) ulckpwdf();
			exit(BADSYN);
		}
	}

	if (optind != argc) {
		(void) fprintf(stderr, "Usage: %s [-fvq]\n", progname);
		(void) ulckpwdf();
		exit(BADSYN);
	}

	/* ignore all signals */

	for (i = 1; i < NSIG; i++)
		(void) sigset(i, SIG_IGN);
	
	errno = 0;		/* For correcting sigset to SIGKILL */

	i = process_file(PASSWD, PASSTEMP, OPASSWD, 1);
	if (i == CHANGED)
		p_file = 1;
	else if (i != 0)
		exit(i);

	i = process_file(SHADOW, SHADTEMP, OSHADOW, 0);
	if (i == CHANGED)
		s_file = 1;
	
	(void) ulckpwdf();

	/*
	 * Changes to either file require that the data base
	 * must be rebuilt.
	 */

	if (p_file || s_file) {
		if ((fd = open(PWH_PIPE, O_WRONLY|O_NDELAY, 0)) != -1) {
			if (p_file && s_file)
				(void) write(fd, "r", 1);
			else if (p_file)
				(void) write(fd, "p", 1);
			else 
				(void) write(fd, "s", 1);
		}
	}

	/*
	 * Only got here if first call to process_file was ok.
	 * If the second call failed then we want to return an
	 * error code.  Otherwise, we return either SUCCESS or
	 * CHANGED indication.
	 */

	if ((i != SUCCESS) && (i != CHANGED))
		exit(i);
	else if (p_file)
		exit(CHANGED);
	else
		exit(i);

	/* NOTREACHED */
}

process_file(infile, tempfile, oldfile, is_pw)
	char *infile;		/* file name, /etc/passwd or /etc/shadow */
	char *tempfile;		/* temporary file name */
	char *oldfile;		/* old file name */
	int is_pw;		/* 1 = password, 0 = shadow */
{
	long loc;
	char *p;
	char buf[BUFSIZ+1];
	char ans[BUFSIZ+1];
	int lines_in, lines_out, mode, ok;
	int made_changes = 0;
	int gave_name = 0;
	FILE *fp_pw, *fp_temp;
	struct passwd *pw;
	struct spwd *sp;
	extern struct passwd *fgetpwent();
	extern struct spwd *fgetspent();
	struct stat sb;
	long ftell();

	if (stat(infile, &sb) < 0) {
	    if (is_pw == 0)
		return(SUCCESS);
	    (void) fprintf(stderr, "%s: Stat of %s failed.\n",
		progname, infile);
	    (void) ulckpwdf();
	    return(FMERR);
	}

	if (is_pw)
		mode = 0444;
	else
		mode = 0400;

	(void) umask(~(sb.st_mode & mode));

	/*
	 * Open input file for reading, open temp file for writeing.
	 * Read records from input file, write to temp file if no
	 * errors.  Otherwise user has chance to delete.
	 */

	if ((fp_pw = fopen(infile, "r")) == NULL) {
		(void) fprintf(stderr, "%s: Open of %s failed.\n",
		    progname, infile);
		(void) ulckpwdf();
		return(FMERR);
	}


	if ((fp_temp = fopen(tempfile, "w")) == NULL) {
		(void) fprintf(stderr, "%s: Open of %s failed.\n",
		    progname, tempfile);
		(void) ulckpwdf();
		return(FMERR);
	}

	if (chown(tempfile, sb.st_uid, sb.st_gid) != 0) {
		(void) fclose(fp_temp);
		(void) fclose(fp_pw);
		(void) fprintf(stderr, "%s: Cannot change owner of temp file %s\n",
		    progname, tempfile);
		if (unlink(tempfile))
			(void) fprintf(stderr, "%s: Cannot unlink temp file %s\n",
			    progname, tempfile);
		(void) ulckpwdf();
		return(FMERR);
	}

	lines_in = 0;
	lines_out = 0;

	for (;;) {		/* for loop -- scan all entries */
	    loc = ftell(fp_pw);
	    errno = 0;
	    if (is_pw)
	        pw = fgetpwent(fp_pw);
	    else
		sp = fgetspent(fp_pw);
	    ok = 0;
	    if (is_pw) {
		if (pw == (struct passwd *) NULL)
			ok = 1;
	    } else {
		if (sp == (struct spwd *) NULL)
			ok = 1;
	    }
	    if (ok == 0) {
		if (is_pw)
		    ok = putpwent(pw, fp_temp);
		else
		    ok = putspent(sp, fp_temp);
		if (ok != 0) {
		    (void) fclose(fp_temp);
		    (void) fclose(fp_pw);
		    (void) fprintf(stderr, "%s: Error writing to temp file %s\n",
			progname, tempfile);
		    (void) ulckpwdf();
		    return(FMERR);
		}
		lines_in++;
		lines_out++;
		continue;
	    }
	    if (errno != EINVAL)	/* assume really at eof */
		break;
	    lines_in++;

	    /* encountered an error */

	   if (fseek(fp_pw, loc, 0)) {
		(void) fprintf(stderr, "%s: seek error on file %s\n",
		    progname, infile);
		(void) ulckpwdf();
		return(FMERR);
	    }
	    if (fgets(buf, BUFSIZ, fp_pw) == NULL) {
		(void) fprintf(stderr, "%s: error reading '%s' around line %d\n",
		    progname, infile, lines_in);
		(void) ulckpwdf();
		return(FMERR);
	    }
	    p = buf;
	    while (*p && *p != '\n' && p < &buf[BUFSIZ])
		p++;
	    if (*p == '\n')
		*p = '\0';

	    if (f_flag) {
		made_changes = 1;
		if (v_flag) {
		    if (gave_name == 0) {
			gave_name = 1;
			(void) fprintf(stderr, "File: %s\n", infile);
		    }
		    (void) fprintf(stderr, "Line %d dropped: %s\n",
			lines_in, buf);
		}
		continue;
	    }

	    if (gave_name == 0) {
		gave_name = 1;
		(void) fprintf(stderr, "File: %s\n", infile);
	    }
loop:
	    (void) printf("error on line %d: %s\n", lines_in, buf);
	    for (;;) {
	        (void) printf("delete [y/n/q]: ");
	        (void) gets(ans);
	        if (ans[0] == 'Y' || ans[0] == 'y') {
		    made_changes = 1;
		    break;
	        } else if (ans[0] == 'Q' || ans[0] == 'q') {
		    for(;;) {
			(void) printf("Quit really? [y/n]: ");
			(void) gets(ans);
	        	if (ans[0] == 'Y' || ans[0] == 'y') {
			    (void) fclose(fp_pw);
			    (void) fclose(fp_temp);
			    return(SUCCESS);
			} else if (ans[0] == 'N' || ans[0] == 'n')
			    goto loop;
		    }
	        } else if (ans[0] == 'N' || ans[0] == 'n') {
			p = buf;
			while (*p)
			    p++;
			*p++ = '\n';
			*p = '\0';
			if (fputs(buf, fp_temp) == EOF) {
			    (void) fprintf(stderr, "%s: error writing to temp file %s\n",
				progname, tempfile);
			    (void) ulckpwdf();
			    return(FMERR);
			}
			lines_out++;
			break;
	        }
	    }
	    
	}	/* end of for loop */

	if (fclose(fp_pw) == EOF) {
	    (void) fclose(fp_temp);
	    (void) fprintf(stderr, "Error closing file %s\n", infile);
	    return(FMERR);
	}
	if (fclose(fp_temp)) {
	    (void) fprintf(stderr, "Error closing temp file %s\n", tempfile);
	    return(FMERR);
	}

	if (made_changes == 0) {
	    (void) unlink(tempfile);
	    if (v_flag)
		(void) printf("No lines in file %s changed.\n", infile);
	    return(SUCCESS);
	}

	if (unlink(oldfile) && (access(oldfile, 0) == 0)) {
	    (void) fprintf(stderr, "%s: error removing old file %s\n",
	        progname, oldfile);
	    (void) ulckpwdf();
	    return(FMERR);
	}

	if (link(infile, oldfile)) {
	    (void) fprintf(stderr, "%s: error linking %s to %s\n",
		progname, infile, oldfile);
	    (void) ulckpwdf();
	    return(FMERR);
	}

	if (unlink(infile)) {
	    (void) fprintf(stderr, "%s: error unlinking %s\n",
		progname, infile);
	    (void) ulckpwdf();
	    return(FMERR);
	}

	if (link(tempfile, infile)) {
	    if (link(oldfile, infile)) {
		(void) fprintf(stderr, "%s: error linking %s to %s\n",
		    progname, tempfile, infile);
		(void) fprintf(stderr, "%s: error linking %s to %s\n",
		    progname, oldfile, infile);
		(void) fprintf(stderr, "could not recover old file\n");
		(void) ulckpwdf();
		return(FMERR);
	    }
	    (void) fprintf(stderr, "%s: error linking %s to %s\n",
	        progname, tempfile, infile);
	    (void) fprintf(stderr, "recovered old file\n");
	    (void) ulckpwdf();
	    return(FMERR);
	}

	if (unlink(tempfile))
	    (void) fprintf(stderr, "%s: WARNING cannot unlink %s\n",
		progname, tempfile);

	if ( (f_flag && !q_flag) || v_flag)
	    (void) printf("%d lines deleted in file %s\n",
		lines_in - lines_out, infile);

	return(SUCCESS);
}
