[net.sources] Password File Checker

jay@telesoft.UUCP (Jay Schlegel @eldest) (06/28/85)

Greetings,
   Recently several articles have appeared in net.unix and/or net.unix-
wizards regarding grunged passwd files.  We've had a recurring problem of
trashed lines in /etc/passwd which would rampantly propagate eventually
filling the file system.  Here's a little C program that's inexpensive to
run from cron, and will warn of a grunged format as well as unapproved
root uid's.  The root entry is also checked for valid password and shell
fields (additional "verify()" calls may be added for any user).  If root's
default shell is other than "/bin/csh" it will be necessary to change
the "DEFSHELL" define.  If there are accounts other than root (as, in our
case, singleu) with "0" uid, it will be necessary to add that username to 
the test in "checkline()".

NOTE:  I've installed pcheck on 4.2BSD (VAX) and Sun 1.2 systems. Others
may (?) require some hacking.

NOTE:  Our local "alert" program is a little hack that sends a wall to
members of a specified group (wherever they may be logged in on our local
ethernet).  If you desire to invoke "pcheck -a" at regular intervals from
cron, change the define for "alert()" to pipe it's message to mail rather
than "/user/local/alert".

PS:  Please excuse the "printdots()" function.  I was just having fun!

Jay Schlegel  --  sdcsvax!telesoft!jay

----------------------- Cut Here ------------------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	pcheck.c
#	pcheck.8
# This archive created: Fri Jun 28 11:52:25 1985
export PATH; PATH=/bin:$PATH
if test -f 'pcheck.c'
then
	echo shar: will not over-write existing file "'pcheck.c'"
else
cat << \SHAR_EOF > 'pcheck.c'
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * pcheck.c
 *
 * Verifies correct format (6 ":" field delimiters) of /etc/passwd, verifies
 * valid passwd and default shell for root (and any other user by adding
 * a "verify() call"), and verifies that "root" and "singleu" are the only
 * accounts with uid "0".  If invoked with the "-a" switch (usually from
 * crontab), the program /user/local/alert is invoked if problems are found.
 * If run manually, pcheck reports it's findings to standard output.
 *
 * Author: Jay Schlegel  1/25/85
 */
#include <stdio.h>
#include <pwd.h>
#define TRUE 1
#define FALSE 0
#define MAXLINE 120		/* Buffer constraint 		*/
#define PWDLEN 13		/* Length of encrypted passwd 	*/
#define DEFSHELL "/bin/csh"	/* Default shell for verify()   */
#define alert() system("echo PASSWD FILE GRUNGED ON `hostname`, RUN PCHECK MANUALLY | /user/local/alert -g Wheel"); exit(2)
/* #define alert() system("echo PASSWD FILE GRUNGED ON `hostname`, RUN PCHECK MANUALLY | /usr/ucb/mail -s 'FIRE!!! GRUNGED PASSWD FILE' admin"); exit(2)
*/

int linecnt = 0;
int AlertAdm = FALSE;	/* for "-a" switch */

main(argc,argv)		/* pcheck -- verify format of /etc/passwd */
int	argc;
char	**argv;
{
	char buf[MAXLINE];
	char savbuf[MAXLINE];
	FILE *fp,*fopen();
	char *eofcheck;
	int dotcnt = 0;
	
	argv++;
	while (--argc > 0)  {		/* worlds simplest arg parser */
	   if(strcmp(*argv,"-a") == 0)  {
	      AlertAdm = TRUE;		/* invoke /user/local/alert */
	   }
	   argv++;
	}
	initbuf(savbuf); 

	if ((fp = fopen("/etc/passwd","r")) == NULL)  {
	   perror("Error opening /etc/passwd");
	   if (AlertAdm) {
	      alert();
	   } else {
	      exit(1);
	   }
	}
	while((eofcheck = fgets(buf,MAXLINE,fp)) != NULL)  {
	   printdots(&dotcnt);		/* print a dot for each line */
	   checkline(buf,savbuf,&dotcnt);
	   strcpy(savbuf,buf);		/* save the previous line */
	}
	printf("\n");
	printf("Number of lines: %d\n", linecnt);
	/* check passwd and shell fields for root */
	verify("root");
	printf("\nDone.\n");
	exit(0);
}

initbuf(bptr)		/* initialize buffer to nulls */
char	*bptr;
{
	char *ptr;
	for(ptr = bptr;ptr < bptr+MAXLINE;ptr++)  {
	   *ptr = NULL;
	}
}

printdots(cntp) 	/* print a dot for each line, count lines */
int	*cntp;		/* ptr to dotcnt			  */
{
	printf(".");
	linecnt++;
	++*cntp;
	if (*cntp == 75)  {
	   printf("\n");
	   *cntp = 0;
	}
}

