/*	@(#)wtmpfix.c	2.1	 */
#

/*
 * wtmpfix - adjust wtmp file and remove date changes.
 *
 *	wtmpfix <wtmp1 >wtmp2
 *
 */

# include <stdio.h>
# include <ctype.h>
# include <sys/types.h>
# include <utmp.h>
# include "acctdef.h"
# include <signal.h>

FILE	*Wtmp, *Opw;

char	*Ofile	={ "/tmp/wXXXXXX" };

struct	dtab
{
	long	d_off1;		/* file offset start */
	long	d_off2;		/* file offset stop */
	long	d_adj;		/* time adjustment */
	struct dtab *d_ndp;	/* next record */
};

struct	dtab	*Fdp;		/* list header */
struct	dtab	*Ldp;		/* list trailer */


long	ftell();
struct	utmp	wrec, wrec2;

intr()
{

	signal(SIGINT,SIG_IGN);
	unlink(Ofile);
	exit(1);
}

main(argc, argv)
char	**argv;
{

	static long	recno = 0;
	register struct dtab *dp;

	if(argc < 2){
		argv[argc] = "-";
		argc++;
	}

	if((int)signal(SIGINT,intr) == -1) {
		perror("signal");
		exit(1);
	}

	mktemp(Ofile);
	if((Opw=fopen(Ofile,"w"))==NULL)
		err("cannot make temporary: %s", Ofile);

	while(--argc > 0){
		argv++;
		if(strcmp(*argv,"-")==0)
			Wtmp = stdin;
		else if((Wtmp = fopen(*argv,"r"))==NULL)
			err("Cannot open: %s", *argv);
		while(winp(Wtmp,&wrec)){
			if(recno == 0 || wrec.ut_type==BOOT_TIME){
				mkdtab(recno,&wrec);
			}
			if(invalid(wrec.ut_name)) {
				fprintf(stderr,
					"wtmpfix: logname \"%8.8s\" changed to \"INVALID\"\n",
					wrec.ut_name);
				strncpy(wrec.ut_name, "INVALID", NSZ);
			}
			if(wrec.ut_type==OLD_TIME){
				if(!winp(Wtmp,&wrec2))
					err("Input truncated at offset %ld",recno);
				if(wrec2.ut_type!=NEW_TIME)
					err("New date expected at offset %ld",recno);
				setdtab(recno,&wrec,&wrec2);
				recno += (2 * sizeof(struct utmp));
				wout(Opw,&wrec);
				wout(Opw,&wrec2);
				continue;
			}
			wout(Opw,&wrec);
			recno += sizeof(struct utmp);
		}
		if(Wtmp!=stdin)
			fclose(Wtmp);
	}
	fclose(Opw);
	if((Opw=fopen(Ofile,"r"))==NULL)
		err("Cannot read from temp: %s", Ofile);
	recno = 0;
	while(winp(Opw,&wrec)){
		adjust(recno,&wrec);
		recno += sizeof(struct utmp);
/*		if(wrec.ut_type==OLD_TIME || wrec.ut_type==NEW_TIME)
			continue;	*/
		wout(stdout,&wrec);
	}
	fclose(Opw);
	unlink(Ofile);
	exit(0);
}

/*	err() writes an error message to the standard error and then
 *	calls the interrupt routine to clean up the temporary file
 *	and exit.  The variable "f" is the format specification that
 *	can contain up to 3 arguments (m1, m2, m3).
 */
err(f,m1,m2,m3)
{

	fprintf(stderr,f,m1,m2,m3);
	fprintf(stderr,"\n");
	intr();
}

/*	winp() reads a record from a utmp.h-type file pointed to
 *	by the stream pointer "f" into the structure whose address
 *	is given by the variable "w".
 *	This reading takes place in two stages: first the raw
 *	records from the utmp.h files are read in (usually /etc/wtmp)
 *	and written into the temporary file (Opw); then the records
 *	are read from the temporary file and placed on the standard 
 *	output.
 */
winp(f,w)
register FILE *f;
register struct utmp *w;
{

	if(fread(w,sizeof(struct utmp),1,f)!=1)
		return 0;
	if((w->ut_type >= EMPTY) && (w->ut_type <= UTMAXTYPE))
		return ((unsigned)w);
	else {
		fprintf(stderr,"Bad file at offset %ld\n",
			ftell(f)-sizeof(struct utmp));
		fprintf(stderr,"%-12s %-8s %lu %s",
			w->ut_line,w->ut_user,w->ut_time,ctime(&w->ut_time));
		intr();
	}
}

/*	wout() writes an output record of type utmp.h.  The
 *	variable "f" is a file descripter of either the temp
 *	file or the standard output.  The variable "w" is an
 *	address of the entry in the utmp.h structure.
 */
wout(f,w)
{

	fwrite(w,sizeof(struct utmp),1,f);
}

mkdtab(p,w)
long	p;
register struct utmp *w;
{

	register struct dtab *dp;

	dp = Ldp;
	if(dp == NULL){
		dp = (struct dtab *)calloc(sizeof(struct dtab),1);
		if(dp == NULL)
			err("out of core");
		Fdp = Ldp = dp;
	}
	dp->d_off1 = p;
}

setdtab(p,w1,w2)
long	p;
register struct utmp *w1, *w2;
{

	register struct dtab *dp;

	if((dp=Ldp)==NULL)
		err("no dtab");
	dp->d_off2 = p;
	dp->d_adj = w2->ut_time - w1->ut_time;
	if((Ldp=(struct dtab *)calloc(sizeof(struct dtab),1))==NULL)
		err("out of core");
	Ldp->d_off1 = dp->d_off1;
	dp->d_ndp = Ldp;
}

adjust(p,w)
long	p;
register struct utmp *w;
{

	long pp;
	register struct dtab *dp;

	pp = p;

	for(dp=Fdp;dp!=NULL;dp=dp->d_ndp){
		if(dp->d_adj==0)
			continue;
		if(pp>=dp->d_off1 && pp < dp->d_off2)
			w->ut_time += dp->d_adj;
	}
}

/*
 *	invalid() determines whether the name field adheres to
 *	the criteria set forth in acctcon1.  If the name violates
 *	conventions, it returns a truth value meaning the name is
 *	invalid; if the name is okay, it returns false indicating
 *	the name is not invalid.
 */
#define	VALID	0
#define	INVALID	1

invalid(name)
char	*name;
{
	register int	i;

	for(i=0; i<NSZ; i++) {
		if(name[i] == '\0')
			return(VALID);
		if( ! (isalnum(name[i]) || (name[i] == '$')
			|| (name[i] == ' ') )) {
			return(INVALID);
		}
	}
	return(VALID);
}
