#ifndef lint
static char rcsid[] = "$Id: dbase.build.c,v 1.32 1994/07/25 15:24:09 gkim Exp $";
#endif

/*
 * dbase.build.c
 *
 *	build the preen.database file with the list of files that
 *	was generated by config.parse.c
 *
 * Gene Kim
 * Purdue University
 * September 27, 1992
 */

#include "../include/config.h"
#include <stdio.h>
#ifdef STDLIBH
#include <stdlib.h>
#include <unistd.h>
#endif
#include <fcntl.h>
#if !defined(SYSV) || (SYSV > 3)
# include <sys/file.h>
#else
# include <unistd.h>
#endif 	/* SYSV */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef NOGETTIMEOFDAY
# include <sys/time.h>
#else
# include <time.h>
#endif 	/* NOGETTIMEOFDAY */
#ifdef DIRENT
# include <dirent.h>
#else
# ifndef XENIX
#  include <sys/dir.h>
# else		/* XENIX */
#  include <sys/ndir.h>
# endif		/* XENIX */
#endif	/* DIRENT */
#if (defined(SYSV) && (SYSV < 3))
# include <limits.h>
#endif	/* SVR2 */
#ifdef STRINGH
#include <string.h>
#else
#include <strings.h>
#endif
#include "../include/list.h"
#include "../include/tripwire.h"

#if defined(SYSV) && (SYSV < 4)
#ifndef HAVE_LSTAT
#  define lstat(x,y) stat(x,y)
#endif
#endif		/* SYSV */

#ifndef SEEK_SET
# define SEEK_SET L_SET
#endif

int files_scanned_num = 0;

/* prototypes */
char *mktemp();
static void database_record_write();

char backupfile[MAXPATHLEN+256];

/*
 * database_build(struct list **pp_list, int tempdatabaseflag)
 *
 *	take the list of file elements from the list and store all of
 *	the pertinent inode and signature information in the database
 *	file.
 *
 *	if (tempdatabaseflag) is set, then we write to a private file
 *	in the /tmp directory.
 */

extern int numinterupdated;

void
database_build (pp_list, mode, pp_entry_list)
    struct list **pp_list;
    int mode;
    struct list **pp_entry_list;
{
    struct list_elem *p_fileentry;
    struct list_elem *p_configentry;
    FILE *fpw;
    char database[MAXPATHLEN+256];
    int entrynum,
        oldumask;
    extern int  errno;


#ifndef NOGETTIMEOFDAY
    struct timezone tzone;
    struct timeval tval;
#else
    time_t tval;
#endif	/* XENIX */

    if (!quietmode) {
	fprintf(stderr, "### Phase 3:   %s file information database\n",
		mode == DBASE_UPDATE ? "Updating" : "Creating");
    }


    /* create the database file
     * 		if we are making the permanent database, then we write
     *		to the specified file.
     *
     *		else, we create a temporary file, and save the name of it.
     */

    /* XXX - we should use open() so we can set the modes */

    oldumask = umask(077);

    /* where do we write the new database? */
    if (mode == DBASE_TEMPORARY) {
	char *tmpfilename = (char *) malloc(strlen(TEMPFILE_TEMPLATE)+1);
	if (tmpfilename == NULL)
	    die_with_err("malloc() failed in database_build", (char *) NULL);
	(void) strcpy(tmpfilename, TEMPFILE_TEMPLATE);

	if ((char *) mktemp(tmpfilename) == NULL)
	    die_with_err("database_build: mktemp()", (char *) NULL);

	(void) strcpy(tempdatabase_file, tmpfilename);
	(void) strcpy(database, tempdatabase_file);
	free(tmpfilename);
    }					/* end if temporary database */
    else if (mode == DBASE_UPDATE) {
	sprintf(database, "./databases/%s", database_file);
    }					/* end if update mode */
    else {			
	sprintf(database, "%s/%s", database_path, database_file);
    }					/* end if non-temporary database */

