/*
 * $Copyright:	$
 * Copyright (c) 1984, 1985, 1986, 1987, 1988, 1989, 1990 
 * Sequent Computer Systems, Inc.   All rights reserved.
 *  
 * This software is furnished under a license and may be used
 * only in accordance with the terms of that license and with the
 * inclusion of the above copyright notice.   This software may not
 * be provided or otherwise made available to, or used by, any
 * other person.  No title to or ownership of the software is
 * hereby transferred."
 *
 */
/*	Copyright (c) 1984 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

/*
 * Copyright  (c) 1985 AT&T
 *	All Rights Reserved
 */

#ident	"$Header: detect.c 1.2 89/10/10 $@(#)vm.oh:src/oh/detect.c	1.2"

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <ctype.h>
#include "tsys.h"
#include "var_arrays.h"
#include "typetab.h"
#include "detabdefs.h"
#include "partabdefs.h"
#include "optabdefs.h"
#include "parse.h"

#define ACC_NOTSET	1
#define ACC_OKREAD	0
#define ACC_NOREAD	-1
#define ck_readable(X)	(access(X, 4))

#define PGSHFT	64		/* kludge to detect core files */
#define LOOKED_AT_OEH	1
#define LOOKED_AT_BYTES	2

extern struct one_part Parts[MAXPARTS];
extern struct odft_entry Detab[MAXODFT];

static int Seen_non_printable;
static int Seen_eighth_bit;
static int Already_looked;
static struct oeh Oeh;

int Pathlen;

#ifndef WISH
void det_mail_in(), det_mail_out();
#endif
struct opt_entry *obj_to_parts();

/* The heuristics program drives off of the detection table (defined in
 * detab.c).  It cycles through this table, executing heuristics commands
 * as it goes.  There are basically 4 kinds of heuristics:
 *
 * Heuristics based on object part names
 * Heuristics based on magic numbers
 * Heuristics based on user-defined functions
 * Heuristics based on internal functions
 *
 * The most efficient method is part-names, the least efficient is
 * user-defined functions since they require a fork().
 * For this reason, it is probably best for user-defined functions to come
 * last if possible.
 */