checkline(bufp,sbufp,cntp)	/* insure 7 fields per entry, check for 0 uid */
char	*bufp;			/* ptr into current buf		*/
char	*sbufp;			/* ptr into previous buf 	*/
int	*cntp;			/* ptr to dotcnt			  */
{
	struct passwd *pwptr;
	char *ptr,*up;
	char uname[12];
	int  colon_cnt = 0;	/* counter for colons 		*/

	for(ptr = bufp;ptr < bufp+strlen(bufp);ptr++)  {
	   if (*ptr == ':')  {
	      colon_cnt++;
	   }
	}
	/* if colon_cnt != 6, format is grunged */
	if (colon_cnt != 6)  {
	   printf("\nFormat grunged, line %d\n", linecnt);
	   printf("Current line:\n %s\n", bufp);
	   printf("Previous line:\n %s\n", sbufp);
	   *cntp = 0;
	   if (AlertAdm)  {
	      alert();
	   }
	}
	/* check for bogus root uids */
	for(up=uname,ptr=bufp;*ptr!=':'&&ptr<(bufp+strlen(bufp));ptr++,up++)  {
	   *up = *ptr;
	}
	*up = '\0';
	pwptr = getpwnam(uname);
	if (strcmp(uname,"root")!=0 &&
	    strcmp(uname,"singleu")!=0)  {  /* add other 0 uid's to this test */
	   if (pwptr->pw_uid == 0)  {
	      printf("\nUNAPPROVED ROOT UID for user \'%s\'!!!\n",uname);
	      *cntp = 0;
	      if (AlertAdm)  {
	         alert();
	      }
	   }
	}
}

verify(uname)	/* verify default shell and char set for encrypted passwd */
char	uname[8];
{
	struct passwd *pwptr;
	char *pswp;		/* ptr into encrypted passwd */
	int i;

	if ((pwptr = getpwnam(uname)) == NULL)  {
	   printf("Couldn't find entry for %s\n",uname);
	   if (AlertAdm)
	      alert();  {
	   }
	}
	printf("\n%s entry:\n",uname);
	printf("PASSWD = %s\n",pwptr->pw_passwd);
	printf("UID = %d\n",pwptr->pw_uid);
	printf("GID = %d\n",pwptr->pw_gid);
	printf("GECOS = %s\n",pwptr->pw_gecos);
	printf("DIR = %s\n",pwptr->pw_dir);
	printf("SHELL = %s\n",pwptr->pw_shell);
	if (i = (strcmp(pwptr->pw_shell,DEFSHELL)) != 0)  {
	   printf("Default shell is not /bin/csh for %s\n",uname);
	   if (AlertAdm)  {
	      alert();
	   }
	}

	/* range check 'a-z,A-Z,0-9,.,/' for passwd char set */
        for (pswp = pwptr->pw_passwd; pswp < pwptr->pw_passwd + PWDLEN; pswp++)
	{
	   if( (('a' <= *pswp) && (*pswp <= 'z')) ||
	       (('A' <= *pswp) && (*pswp <= 'Z')) ||
	       (('0' <= *pswp) && (*pswp <= '9')) ||
	       ('.' == *pswp) ||
	       ('/' == *pswp)
	     )  {
	   } else {
	      printf("Invalid char '%c' in passwd for %s\n",*pswp,uname);
	      if (AlertAdm)  {
	         alert();
	      }
	   }
	}
}

SHAR_EOF
chmod +x 'pcheck.c'
fi # end of overwriting check
if test -f 'pcheck.8'
then
	echo shar: will not over-write existing file "'pcheck.8'"
else
cat << \SHAR_EOF > 'pcheck.8'
.ds ]W TeleSoft 
.TH PCHECK 1 "28 Jan 1985"
.SH NAME
pcheck \- check format of /etc/passwd, verify root default shell and passwd fields.
.SH SYNOPSIS
.B pcheck
[
.if t \fB\-a\fR 
.if n -a
]
.SH DESCRIPTION
.I Pcheck
verifies that each line of /etc/passwd contains 6 ':' field separators.
The root default shell field is verified as '/bin/csh', and
a character set check is performed on the encrypted password.
.I Pcheck
also checks for unapproved "0" uid entries.
Diagnostic information is displayed to standard output.
If invoked with the -a switch and an error is found,
.I pcheck
invokes alert (see alert(1)) to notify admin of the grunged passwd file. The
alert message includes the hostname of the problem machine. The -a switch is
typically only used when invoking pcheck from crontab.
.SH FUNCTION LETTERS
.TP 5
.B a
Alert admin that the passwd file is grunged.
.SH SEE ALSO
alert(1)
SHAR_EOF
chmod +x 'pcheck.8'
fi # end of overwriting check
#	End of shell archive
exit 0