    /* back up any existing database */
    if (mode == DBASE_UPDATE) {
	FILE *fpin, *fpout;
	char backup[MAXPATHLEN+256];
	char olddatabase[MAXPATHLEN+256];
        register int ctemp;

	/* get a file descriptor to the old database, so we can open it 
	 * and make a backup copy
	 */
	if (specified_dbasemode) {
	    if (!(fpin = (FILE *) fdopen(specified_dbasefd, "r"))) {
		die_with_err("database_build: Couldn't open database `%s':", 
			    olddatabase);
	    }
	    rewind(fpin);
	    if (ftell(fpin) != 0) {
	        die_with_err("database_build: ftell()", NULL);
	    }

	    if (specified_dbasemode == SPECIFIED_FILE) 
		strcpy(olddatabase, specified_dbasefile);

	} else {
	    /* what should we call the old database? */
	    sprintf(olddatabase, "%s/%s", database_path, database_file);

	    if ((fpin = fopen(olddatabase, "r")) == NULL) {
		die_with_err("database_build: Couldn't open database `%s':", 
			    olddatabase);
	    }
	}

	/* we hard code this, since we always want the new diretory placed
	 * relative to our position 
	 */
	sprintf(backupfile, "%s.old", database_file);

	/* make sure we stay underneath maximum file length */

	if ((int)(strlen(database_file) + 4) > MAXNAMLEN) {

	    /* tack on .old as well as it fits */
	    (void) strcpy(backup + (MAXNAMLEN - 4), ".old");
	}
	/* so we can reference it later */
	(void) sprintf(backup, "./databases/%s", backupfile);
	/* (void) strcpy(backupfile, backup);  strlen(./Databases/) == 12 */

SPDEBUG(3) 
printf("database_build(): ---> olddatabase = (%s)\n", olddatabase);

	if ((fpout = fopen(backup, "w")) == NULL)
	    die_with_err("Couldn't open '%s'!\n", backup);

	/* make the backup file */
	while ((ctemp = getc(fpin)) != EOF)
	    putc((char) ctemp, fpout);

	if (!specified_dbasemode)
	    (void) fclose(fpin);
	(void) fclose(fpout);

	/* print banner (in case user stops program during database update) */
	if (!quietmode) {
	    fputs("###\n", stderr);
	    fprintf(stderr,
"### Old database file will be moved to `%s'\n", backupfile);
	    fputs("###            in ./databases.\n", stderr);
	    fputs("###\n", stderr);
	    fprintf(stderr, 
"### Updated database will be stored in '%s'\n", database);
	    fprintf(stderr,
"###            (Tripwire expects it to be moved to '%s'.)\n", database_path);
	    fputs("###\n", stderr);

	}
    }

    /* rebuild the database */
    if ((fpw = fopen(database, "w")) == NULL)
	die_with_err("Hint: Maybe the database directory '%s' doesn't exist?  fopen()", database);

    (void) umask(oldumask);


    /* get time information for banner */

#ifndef NOGETTIMEOFDAY
    if (gettimeofday(&tval, &tzone) < 0)
        die_with_err("gettimeofday()", (char *) NULL);
#else
    tval = time((time_t *) 0);
#endif	/* XENIX */


    /* add a banner to the top of the database file */
    /*		note that the newline comes from date  */
    {
	char timestring[30];
	strncpy(timestring, ctime((time_t *)&tval), 26);
	fprintf(fpw, "# Generated by Tripwire, version %s on %s",
				    version_num, timestring);
	fprintf(fpw, "@@dbaseversion %d\n", db_version_num);
    }

    /* we use &filelist as the key */
    if (list_open(pp_list) < 0)
	die_with_err("database_build: list_open() failed!\n", (char *) NULL);