int
heuristics(path, stray)
char *path;
char stray[][OLDIRSIZ+1];
{
	struct stat sbuf;
	char buf[2048];		/* xed header size */
	register int i;
	int psize = strlen(path) + 1;
	int size = array_len(stray);
	long docmask = 0L;
	char pathbuf[PATHSIZ];
	bool is_directory, determined;
	int heur;
	int accessible;

	strcpy(pathbuf, path);
	strcat(pathbuf, "/");
	Pathlen = psize;

	for (i = 0; i < size; i++) {
		if (stray[i][0] == '\0')
			continue;		/* already determined by other heuristics */
		if (psize + strlen(stray[i]) + 3 > PATHSIZ)	/* 3: "/" & prefixes */
			continue;	/* ignore - path too big */
		strcpy(pathbuf+psize, stray[i]);
		if (stat(pathbuf, &sbuf) == -1) {
#ifdef _DEBUG
			_debug(stderr, "can't stat %s\n", pathbuf);
#endif
				continue;
		}

		/* MUST be a directory to be check for exist_heuristics;
		 * Directories will ONLY be checked for exist_heuristics, part_-
		 * heuristics and shell and exec functions. (No magic or internal
		 * (oeu,ascii,core,archive,mailin/out) functions will be run.)
		 */
		if (sbuf.st_mode & 040000)
			is_directory = TRUE;
		else
			is_directory = FALSE;

		if ( sbuf.st_mode & 04000 )		/* narrow screen file */
			docmask = M_NAR;
		else
			docmask = 0L;

		determined = FALSE;
		accessible = ACC_NOTSET;
		Already_looked = 0;
		for (heur = 0; !determined && Detab[heur].objtype[0]; heur++) {
			switch (Detab[heur].func_type) {
			case F_DPARTS:
				if (is_directory == FALSE)
					continue;
				if (exist_heuristics(path, stray[i], Detab[heur].objtype,
					Detab[heur].defmask, Detab[heur].defodi, sbuf.st_mtime))
					determined = TRUE;
				break;
			case F_PARTS:
				if (part_heuristics(path, stray, i, Detab[heur].objtype,
					Detab[heur].defmask, Detab[heur].defodi, sbuf.st_mtime,
					NULL))
					determined = TRUE;
				break;
			case F_MAGIC:
				if (is_directory == TRUE)
					continue;
				if (accessible == ACC_NOTSET)
					accessible = ck_readable(pathbuf);
				if (accessible == ACC_NOREAD)
					break;
				if (magic_heuristics(path, stray[i], Detab[heur].objtype,
					Detab[heur].defmask, Detab[heur].defodi, sbuf.st_mtime,
					Detab[heur].magic_offset, Detab[heur].magic_bytes))
					determined = TRUE;
				break;
			case F_SHELL:
			case F_EXEC:
				if (external_heuristics(path, stray[i], Detab[heur].defmask,
					Detab[heur].defodi, sbuf.st_mtime, NULL))
					determined = TRUE;
				break;
			case F_INT:
				if (is_directory == TRUE)
					continue;
				switch (Detab[heur].intern_func) {
				case IDF_ZLASC:  /* zero length ascii */
					if (sbuf.st_size == 0) {
						/* file pathsize already tested at top of this fcn */
						ott_make_entry(stray[i], stray[i], Detab[heur].objtype,
							docmask|Detab[heur].defmask, Detab[heur].defodi,
							sbuf.st_mtime);
						stray[i][0] = '\0';
						determined = TRUE;
					}
					break;
				case IDF_ASC:
					if (accessible == ACC_NOTSET)
						accessible = ck_readable(pathbuf);
					if (accessible == ACC_NOREAD)
						break;
					look_at_bytes(path, stray[i]);
					if (! Seen_non_printable) {
						/* file pathsize already tested at top of this fcn */
						ott_make_entry(stray[i], stray[i], Detab[heur].objtype,
							docmask|Detab[heur].defmask, Detab[heur].defodi,
							sbuf.st_mtime);
						stray[i][0] = '\0';
						determined = TRUE;
					}
					break;
				case IDF_TRANS:
					if (accessible == ACC_NOTSET)
						accessible = ck_readable(pathbuf);
					if (accessible == ACC_NOREAD)
						break;
					if (oeu_heuristics(path, stray[i], Detab[heur].objtype,
							Detab[heur].defmask, Detab[heur].defodi, 
							sbuf.st_mtime)) {
						determined = TRUE;
					}
					break;
				case IDF_CORE:
					/* if a file is named "core" and it is at least 3 pages long
					 * and it is an even multiple of a page size, and it has at
					 * least one byte within the first five hundred with the
					 * eighth bit set, then it is probably a core file.
					 */
					if (accessible == ACC_NOTSET)
						accessible = ck_readable(pathbuf);
					if (accessible == ACC_NOREAD)
						break;
					look_at_bytes(path, stray[i]);
					if (strcmp(stray[i], "core") == 0 && Seen_non_printable &&
						 sbuf.st_size >= (1<<PGSHFT)*3 && ! (sbuf.st_size % (1<<PGSHFT) )) {
						/* file pathsize already tested at top of this fcn */
						ott_make_entry(stray[i],stray[i],Detab[heur].objtype,
							Detab[heur].defmask,Detab[heur].defodi,
							sbuf.st_mtime);
						stray[i][0] = '\0';
						determined = TRUE;
					}
					break;
				case IDF_ARCH:
					if (accessible == ACC_NOTSET)
						accessible = ck_readable(pathbuf);
					if (accessible == ACC_NOREAD)
						break;
					look_at_bytes(path, stray[i]);
					if (Seen_non_printable && has_suffix(stray[i], ".a") &&
							strncmp(buf, "!<arch>", 7) == 0) {
						/* file pathsize already tested at top of this fcn */
						ott_make_entry(stray[i], stray[i], Detab[heur].objtype,
							Detab[heur].defmask,Detab[heur].defodi,
							sbuf.st_mtime);
						stray[i][0] = '\0';
						determined = TRUE;
					}
					break;
				case IDF_ENCRYPT:
					if (accessible == ACC_NOTSET)
						accessible = ck_readable(pathbuf);
					if (accessible == ACC_NOREAD)
						break;
					if (oeu_heuristics(path, stray[i], NULL,
							Detab[heur].defmask, Detab[heur].defodi, 
							sbuf.st_mtime)) {
						determined = TRUE;
					}
					break;
				case IDF_UNKNOWN:
					/* file pathsize already tested at top of this fcn */
					ott_make_entry(stray[i], stray[i], Detab[heur].objtype,
						Detab[heur].defmask, Detab[heur].defodi, sbuf.st_mtime);
					stray[i][0] = '\0';
					determined = TRUE;
					break;
#ifndef WISH
				case IDF_MAIL_IN:
					if (part_heuristics(path, stray, i, Detab[heur].objtype,
							Detab[heur].defmask, Detab[heur].defodi, 
							sbuf.st_mtime, det_mail_in)) {
						determined = TRUE;
					}
					break;
				case IDF_MAIL_OUT:
					if (part_heuristics(path, stray, i, Detab[heur].objtype,
							Detab[heur].defmask, Detab[heur].defodi, 
							sbuf.st_mtime, det_mail_out)) {
						determined = TRUE;
					}
					break;
#endif
#ifdef _DEBUG
				default:
					_debug(stderr, "no such func: %d\n", Detab[heur].intern_func);
#endif
				}
			}
		}
	}
	return(0);
}

