/*
 * md5.c  -  routines to handle MD5 checksums
 *
 * Copyright (C) 2002-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: md5.c,v 1.14 2007/01/06 18:31:15 gkminix Exp $
 */

#include <common.h>
#include <nblib.h>
#include "makerom.h"
#include "doconfig.h"
#include "md5.h"



/*
 * Private variables
 */
static int errnum;
static int warnnum;



/*
 * Definition of list of checksums
 */
struct md5rec {
	char           *md5sum;
	char           *drvname;
	struct md5rec  *next;
};

static struct md5rec *md5list = NULL;



/*
 * Read one line of MD5 checksum file
 */
static int md5line __F((fd, md5buf, fnambuf),
				FILE *fd AND
				char *md5buf AND
				char *fnambuf)
{
  int state;
  int c, i, j;

  /* Read each character of the next input line */
  i = j = state = 0;
  while ((c = fgetc(fd)) != '\n' && c != '\0' && c != EOF) {
	switch (state) {
		case 0:
			if (isxdigit(c)) {
				if (i >= MD5_SUM_LENGTH)
					state = 4;
				else
					md5buf[i++] = tolower(c);
			} else if (i == 0 && c == '#')
				state = 5;
			else if (i > 0 && isspace(c))
				state = (i == MD5_SUM_LENGTH ? 1 : 4);
			else if (i > 0 || !isspace(c))
				state = 4;
			break;
		case 1:
			if (isspace(c) || c == '*')
				break;
			else
				state = 2;
				/* Fall through */
		case 2:
			if (isprint(c)) {
				if (j >= MAXPATHLEN)
					state = 4;
				else
					fnambuf[j++] = c;
			} else if (isspace(c))
				state = 3;
			else
				state = 4;
			break;
		case 3:
		case 4:
		case 5:
			/* Skip rest of line */
			break;
	}
  }
  md5buf[i] = '\0';
  fnambuf[j] = '\0';
  return(state == 3  || (state == 2 && j > 0) ? 1 :
					(state == 5 || i == 0 ? 2 : 0));
}



/*
 * Find an entry in the MD5 checksum database
 */
static struct md5rec *findfile __F((fname), const char *fname)
{
  struct md5rec *mp;

  for (mp = md5list; mp != NULL; mp = mp->next)
	if (!strcmp(mp->drvname, fname))
		break;
  return(mp);
}



/*
 * Read one file containing MD5 checksum strings
 *
 * Each line contains a 32 character MD5 checksum and a file name. The
 * last modification time of each file is compared against the last
 * modification time of the checksum file. If the checksum file is older
 * the checksum entry is considered to be invalid.
 */
static void readone __F((fname), const char *fname)
{
  FILE *fd;
  struct md5rec *mp;
  static char chksum[MD5_SUM_LENGTH + 1];
  static char drvname[MAXPATHLEN + 1];
  char *dirname = NULL;
  char *tmpfname = NULL;
  char *cp;
  time_t fdtime;
  int i, lineno;

  /* Determine last modification time of MD5 checksum file */
  fdtime = filetime(fname, FT_MTIME);
  if (fdtime == (time_t)(-1))
	nbexit(-1);

  /* Open input file */
  if ((fd = fopen(fname, "r")) == NULL) {
	prnerr("unable to open MD5 checksum file %s", fname);
	nbexit(EXIT_MAKEROM_MD5FILE);
  }

  /* Determine directory of MD5 checksum file */
  copystr(&dirname, fname);
  if ((cp = strrchr(dirname, '/')) != NULL) {
	*cp = '\0';
	setpath(&dirname, NULL);
  } else {
	free(dirname);
	dirname = NULL;
  }

  /* Read each line of input file */
  lineno = 1;
  while (!feof(fd)) {
	if ((i = md5line(fd, chksum, drvname)) == 0) {
		prnerr("MD5 file %s, line %d: invalid line", fname, lineno);
		errnum++;
	} else if (i == 1) {
		/* Check if file exists */
		copystr(&tmpfname, drvname);
		checkaccess(&tmpfname, dirname, ACCESS_FILE_EXIST);
		if (tmpfname != NULL) {
			if ((mp = findfile(tmpfname)) != NULL) {
				free(tmpfname);
				tmpfname = NULL;
			} else {
				mp = nbmalloc(sizeof(struct md5rec));
				mp->drvname = tmpfname;
				mp->next = md5list;
				md5list = mp;
				tmpfname = NULL;
			}
			copystr(&(mp->md5sum), chksum);
			if (fdtime < filetime(mp->drvname, FT_MTIME)) {
				prnerr("driver file %s is newer than MD5 file %s",
							mp->drvname, fname);
				warnnum++;
			}
		} else {
			prnerr("MD5 file %s, line %d: can't find driver file",
							fname, lineno);
			warnnum++;
		}
	}
	lineno++;
  }

  /* Close input file */
  (void)fclose(fd);
}



/*
 * Read all MD5 checksum files
 */
void readmd5 __F_NOARGS
{
  char *cp, *fname, *buf = NULL;
  unsigned int filenum = 0;

  /* Check if we have anything to do */
  if (!strcmp(config.md5fname, "none"))
	return;
  else if (config.md5fname == NULL) {
	prnlog(LOGLEVEL_NORMAL, "Warning: No MD5 checksum file specified!");
	return;
  }

  /* Reset error counters */
  errnum = 0;
  warnnum = 0;

  /* Read all MD5 checksum files */
  copystr(&buf, config.md5fname);
  setpath(&buf, config.netdrvdir);
  cp = strtok(buf, ":");
  while (cp != NULL) {
	fname = NULL;
	copystr(&fname, cp);
	checkaccess(&fname, NULL, ACCESS_FILE_READ);
	if (fname != NULL) {
		prnlog(LOGLEVEL_NOTICE, "Processing MD5 checksum file '%s'\n",
									fname);
		readone(fname);
		free(fname);
		filenum++;
	}
	cp = strtok(NULL, ":");
  }
  free(buf);

  /* Check for errors */
  if (errnum > 0)
	nbexit(EXIT_MAKEROM_MD5FILE);
  if (filenum == 0) {
	prnerr("Unable to find at least one MD5 checksum file. You will not");
	prnerr("be able to use drivers which require to get identified using");
	prnerr("an MD5 checksum. Please update the MD5 checksum files or let");
	prnerr("your system administrator update them.");
  } else if (warnnum > 0) {
	prnerr("The MD5 checksum files contain entries which are older than");
	prnerr("the files they reference. These entries will not be used,");
	prnerr("so the result of running this program might not be what you");
	prnerr("expect. Please update the MD5 checksum files or let your");
	prnerr("system administrator update them.");
  }
}



/*
 * Check a filename and it's checksum against the entries in the checksum
 * file. If the checksums don't match, or the file name doesn't exist in
 * the checksum database, clear the fname parameter.
 */
void checkmd5sum __F((fname, md5sum), char **fname AND const char *md5sum)
{
  struct md5rec *mp;

  if (fname != NULL && *fname != NULL && md5sum != NULL) {
	mp = findfile(*fname);
	if (mp == NULL || strcmp(mp->md5sum, md5sum)) {
		/* File doesn't exist in database or checksums don't match */
		free(*fname);
		*fname = NULL;
	}
  }
}



/*
 * Clear the MD5 checksum database
 */
void clearmd5 __F_NOARGS
{
  struct md5rec *mp;

  while (md5list != NULL) {
	mp = md5list;
	if (mp->drvname != NULL)
		free(mp->drvname);
	if (mp->md5sum != NULL)
		free(mp->md5sum);
	md5list = mp->next;
	free(mp);
  }
}