    while ((p_fileentry = list_get(pp_list)) != NULL) {

	struct stat statbuf;
	char filename[2048], ignorevec[512];


	/*
	 * if we're in UPDATE mode, we simply copy entries unless
	 * FLAG_UPDATE is set.
	 */

	if (mode == DBASE_UPDATE) {

	    int flagval;

	    flagval = list_getflag(p_fileentry->varname, pp_list);
	    if (!(flagval & FLAG_UPDATE)) {
		fprintf(fpw, "%s %s", filename_escape(p_fileentry->varname),
						p_fileentry->varvalue);
SPDEBUG(10)
printf("database_build(): --(dumping, flag=%d)--> %s\n", flagval, p_fileentry->varname);
		continue;
	    }
	    else {
SPDEBUG(10)
printf("database_build(): --(will update, flag=%d)--> %s\n", flagval, p_fileentry->varname);
	    }
	}

	/* get the stat information on it */
	strcpy(filename, p_fileentry->varname);

	if (sscanf(p_fileentry->varvalue, "%d %s", &entrynum, ignorevec) != 2)
           die_with_err("database_build: sscanf() parsing error!\n",
						(char *) NULL);

	if (lstat(filename, &statbuf) < 0) {
	    if (errno == ENOENT) {
		fprintf(stderr,
		    "%s: %s: disappeared.  Skipping...\n", progname, filename);
		continue;
	    }
	    else
	      die_with_err("database_build: lstat()", filename);
	}

	/* pick up NO_OPEN flag if we're in UPDATE mode
	 *
	 * if it is a special file or device, add it to the list, but
	 * make sure we don't open it and read from it!
	 */
	if (mode == DBASE_UPDATE)
	    switch (statbuf.st_mode & S_IFMT) {
	      case S_IFIFO:
	      case S_IFCHR:
	      case S_IFDIR:
	      case S_IFBLK:
#if !defined(SYSV) || (SYSV > 3)
#ifndef apollo
/* Foolish Apollos define S_IFSOCK same as S_IFIFO in /bsd4.3/usr/include/sys/stat.h */
	      case S_IFSOCK:
#endif
#endif
		(void) list_setflag(filename, FLAG_NOOPEN, pp_list);
		break;
#if !defined(SYSV) || (SYSV > 3)
	      case S_IFLNK:	/* if it's a symbolic link, make sure we flag it as such! */
		(void) list_setflag(filename, FLAG_SYMLINK, pp_list);
		break;
#endif
	    }

	database_record_write(fpw, filename, p_fileentry->flag, ignorevec,
					&statbuf, entrynum);

	files_scanned_num++;
    }					/* end while list_read() */

    /* cleanup */
    if (list_close(pp_list) < 0)
      die_with_err("database_build: list_close() failed!\n", (char *) NULL);

    /* print out table of contents in permanent database */
    if (mode != DBASE_TEMPORARY) {
	/* we use &pp_entry_list as the key */
	if (list_open(pp_entry_list) < 0)
	  die_with_err("database_build: list_open() failed!\n", (char *) NULL);

	/* print out the contents */
	while ((p_configentry = list_get(pp_entry_list)) != NULL) {
	    char entry[2048];
	    int err;
	    if ((err = sscanf(p_configentry->varvalue, "%s", entry)) != 1) {
		fprintf(stderr, "database_build: parse error (nfields=%d)!\n", err);
		fprintf(stderr, ">> %s\n", p_configentry->varvalue);
		exit(1);

	    }
	    /* skip those reverse index entries */
	    if (p_configentry->flag)
		continue;

	    fprintf(fpw, "@@contents %s %s\n", filename_escape(p_configentry->varname),
				entry);
SPDEBUG(10) 
printf("--(contents)-->%s\n", entry); 
	}

	/* close the list */
	if (list_close(pp_entry_list) < 0)
	  die_with_err("database_build: list_close() failed!\n", (char *) NULL);
    }

    /* we don't want to allow anyone to spoof the temporary file in /tmp */
    if (mode == DBASE_TEMPORARY) {
	if ((fptempdbase = freopen(database, "r", fpw)) == NULL)
	    die_with_err("temporary database file disappeared?!?", database);
	rewind(fptempdbase);
    } else {
	fclose(fpw);
    }

    return;
}

/*
 * database_record_write(FILE *fpw, char *filename, int flags,
 *                              char *ignorevec, struct stat *statbuf,
 *				int entrynum)
 *
 * 	write out the pertinent information of the specifed file to the
 *	database.
 *
 * 	gather the signatures, and include that in the info going to
 *		to the database.
 *
 *	(entrynum) is the unique entry number tag from tw.config.
 */