static bool
exist_heuristics(path, name, objtype, mask, odi, mtime)
char *path, *name, *objtype;
long mask;
char *odi;
long mtime;
{
	register int i;
	struct opt_entry *partab;
	int part_offset, numparts;
	char *base;
	char *pattern;
	char *part_construct();
	int	found[MAXOBJPARTS];
	char pathbuf[PATHSIZ];
	char *part_match();

	/* get the parts table associated with objtype */

	if ((partab = obj_to_parts(objtype)) == NULL)
		return(FALSE);
	part_offset = partab->part_offset;
	numparts = partab->numparts;

	if ((base = part_match(name, Parts[part_offset].part_template)) == NULL)
		return(FALSE);

	found[0] = 1;
	for (i = 1; i < numparts; i++)
		found[i] = -1;

	for (i = 1; i < numparts; i++) {
		pattern = part_construct(base, Parts[part_offset+i].part_template);
		/* if any part's path is > PATHSIZ, do not display it */
		if (strlen(pattern) + Pathlen + 3 > PATHSIZ)
			return(FALSE);
		sprintf(pathbuf, "%s/%s", path, pattern);
		if (access(pathbuf, 0) == -1) {		/* exists ? */
			if (!(Parts[part_offset+i].part_flags & PRT_OPT))
				return(FALSE);
		} else {
			found[i] = 1;
		}
	}
	/* file pathsize already tested in heuristics() - this uses "name" */
	ott_make_entry(name, base, objtype, mask|partab->int_class, odi, mtime);

	for (i = 1; i < numparts; i++) {
		if (found[i] == 1)
			/* file pathsize already tested when each part found */
			ott_make_entry(part_construct(base, 
				Parts[part_offset+i].part_template), 
				NULL, NULL, mask|partab->int_class, NULL, mtime);
	}

	return(TRUE);
}

static bool
part_heuristics(path, stray, index, objtype, mask, odi, mtime, info_func)
char *path;
char stray[][OLDIRSIZ+1];
char *objtype;
int index;
long mask;
char *odi;
long mtime;
void (*info_func)();
{
	register int i, j;
	int	found[MAXOBJPARTS];
	struct opt_entry *partab;
	int part_offset, numparts;
	int size = array_len(stray);
	char *p, base[PNAMESIZ];
	char fullpath[PATHSIZ];
	char *dname;
	char *part_match();

	/* get the parts table associated with objtype */

	if ((partab = obj_to_parts(objtype)) == NULL)
		return(FALSE);
	part_offset = partab->part_offset;
	numparts = partab->numparts;

	for (i = 0; i < numparts; i++)
		found[i] = -1;

	/* look for the entry index in the table, in reverse order since
	 * the more restrictive names are at the end (for example, the first
	 * parts template is often unrestricted).
	 */

	for (i = numparts-1; i >= 0; i--)
		if (p = part_match(stray[index], Parts[part_offset+i].part_template)) {
			found[i] = index;
			strcpy(base, p);
			break;
		}

	if (!p)				/* was not found */
		return(FALSE);

	/* if any part's path is > PATHSIZ, do not display it */
	if ((found[i] != -1) && (strlen(stray[found[i]]) + Pathlen + 3 > PATHSIZ))
		return(FALSE);

	/* scan through the rest of the parts, looking in the stray
	 * array for each one.  If a required part is ever not found,
	 * or if the name is > PATHSIZ,
	 * then immediately return FALSE.
	 */

	for (i = 0; i < numparts; i++) {
		/* don't look for an already found part */
		if (found[i] != -1)
			continue;
		for (j = 0; j < size; j++) {
			if (stray[j][0] == '\0' || j == index)
				continue;
			if ((p=part_match(stray[j], Parts[part_offset+i].part_template)) &&
					strcmp(p, base) == 0) {
				found[i] = j;
				break;
			}
		}

		/* if a required part is not found, then return FALSE */

		if (found[i] == -1 && !(Parts[part_offset+i].part_flags & PRT_OPT))
			return(FALSE);
		/* if any part's path is > PATHSIZ, do not display it */
		if ((found[i] != -1) && (strlen(stray[found[i]]) + Pathlen + 3 > PATHSIZ))
			return(FALSE);
	}

	/* at this point, we should have all the parts, so we will go
	 * through the found array and make entries for each part.
	 */

	j = 0;
	while (found[j] == -1)
		j++;

	if (info_func != NULL) {
		strcpy(fullpath, path);
		strcat(fullpath, "/");
		strcat(fullpath, stray[found[j]]);
		(*info_func)(fullpath, &dname, &odi, &mask, &mtime);
	} else {
		if (base && *base)
			dname = base;
		else
			dname = stray[found[j]];
	}
	/* file pathsize already tested when each part found */
	ott_make_entry(stray[found[j]], dname, objtype, mask|partab->int_class, odi, mtime);
	stray[found[j]][0] = '\0';

	for (i = j+1; i < numparts; i++) {
		if (found[i] != -1) {
			/* file pathsize already tested when each part found */
			ott_make_entry(stray[found[i]], NULL, NULL,
						mask|partab->int_class, NULL, mtime);
			stray[found[i]][0] = '\0';
		}
	}

	return(TRUE);
}

