/*
 * md5.c  -  routines to handle MD5 checksums
 *
 * Copyright (C) 2002-2003 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.4 2003/01/25 23:29:42 gkminix Exp $
 */

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



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

static struct md5rec *md5list = NULL;



/*
 * Read one line of MD5 checksum file
 */
static int md5line(fd, md5buf, fnambuf)
FILE *fd;
char *md5buf;
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(fname)
char *fname;
{
  struct md5rec *mp;

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



/*
 * Read a 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.
 */
void readmd5(fname)
char *fname;
{
  FILE *fd;
  struct md5rec *mp;
  char *chksum, *drvname;
  char *tmpfname = NULL;
  time_t fdtime;
  int i, lineno, errno, warnno;

  /* Get the last modification time of the checksum file */
  fdtime = filetime(fname, FT_MTIME);

  /* Allocate buffers and open input file */
  chksum = nbmalloc(MD5_SUM_LENGTH + 1);
  drvname = nbmalloc(MAXPATHLEN + 1);
  if ((fd = fopen(fname, "r")) == NULL) {
	prnerr1("unable to open MD5 checksum file %s", fname);
	exit(EXIT_MAKEROM_MD5FILE);
  }

  /* Read each line of input file */
  lineno = 1;
  errno = 0;
  warnno = 0;
  while (!feof(fd)) {
	if ((i = md5line(fd, chksum, drvname)) == 0) {
		prnerr1("MD5 checksum file line %d: invalid line", lineno);
		errno++;
	} else if (i == 1) {
		/* Check if file exists */
		copystr(&tmpfname, drvname);
		checkaccess(&tmpfname, config.netdrvdir);
		if (tmpfname != NULL) {
			if (findfile(tmpfname) != NULL) {
				prnerr1("MD5 checksum file line %d: file already defined", lineno);
				errno++;
				free(tmpfname);
			} else if (fdtime < filetime(tmpfname, FT_MTIME)) {
				prnerr1("File %s is newer than MD5 checksum file", tmpfname);
				warnno++;
				free(tmpfname);
			} else {
				mp = nbmalloc(sizeof(struct md5rec));
				copystr(&(mp->md5sum), chksum);
				mp->fname = tmpfname;
				mp->next = md5list;
				md5list = mp;
			}
			tmpfname = NULL;
		} else {
			prnerr1("MD5 checksum file line %d: file does not exist", lineno);
			warnno++;
		}
	}
	lineno++;
  }

  /* Close input file and free buffers */
  fclose(fd);
  free(chksum);
  free(drvname);
  if (errno > 0)
	exit(EXIT_MAKEROM_MD5FILE);
  if (warnno > 0) {
	prnerr0("The MD5 checksum file contains entries which are older than");
	prnerr0("the files they reference. These entries will not be used,");
	prnerr0("so the result of running this program might not be what you");
	prnerr0("expect. Please update the MD5 checksum file or let your");
	prnerr0("system administrator update it.");
  }
}



/*
 * 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(fname, md5sum)
char **fname;
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()
{
  struct md5rec *mp;

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



syntax highlighted by Code2HTML, v. 0.9.1