static void
database_record_write (fpw, filename, flags, ignorevec, statbuf, entrynum)
    FILE *fpw;
    char *filename;
    int flags;
    char *ignorevec;
    struct stat *statbuf;
    int entrynum;
{
    static int fdsymlink = -1;		/* to store contents of readlink() */
    char 	sigs[NUM_SIGS][SIG_MAX_LEN];
    int		fd, i;
    int		ignoremask;
    char	vec64_a[50];
    char	vec64_c[50];
    char	vec64_m[50];
    char	sigs_concat[NUM_SIGS * SIG_MAX_LEN];
    /* filename, entrynum, ignore, mode, inode, nlinks, uid, gid, size,
     *		access, modify, ctime, {sig0, sig1, ..., sig9}
     */
    static char *format = "%s %ld %s %lo %ld %ld %ld %ld %ld %s %s %s %s\n";

    /* initialize our temporary file */
    if (fdsymlink == -1) {
	fdsymlink = fd_tempfilename_generate();
    }

    if (verbosity) {
	fprintf(stderr, "scanning: %s\n", filename);
    }

    /*
     * check for NOOPEN flag (for special files that shouldn't be
     * read from, like devices); we make up null signatures.
     */
    if (flags & FLAG_NOOPEN) {
	for (i = 0; i < NUM_SIGS; i++) {
	    register char *pc = sigs[i];
	    *pc++ = '0';
	    *pc++ = ' ';
	    *pc++ = '\0';
	}
	goto SKIPPED_SIGS;
    }
    /*
     * New for Tripwire v1.1.1:  (adapted from Paul Szabo and Spaf)
     *
     * We open up symbolic links and store its signature in the database
     * Because I'm lazy, I open it, write it out to a temporary file
     * and feed it to the normal signature generation routines.
     */
#if !defined(SYSV) || (SYSV > 3)
    else if (flags & FLAG_SYMLINK) {
	char linkcontents[MAXPATHLEN+256];
	int err, slen;

	if ((err = readlink(filename, linkcontents, 
					sizeof(linkcontents))) < 0) {
	    warn_with_err("couldn't read symbolic link for '%s'", filename);
	    return;
	}
	/* Ensure null termination (may already have truncated string) */
	/*
	* Some versions of readlink return null-terminated strings
	* (e.g. Apollo SR10.4) (but only if there is room); 
	* some other versions do not bother (e.g. Apollo SR10.2).
	*/     
	if (err < sizeof(linkcontents))
	    linkcontents[err] = '\0';
	else     
	    linkcontents[sizeof(linkcontents)-1] = '\0';
	slen = strlen(linkcontents);

	if (ftruncate(fdsymlink, 0) < 0) {
	    die_with_err("truncate()", NULL);
	}           
	if (lseek(fdsymlink, 0, SEEK_SET) < 0) {
	    die_with_err("lseek()", NULL);
	}
	if ((err = write(fdsymlink, linkcontents, slen)) != slen) {
	    warn_with_err("couldn't write symbolic link info for '%s'", 
			filename);
	    return;
	}
    }
    else {
	/* descriptor for signature functions */
	if ((fd = open(filename, O_RDONLY)) < 0) {
	    /* skip it if we had an error */
	    warn_with_err("Trying to open %s for signature", filename);
	    return;
	}
    }
#else
    /* descriptor for signature functions */
    if ((fd = open(filename, O_RDONLY)) < 0) {
	/* skip it if we had an error */
	warn_with_err("Trying to open %s for signature", filename);
	return;
    }
#endif

    /* first find out which signatures we don't need to collect */
    ignoremask = ignore_vec_to_scalar(ignorevec);

    /* collect signatures */
    for (i = 0; i < NUM_SIGS; i++) {
	char *pc = sigs[i];

	/* do we skip this signature? */
	if ((ignoremask & (IGNORE_0 << i)) || (runtimeignore & (IGNORE_0 << i)))
	    (void) strcpy(pc, "0 ");
	else {
	    /* special file descriptor for those symbolic links */
	    if (flags & FLAG_SYMLINK) 
		(*pf_signatures[i])(fdsymlink, pc, SIG_MAX_LEN);
	    else
		(*pf_signatures[i])(fd, pc, SIG_MAX_LEN);
	    (void) strcat(pc, " ");
	}
    }

    /* close up the descriptor, since we're done */
    if (!(flags & FLAG_SYMLINK))
	(void) close(fd);

SKIPPED_SIGS:

    /* concatenate all the signature */
    sigs_concat[0] = '\0';
    for (i = 0; i < NUM_SIGS; i++)
	strcat(sigs_concat, sigs[i]);

    /* filename, ignore, mode, inode, nlinks, uid, gid, size, access, modify,
     * 		ctime, sig0, sig1, ..., sig9
     */

    SPDEBUG(6) printf("--(database_record_write)--> %s\n", filename);


    {
	time_t va = statbuf->st_atime,
		 vm = statbuf->st_mtime,
		 vc = statbuf->st_ctime;

	fprintf(fpw, format, filename_escape(filename), (int32)entrynum, ignorevec,
	    (int32)statbuf->st_mode, (int32)statbuf->st_ino,
	    (int32)statbuf->st_nlink, (int32)statbuf->st_uid,
	    (int32)statbuf->st_gid, (int32)statbuf->st_size,
	    pltob64((uint32 *) &va, (char *) vec64_a, 1),
	    pltob64((uint32 *) &vm, (char *) vec64_m, 1),
	    pltob64((uint32 *) &vc, (char *) vec64_c, 1),
	    sigs_concat);
    }

    return;

}