static int
look_at_bytes(path, file)
char *path, *file;
{
	char buf[500];
	register char	*p;
	register int	numread;
	register int	fd;

	if (Already_looked & LOOKED_AT_BYTES)
		return;

	Already_looked |= LOOKED_AT_BYTES;
	Seen_eighth_bit = Seen_non_printable = FALSE;
	sprintf(buf, "%s/%s", path, file);
	if ((fd = open(buf, O_RDONLY)) < 0)
		return;
	numread = read(fd, buf, sizeof(buf));
	close(fd);

	for (p = buf; numread > 0; numread--, p++)
		if (!isprint(*p) && !isspace(*p) && *p != '\7' && *p != '\b') {
			Seen_non_printable = TRUE;
			if (!isascii(*p))
				Seen_eighth_bit = TRUE;
		}
}

static int
magic_heuristics(path, name, objtype, mask, odi, mtime, offsets, bytes)
char *path, *name, *objtype;
long mask;
char *odi;
long mtime;
long *offsets;
char *bytes;
{
	FILE *fp;
	register int i;
	char buf[PATHSIZ];

	/* file pathsize already tested in heuristics() */
	sprintf(buf, "%s/%s", path, name);
	if ((fp = fopen(buf, "r")) == NULL)
		return(0);

	for (i = 0; offsets[i] != -1; i++) {
		/* if the next offset is equal to the previous plus one, no need
		 * to seek
		 */
		if (i == 0 || offsets[i-1] != offsets[i] - 1) {
			if (fseek(fp, offsets[i], 0) != 0) {
				fclose(fp);
				return(0);
			}
		}
		if (getc(fp) != bytes[i]) {
			fclose(fp);
			return(0);
		}
	}

	fclose(fp);
	ott_make_entry(name, name, objtype, mask, odi, mtime);
	name[0] = 0;

	return(1);
}


/* currently unimplemented */

static int
external_heuristics()
{
	return(0);
}

static int
oeu_heuristics(path, name, objtype, defmask, defodi, mtime)
char *path, *name, *objtype;
long defmask;
char *defodi;
long mtime;
{
	char fullpath_or_odi[PATHSIZ];
	
	/* file pathsize already tested in heuristics() */
	sprintf(fullpath_or_odi, "%s/%s", path, name);
	if (look_at_oeh(fullpath_or_odi) != 0) {
		return(0);
	}

	if (!objtype) {	/* any encrypted object */
		if (Oeh.encrytest)
			objtype = Oeh.num;
		else
			return (0);
	}
	/* reuse fullpath_or_odi variable */
	strcpy(fullpath_or_odi, "TYPE=");
	strcat(fullpath_or_odi, Oeh.type);
	ott_make_entry(name, name, objtype,
			defmask, (defodi&&*defodi)?defodi:fullpath_or_odi, mtime);
	return(1);
}

int
look_at_oeh(path)
char *path;
{
	static int oeh_retcode;

	if (Already_looked & LOOKED_AT_OEH)
		return(oeh_retcode);

#ifdef WISH
	oeh_retcode = oeucheck(path, &Oeh, READ_HEADER);
#else
	oeh_retcode = oeuparse(path, &Oeh, READ_HEADER);
#endif
#ifdef _DEBUG
	_debug(stderr, "oeuparse(%s) returned %d\n", path, oeh_retcode);
#endif
	Already_looked |= LOOKED_AT_OEH;
	return(oeh_retcode);
}
