Skip to content
Snippets Groups Projects
chksmb.c 33.2 KiB
Newer Older
/* Synchronet message/file base (SMB) validity checker */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * 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 (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include <stdlib.h>		/* exit */
#include <time.h>		/* ctime */
rswindell's avatar
rswindell committed
#include <ctype.h>		/* toupper */
#include "git_branch.h"
#include "git_hash.h"

/* SMB-specific */
#include "genwrap.h"
#include "dirwrap.h"	/* fexist */
#include "filewrap.h"	/* filelength */

/****************************************************************************/
/* Returns in 'string' a character representation of the number in l with   */
/* commas.																	*/
/****************************************************************************/
char *ultoac(ulong l, char *string)
{
	char str[256];
	i=strlen(str)-1;
	j=i/3+1+i;
	string[j--]=0;
	for(k=1;i>-1;k++) {
		string[j--]=str[i--];
		if(j>0 && !(k%3))
rswindell's avatar
rswindell committed
	}
}

/****************************************************************************/
/* Returns an ASCII string for FidoNet address 'addr'                       */
/****************************************************************************/
char *faddrtoa(fidoaddr_t addr)
{
	static char str[25];
	char point[25];

	sprintf(str,"%hu:%hu/%hu",addr.zone,addr.net,addr.node);
	if(addr.point) {
		sprintf(point,".%u",addr.point);
rswindell's avatar
rswindell committed
	}
char* strip_ctrl(char *str)
rswindell's avatar
rswindell committed
	for(i=j=0;str[i] && j<sizeof(tmp)-1;i++) {
		if(str[i]==CTRL_A && str[i+1]!=0)
			i++;
		else if((uchar)str[i]>=' ')
rswindell's avatar
rswindell committed
	}
	if(i!=j) {
		tmp[j]=0;
		strcpy(str,tmp);
	}
BOOL contains_ctrl_chars(char* str)
{
	uchar* p;

	if(str==NULL)
		return FALSE;
	for(p = (uchar *)str; *p; p++)
		if(*p < ' ')
			return TRUE;
	return FALSE;
}

void print_hash(hash_t* hash)
{
	printf("\t%-20s = %lu\n"		,"hash.number"	, (ulong)hash->number);
	printf("\t%-20s = 0x%08lX\n"	,"hash.time"	, (ulong)hash->time);
	printf("\t%-20s = %lu\n"		,"hash.length"	, (ulong)hash->length);
	printf("\t%-20s = 0x%02X\n"		,"hash.source"	, (unsigned)hash->source);
	printf("\t%-20s = 0x%02X\n"		,"hash.flags"	, (unsigned)hash->flags);
	if(hash->flags & SMB_HASH_CRC16)
		printf("\t%-20s = 0x%04hX\n"	,"hash.crc16"	, hash->data.crc16);
	if(hash->flags & SMB_HASH_CRC32)
		printf("\t%-20s = 0x%08X\n"		,"hash.crc32"	, hash->data.crc32);
	if(hash->flags & SMB_HASH_MD5)
		printf("\t%-20s = %s\n"			,"hash.md5"		, MD5_hex(str, hash->data.md5));
	if(hash->flags & SMB_HASH_SHA1)
		printf("\t%-20s = %s\n"			,"hash.sha1"	, SHA1_hex(str, hash->data.sha1));
char *usage="\nusage: chksmb [-opts] <filespec.SHD>\n"
			"       s - stop after errored message/file base\n"
			"       p - pause after errored messsage/file base\n"
			"       h - don't check hash file\n"
			"       a - don't check allocation files\n"
			"       t - don't check translation strings\n"
			"       i - don't check message IDs\n"
			"       S - don't check subject CRCs\n"
			"       N - don't check to/from name CRCs\n"
			"       e - display extended info on corrupted msgs\n";

int main(int argc, char **argv)
{
	int 		h,i,j,x,y,lzh,errors,errlast;
	BOOL		stop_on_error=FALSE,pause_on_error=FALSE,chkxlat=TRUE,chkalloc=TRUE,chkhash=TRUE
				,lzhmsg,extinfo=FALSE,msgerr;
	BOOL		chk_subjcrc = TRUE;
	BOOL		chk_namecrc = TRUE;
deuce's avatar
deuce committed
	uint16_t	xlat;
	uint32_t	m;
	ulong		l,n,size,total=0,orphan,deleted,headers
				,*offset,*number,xlaterr
				,delidx
				,delhdrblocks,deldatblocks,hdrerr,lockerr,hdrnumerr,hdrlenerr
				,getbodyerr,gettailerr
				,acthdrblocks,actdatblocks
				,dupenum,dupenumhdr,dupeoff,attr,actalloc,types
				,datactalloc,misnumbered,timeerr,idxofferr,idxerr
				,subjcrc,fromcrc,tocrc
				,zeronum,idxzeronum,idxnumerr,packable=0L,totallzhsaved=0L
				,totalmsgs=0,totallzhmsgs=0,totaldelmsgs=0,totalmsgbytes=0L
	ulong		largest=0;
	ulong		largest_msgnum=0;
	smb_t		smb;
	idxrec_t	idx;
	idxrec_t*	idxrec = NULL;
	fileidxrec_t* fidxrec = NULL;
	fprintf(stderr,"\nCHKSMB v3.20-%s %s/%s SMBLIB %s - Check Synchronet Message/File Base\n"
		,PLATFORM_DESC, GIT_BRANCH, GIT_HASH, smb_lib_ver());
	if(argc<2) {
		printf("%s",usage);
		exit(1);
rswindell's avatar
rswindell committed
	}
	errlast=errors=0;
	for(x=1;x<argc;x++) {
		if(stop_on_error && errors)
			break;
		if(pause_on_error && errlast!=errors) {
			fprintf(stderr,"%s\nHit any key to continue...", beep);
			printf("\n");
rswindell's avatar
rswindell committed
		}
		if(argv[x][0]=='-'
			) {
			for(y=1;argv[x][y];y++)
				switch(argv[x][y]) {
					case 'q':
						pause_on_error=TRUE;
						stop_on_error=TRUE;
					case 'S':
						chk_subjcrc = FALSE;
						break;
					case 'N':
						chk_namecrc = FALSE;
						break;
						chkxlat=FALSE;
						chkalloc=FALSE;
						break;
						chkhash=FALSE;
						extinfo=TRUE;
						break;
					default:
						printf("%s",usage);
						exit(1);
rswindell's avatar
rswindell committed
			}
			continue;
rswindell's avatar
rswindell committed
		}
	ZERO_VAR(smb);
	SAFECOPY(smb.file,argv[x]);
	p = getfext(smb.file);
	if(p != NULL && stricmp(p, ".shd") == 0) *p=0;
	SAFEPRINTF(str, "%s.shd", smb.file);
	if(!fexist(str)) {
		printf("\n%s doesn't exist.\n",smb.file);
		continue;
rswindell's avatar
rswindell committed
	}
	fprintf(stderr,"\nChecking %s Headers\n\n",smb.file);
	smb.retry_time=30;
	if((i=smb_open(&smb))!=0) {
		printf("smb_open returned %d: %s\n",i,smb.last_error);
		continue;
rswindell's avatar
rswindell committed
	}
	const char* base_type = (smb.status.attr & SMB_FILE_DIRECTORY) ? "File" : "Message";

	/* File size sanity checks here: */

	shd_length=filelength(fileno(smb.shd_fp));
	if(shd_length < sizeof(smbhdr_t)) {
		printf("Empty\n");
		smb_close(&smb);
		continue;
rswindell's avatar
rswindell committed
	}
	if(shd_length < (off_t)smb.status.header_offset) {
		printf("!Status header corruption (header offset: %lu)\n", (ulong)smb.status.header_offset);
		smb_close(&smb);
		continue;
	}

	size_t idxreclen = smb_idxreclen(&smb);
	off_t sid_length = filelength(fileno(smb.sid_fp));
	if(sid_length != smb.status.total_msgs * idxreclen) {
		printf("!Size of index file (%ld) is incorrect (expected: %ld)\n", (long)sid_length, (long)(smb.status.total_msgs * idxreclen));
		smb_close(&smb);
		errors++;
		continue;
	}

	FREE_AND_NULL(idxrec);
	if((idxrec = calloc(smb.status.total_msgs, idxreclen)) == NULL) {
		printf("!Error allocating %lu index record\n", (ulong)smb.status.total_msgs);
		smb_close(&smb);
		errors++;
		continue;
	}
	if(fread(idxrec, idxreclen, smb.status.total_msgs, smb.sid_fp) != smb.status.total_msgs) {
		printf("!Error reading %lu index records\n", (ulong)smb.status.total_msgs);
		smb_close(&smb);
		errors++;
		continue;
	}
	fidxrec = (fileidxrec_t*)idxrec;
	off_t shd_hdrs = shd_length - smb.status.header_offset;

	if(shd_hdrs && (shd_hdrs%SHD_BLOCK_LEN) != 0)
		printf("!Size of msg header records in SHD file incorrect: %lu\n", (ulong)shd_hdrs);
	if((i=smb_locksmbhdr(&smb))!=0) {
		smb_close(&smb);
		printf("smb_locksmbhdr returned %d: %s\n",i,smb.last_error);
		continue;
rswindell's avatar
rswindell committed
	}
	if(((shd_hdrs/SHD_BLOCK_LEN)*sizeof(*number)) != 0){
		if((number=malloc((size_t)(((shd_hdrs/SHD_BLOCK_LEN)+2))*sizeof(*number)))
			==NULL) {
			printf("Error allocating %lu bytes of memory\n"
				,(ulong)((shd_hdrs/SHD_BLOCK_LEN)*sizeof(*number)));
			return(++errors); 
		} 
rswindell's avatar
rswindell committed
	}
	off_t sdt_length = filelength(fileno(smb.sdt_fp));
	if(sdt_length && (sdt_length % SDT_BLOCK_LEN) != 0)
		printf("!Size of SDT file (%lu) not evenly divisible by block length (%u)\n"
			,(ulong)sdt_length, SDT_BLOCK_LEN);
	if(chkalloc && !(smb.status.attr&SMB_HYPERALLOC)) {
		if((i=smb_open_ha(&smb))!=0) {
			printf("smb_open_ha returned %d: %s\n",i,smb.last_error);
			return(++errors);
rswindell's avatar
rswindell committed
		}
		if(filelength(fileno(smb.shd_fp)) != smb.status.header_offset
			+ (filelength(fileno(smb.sha_fp)) * SHD_BLOCK_LEN))
			printf("!Size of SHA file (%lu) does not match SHD file (%lu)\n"
				,(ulong)filelength(fileno(smb.sha_fp))
				,(ulong)filelength(fileno(smb.shd_fp)));

		if((i=smb_open_da(&smb))!=0) {
			printf("smb_open_da returned %d: %s\n",i,smb.last_error);
			return(++errors);
		}
		if((filelength(fileno(smb.sda_fp)))/sizeof(uint16_t) != filelength(fileno(smb.sdt_fp))/SDT_BLOCK_LEN)
			printf("!Size of SDA file (%lu) does not match SDT file (%lu)\n"
				,(ulong)filelength(fileno(smb.sda_fp))
				,(ulong)filelength(fileno(smb.sdt_fp)));
rswindell's avatar
rswindell committed
	}

	headers=deleted=orphan=dupenumhdr=attr=zeronum=timeerr=lockerr=hdrerr=0;
	actalloc=datactalloc=deldatblocks=delhdrblocks=xlaterr=0;
	lzhblocks=lzhsaved=acthdrblocks=actdatblocks=0;
	acthdrblocks=actdatblocks=0;
	dfieldlength=dfieldoffset=0;
	for(l=smb.status.header_offset; l < (uint32_t)shd_length;l+=size) {
rswindell's avatar
rswindell committed
		size=SHD_BLOCK_LEN;
		fprintf(stderr,"\r%2lu%%  ",(long)(100.0/((float)shd_length/l)));
		fflush(stderr);
		msg.idx.offset=l;
		msgerr=FALSE;
		if((i=smb_lockmsghdr(&smb,&msg))!=0) {
			printf("\n(%06lX) smb_lockmsghdr returned %d: %s\n",l,i,smb.last_error);
			continue;
rswindell's avatar
rswindell committed
		}
		if((i=smb_getmsghdr(&smb,&msg))!=0) {
			smb_unlockmsghdr(&smb,&msg);
			if(chkalloc && !(smb.status.attr&SMB_HYPERALLOC)) {
				fseek(smb.sha_fp
					,(l-smb.status.header_offset)/SHD_BLOCK_LEN,SEEK_SET);
				j=fgetc(smb.sha_fp);
				if(j) { 			/* Allocated block or at EOF */
					printf("%s\n(%06lX) smb_getmsghdr returned %d: %s\n",beep,l,i,smb.last_error);
					hdrerr++;
rswindell's avatar
rswindell committed
				}
					delhdrblocks++;
rswindell's avatar
rswindell committed
			}
			else {
				/* printf("%s\n(%06lX) smb_getmsghdr returned %d\n",beep,l,i); */
				delhdrblocks++;
rswindell's avatar
rswindell committed
			}
			continue;
rswindell's avatar
rswindell committed
		}
		smb_unlockmsghdr(&smb,&msg);
rswindell's avatar
rswindell committed
		size=smb_hdrblocks(smb_getmsghdrlen(&msg))*SHD_BLOCK_LEN;

		if(msg.hdr.type == SMB_MSG_TYPE_FILE)
			SAFECOPY(from, msg.subj);
		else
			SAFECOPY(from, msg.from);
deuce's avatar
deuce committed
		fprintf(stderr,"#%-5"PRIu32" (%06lX) %-25.25s ",msg.hdr.number,l,from);
		for(n = 0; n < smb.status.total_msgs; n++) {
			idxrec_t* idx;
			if(idxreclen == sizeof(*fidxrec))
				idx = &fidxrec[n].idx;
			else
				idx = &idxrec[n];
			if(idx->number == msg.hdr.number)
			if(idx->offset > l && idx->offset < l + (smb_hdrblocks(msg.hdr.length) * SHD_BLOCK_LEN)) {
				fprintf(stderr,"%s%s header overlap\n", base_type, beep);
					printf("ERR: Header for %s #%lu overlaps with #%lu\n"
						,base_type, (ulong)idxrec[n].number, (ulong)msg.hdr.number);
		if(msg.hdr.length!=smb_getmsghdrlen(&msg)) {
			fprintf(stderr,"%sHeader length mismatch\n",beep);
			msgerr=TRUE;
				printf("MSGERR: Header length (%hu) does not match calculated length (%u)\n"
					,msg.hdr.length,smb_getmsghdrlen(&msg));
			hdrlenerr++;
		if(msg.hdr.attr&MSG_DELETE)
			body=tail=NULL;
		else {
			if(contains_ctrl_chars(msg.to)
				|| (msg.to_net.type != NET_FIDO && contains_ctrl_chars(msg.to_net.addr))
				|| contains_ctrl_chars(msg.from)
				|| (msg.from_net.type != NET_FIDO && contains_ctrl_chars(msg.from_net.addr))
				|| contains_ctrl_chars(msg.subj)) {
				fprintf(stderr,"%sHeader field contains control characters\n", beep);
				msgerr=TRUE;
				ctrl_chars++;
			}
			long age = (long)(now - msg.hdr.when_imported.time);
			if(age  > (long)oldest)
				oldest = age;
			/* Test reading of the message text (body and tails) */
			if((body=smb_getmsgtxt(&smb,&msg,GETMSGTXT_BODY_ONLY))==NULL) {
				fprintf(stderr,"%sGet text body failure\n",beep);
				msgerr=TRUE;
				if(extinfo)
					printf("MSGERR: %s\n", smb.last_error);
				getbodyerr++;
			}
			if((tail=smb_getmsgtxt(&smb,&msg,GETMSGTXT_TAIL_ONLY))==NULL) {
				fprintf(stderr,"%sGet text tail failure\n",beep);
				msgerr=TRUE;
				if(extinfo)
					printf("MSGERR: %s\n", smb.last_error);
				gettailerr++;
			}
		}

		if(msg.hdr.type != smb_msg_type(msg.hdr.attr)) {
			fprintf(stderr,"%s%s type mismatch (%d, expected %d)\n"
				,beep, base_type, msg.hdr.type, smb_msg_type(msg.hdr.attr));
		else if(msg.hdr.type == SMB_MSG_TYPE_NORMAL && chk_msgids && msg.from_net.type == NET_NONE && msg.id == NULL) {
			fprintf(stderr,"%sNo Message-ID\n",beep);
			msgerr=TRUE;
			if(extinfo)
				printf("MSGERR: Header missing Message-ID\n");
			msgids++;
		}
		if(!(smb.status.attr&(SMB_EMAIL|SMB_NOHASH|SMB_FILE_DIRECTORY)) && chkhash) {
			/* Look-up the message hashes */
			hashes=smb_msghashes(&msg,(uchar*)body,SMB_HASH_SOURCE_DUPE);
			if(hashes!=NULL
				&& hashes[0]!=NULL
				&& (i=smb_findhash(&smb,hashes,NULL,SMB_HASH_SOURCE_DUPE,/* mark */TRUE ))
					!=SMB_SUCCESS) {
				for(h=0;hashes[h]!=NULL;h++) {
					if(hashes[h]->flags&SMB_HASH_MARKED)
						continue;
					fprintf(stderr,"%sFailed to find %s hash\n"
						,beep,smb_hashsourcetype(hashes[h]->source));
					msgerr=TRUE;
						printf("MSGERR: %d searching for %s: %s\n"
							,i
							,smb_hashsourcetype(hashes[h]->source)
							,smb_hashsource(&msg,hashes[h]->source));
#ifdef _DEBUG
						printf("\n");
						printf("%-10s: %s\n",		"Source",	smb_hashsourcetype(hashes[h]->source));
deuce's avatar
deuce committed
						printf("%-10s: %"PRIu32"\n",		"Length",	hashes[h]->length);
						printf("%-10s: %x\n",		"Flags",	hashes[h]->flags);
						if(hashes[h]->flags&SMB_HASH_CRC16)
							printf("%-10s: %04x\n",	"CRC-16",	hashes[h]->data.crc16);
						if(hashes[h]->flags&SMB_HASH_CRC32)
							printf("%-10s: %08"PRIx32"\n","CRC-32",	hashes[h]->data.crc32);
						if(hashes[h]->flags&SMB_HASH_MD5)
							printf("%-10s: %s\n",	"MD5",		MD5_hex(str,hashes[h]->data.md5));
						if(hashes[h]->flags&SMB_HASH_SHA1)
							printf("%-10s: %s\n",	"SHA-1",	SHA1_hex(str,hashes[h]->data.sha1));
			smb_close_hash(&smb);	/* just incase */
			FREE_LIST(hashes,i);
		}
		lzhmsg=FALSE;
		ulong data_length = smb_getmsgdatlen(&msg);
		if(data_length > largest) {
			largest = data_length;
			largest_msgnum = msg.hdr.number;
		}
		if(msg.hdr.attr&MSG_DELETE) {
			deleted++;
			if(number)
				number[headers]=0;
			if(smb.status.attr&SMB_HYPERALLOC)
				deldatblocks+=smb_datblocks(data_length);
rswindell's avatar
rswindell committed
		}
			actdatblocks+=smb_datblocks(data_length);
			if(msg.hdr.number>smb.status.last_msg) {
				fprintf(stderr,"%sOut-Of-Range %s number\n",beep, base_type);
				msgerr=TRUE;
deuce's avatar
deuce committed
					printf("MSGERR: Header number (%"PRIu32") greater than last (%"PRIu32")\n"
						,msg.hdr.number,smb.status.last_msg);
				hdrnumerr++;
rswindell's avatar
rswindell committed
			}
			if(smb_getmsgidx(&smb,&msg) || msg.idx.offset != l) {
				fprintf(stderr,"%sNot found in index\n",beep);
				msgerr=TRUE;
deuce's avatar
deuce committed
					printf("MSGERR: Header number (%"PRIu32") not found in index\n"
						,msg.hdr.number);
				orphan++;
rswindell's avatar
rswindell committed
			}
			else {
				if(msg.hdr.attr!=msg.idx.attr) {
					fprintf(stderr,"%sAttributes mismatch\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: Header attributes (%04X) do not match index "
							"attributes (%04X)\n"
							,msg.hdr.attr,msg.idx.attr);
				}
				if(msg.hdr.when_imported.time!=msg.idx.time) {
					fprintf(stderr,"%sImport date/time mismatch\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: Header import date/time does not match "
							"index import date/time\n");
					timeerr++;
				if(chk_subjcrc && (msg.hdr.type == SMB_MSG_TYPE_NORMAL || msg.hdr.type == SMB_MSG_TYPE_POLL)
					&& msg.idx.subj!=smb_subject_crc(msg.subj)) {
					fprintf(stderr,"%sSubject CRC mismatch\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: Subject (%04X) does not match index "
							"CRC (%04X)\n"
							,smb_subject_crc(msg.subj),msg.idx.subj);
					subjcrc++;
				if(smb.status.attr & SMB_EMAIL
					&& (msg.from_ext!=NULL || msg.idx.from)
					&& (msg.from_ext==NULL || msg.idx.from!=atoi(msg.from_ext))) {
					fprintf(stderr,"%sFrom extension mismatch\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: From extension (%s) does not match index "
							"(%u)\n"
							,msg.from_ext,msg.idx.from);
					fromcrc++;
				if(chk_namecrc && !(smb.status.attr & SMB_EMAIL)
					&& (msg.hdr.type == SMB_MSG_TYPE_NORMAL || msg.hdr.type == SMB_MSG_TYPE_POLL)
					&& msg.idx.from!=smb_name_crc(msg.from)) {
					fprintf(stderr,"%sFrom CRC mismatch\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: From (%04X) does not match index "
							"CRC (%04X)\n"
							,smb_name_crc(msg.from),msg.idx.from);
					fromcrc++;
				if(smb.status.attr & SMB_EMAIL
					&& (msg.to_ext!=NULL || msg.idx.to)
					&& (msg.to_ext==NULL || msg.idx.to!=atoi(msg.to_ext))) {
					fprintf(stderr,"%sTo extension mismatch\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: To extension (%s) does not match index "
							"(%u)\n"
							,msg.to_ext,msg.idx.to);
					tocrc++;
				if(chk_namecrc && !(smb.status.attr & SMB_EMAIL)
					&& (msg.hdr.type == SMB_MSG_TYPE_NORMAL || msg.hdr.type == SMB_MSG_TYPE_POLL)
					&& msg.to_ext==NULL && msg.idx.to!=smb_name_crc(msg.to)) {
					fprintf(stderr,"%sTo CRC mismatch\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: To (%04X) does not match index "
							"CRC (%04X)\n"
							,smb_name_crc(msg.to),msg.idx.to);
					tocrc++;
				if(msg.hdr.netattr&NETMSG_INTRANSIT) {
					fprintf(stderr,"%sIn transit\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: Flagged 'in transit'\n");
					intransit++;
				}
				if((msg.hdr.attr&(MSG_MODERATED|MSG_VALIDATED|MSG_DELETE)) == MSG_MODERATED) {
					fprintf(stderr,"%sUnvalidated\n",beep);
					msgerr=TRUE;
					if(extinfo)
						printf("MSGERR: Flagged 'moderated', but not yet 'validated'\n");
					unvalidated++;
				}
			if(msg.hdr.number==0) {
				fprintf(stderr,"%sZero %s number\n",beep, base_type);
				msgerr=TRUE;
				if(extinfo)
					printf("MSGERR: Header number is zero (invalid)\n");
				zeronum++;
rswindell's avatar
rswindell committed
			}
			if(number) {
				for(m=0;m<headers;m++)
					if(number[m] && msg.hdr.number==number[m]) {
						fprintf(stderr,"%sDuplicate %s number\n",beep, base_type);
						msgerr=TRUE;
deuce's avatar
deuce committed
							printf("MSGERR: Header number (%"PRIu32") duplicated\n"
								,msg.hdr.number);
						dupenumhdr++;
rswindell's avatar
rswindell committed
					}
				number[headers]=msg.hdr.number;
rswindell's avatar
rswindell committed
			}
			if(chkxlat) {		/* Check translation strings */
				for(i=0;i<msg.hdr.total_dfields;i++) {
					fseek(smb.sdt_fp,msg.hdr.offset+msg.dfield[i].offset,SEEK_SET);
					if(!fread(&xlat,2,1,smb.sdt_fp))
						xlat=0xffff;
					lzh=0;
					if(xlat==XLAT_LZH) {
						lzh=1;
						if(!fread(&xlat,2,1,smb.sdt_fp))
							xlat=0xffff;
rswindell's avatar
rswindell committed
					}
					if(xlat!=XLAT_NONE) {
						fprintf(stderr,"%sUnsupported Xlat %04X dfield[%u]\n"
							,beep,xlat,i);
						msgerr=TRUE;
						if(extinfo)
							printf("MSGERR: Unsupported translation type (%04X) "
deuce's avatar
deuce committed
								"in dfield[%u] (offset %"PRIu32")\n"
rswindell's avatar
rswindell committed
					}
							lzhmsg=TRUE;
							if(fread(&m,4,1,smb.sdt_fp)) { /* Get uncompressed len */
								lzhsaved+=(smb_datblocks(m+2)
									-smb_datblocks(msg.dfield[i].length))
									*SDT_BLOCK_LEN;
								lzhblocks+=smb_datblocks(msg.dfield[i].length);
rswindell's avatar
rswindell committed
		}

		if(chkalloc && !(smb.status.attr&SMB_HYPERALLOC)) {
			fseek(smb.sha_fp,(l-smb.status.header_offset)/SHD_BLOCK_LEN,SEEK_SET);
			for(m=0;m<size;m+=SHD_BLOCK_LEN) {
	/***
				if(msg.hdr.attr&MSG_DELETE && (i=fgetc(smb.sha_fp))!=0) {
					fprintf(stderr,"%sDeleted Header Block %lu marked %02X\n"
						,beep,m/SHD_BLOCK_LEN,i);
					msgerr=TRUE;
rswindell's avatar
rswindell committed
					}
	***/
				if(!(msg.hdr.attr&MSG_DELETE) && (i=fgetc(smb.sha_fp))!=1) {
deuce's avatar
deuce committed
					fprintf(stderr,"%sActive Header Block %"PRIu32" marked %02X\n"
						,beep,m/SHD_BLOCK_LEN,i);
					msgerr=TRUE;
deuce's avatar
deuce committed
						printf("MSGERR: Active header block %"PRIu32" marked %02X "
							"instead of 01\n"
							,m/SHD_BLOCK_LEN,i);
					actalloc++;
				}
rswindell's avatar
rswindell committed
			}

			if(!(msg.hdr.attr&MSG_DELETE)) {
				acthdrblocks+=(size/SHD_BLOCK_LEN);
				for(n=0;n<msg.hdr.total_dfields;n++) {
					if(msg.dfield[n].offset&0x80000000UL) {
						msgerr=TRUE;
deuce's avatar
deuce committed
							printf("MSGERR: Invalid Data Field [%lu] Offset: %"PRIu32"\n"
								,n,msg.dfield[n].offset);
						dfieldoffset++;
rswindell's avatar
rswindell committed
					}
					if(msg.dfield[n].length&0x80000000UL) {
						msgerr=TRUE;
deuce's avatar
deuce committed
							printf("MSGERR: Invalid Data Field [%lu] Length: %"PRIu32"\n"
								,n,msg.dfield[n].length);
						dfieldlength++;
rswindell's avatar
rswindell committed
					}
					fseek(smb.sda_fp
						,((msg.hdr.offset+msg.dfield[n].offset)/SDT_BLOCK_LEN)*2
						,SEEK_SET);
					for(m=0;m<msg.dfield[n].length;m+=SDT_BLOCK_LEN) {
deuce's avatar
deuce committed
						/* TODO: LE Only */
						i=0;
						if(!fread(&i,2,1,smb.sda_fp) || !i) {
							fprintf(stderr
deuce's avatar
deuce committed
								,"%sActive Data Block %lu.%"PRIu32" marked free\n"
								,beep,n,m/SHD_BLOCK_LEN);
							msgerr=TRUE;
deuce's avatar
deuce committed
								printf("MSGERR: Active Data Block %lu.%"PRIu32" "
									"marked free\n"
									,n,m/SHD_BLOCK_LEN);
rswindell's avatar
rswindell committed
			}
				delhdrblocks+=(size/SHD_BLOCK_LEN);
rswindell's avatar
rswindell committed
		}

		else {	 /* Hyper Alloc */
			if(msg.hdr.attr&MSG_DELETE)
				delhdrblocks+=(size/SHD_BLOCK_LEN);
			else
				acthdrblocks+=(size/SHD_BLOCK_LEN);
rswindell's avatar
rswindell committed
		}

		totallzhmsgs+=lzhmsg;
		headers++;
		if(msgerr && extinfo) {
			printf("\n");
			printf("%-16s %s\n",(smb.status.attr & SMB_FILE_DIRECTORY) ? "file base":"message base",smb.file);
			printf("\n");
rswindell's avatar
rswindell committed
		}
		smb_freemsgmem(&msg);
rswindell's avatar
rswindell committed
	}

	fprintf(stderr,"\r%79s\r100%%\n","");

	if(chkalloc && !(smb.status.attr&SMB_HYPERALLOC)) {

		fprintf(stderr,"\nChecking %s Data Blocks\n\n",smb.file);

		off_t sda_length=filelength(fileno(smb.sda_fp));

		fseek(smb.sda_fp,0L,SEEK_SET);
		for(l=0;l < (ulong)sda_length;l+=2) {
				fprintf(stderr,"\r%2lu%%  ",l ? (long)(100.0/((float)sda_length/l)) : 0);
deuce's avatar
deuce committed
			/* TODO: LE Only */
			i=0;
			if(!fread(&i,2,1,smb.sda_fp))
				break;
			if(!i)
				deldatblocks++;
rswindell's avatar
rswindell committed
		}
		smb_close_ha(&smb);
		smb_close_da(&smb);
		fprintf(stderr,"\r%79s\r100%%\n","");
rswindell's avatar
rswindell committed
	}
	total=(ulong)filelength(fileno(smb.sid_fp))/smb_idxreclen(&smb);

	dupenum=dupeoff=misnumbered=idxzeronum=idxnumerr=idxofferr=idxerr=delidx=0;

	if(total) {

	fprintf(stderr,"\nChecking %s Index\n\n",smb.file);

	if((offset=malloc(total*sizeof(*offset)))==NULL) {
		printf("Error allocating %lu bytes of memory\n",total*sizeof(*offset));
		return(++errors); 
rswindell's avatar
rswindell committed
	}
	if((number=malloc(total*sizeof(*number)))==NULL) {
		printf("Error allocating %lu bytes of memory\n",total*sizeof(*number));
		return(++errors); 
rswindell's avatar
rswindell committed
	}

	for(l=0;l<total;l++) {
		fseek(smb.sid_fp, l * smb_idxreclen(&smb), SEEK_SET);
		fprintf(stderr,"\r%2lu%%  %5lu ",l ? (long)(100.0/((float)total/l)) : 0,l);
		if(!fread(&idx,sizeof(idx),1,smb.sid_fp))
deuce's avatar
deuce committed
		fprintf(stderr,"#%-5"PRIu32" (%06"PRIX32") 1st Pass ",idx.number,idx.offset);
		if(idx.attr&MSG_DELETE) {
			/* Message Disabled... why?  ToDo */
			/* fprintf(stderr,"%sMarked for deletion\n",beep); */
			delidx++;
rswindell's avatar
rswindell committed
		}
		for(m=0;m<l;m++)
			if(number[m]==idx.number) {
				fprintf(stderr,"%sDuplicate %s number\n",beep, base_type);
rswindell's avatar
rswindell committed
			}
		for(m=0;m<l;m++)
			if(offset[m]==idx.offset) {
deuce's avatar
deuce committed
				fprintf(stderr,"%sDuplicate offset: %"PRIu32"\n",beep,idx.offset);
rswindell's avatar
rswindell committed
			}
		if(idx.offset<smb.status.header_offset) {
			fprintf(stderr,"%sInvalid offset\n",beep);
			idxofferr++;
rswindell's avatar
rswindell committed
		}
		if(idx.number==0) {
			fprintf(stderr,"%sZero %s number\n",beep, base_type);
rswindell's avatar
rswindell committed
		}
		if(idx.number>smb.status.last_msg) {
			fprintf(stderr,"%sOut-Of-Range %s number\n",beep, base_type);
rswindell's avatar
rswindell committed
		}
		number[l]=idx.number;
		offset[l]=idx.offset;
rswindell's avatar
rswindell committed
	}
	if(l<total) {
		fprintf(stderr,"%sError reading index record\n",beep);
		idxerr=1;
rswindell's avatar
rswindell committed
	}
	else {
		fprintf(stderr,"\r%79s\r","");
		for(m=0;m<total;m++) {
deuce's avatar
deuce committed
			fprintf(stderr,"\r%2lu%%  %5"PRIu32" ",m ? (long)(100.0/((float)total/m)) : 0,m);
			fprintf(stderr,"#%-5lu (%06lX) 2nd Pass ",number[m],offset[m]);
			for(n=0;n<m;n++)
				if(number[m] && number[n] && number[m]<number[n]) {
					fprintf(stderr,"%sMisordered %s number\n",beep, base_type);
					misnumbered++;
					number[n]=0;
rswindell's avatar
rswindell committed
		}
		fprintf(stderr,"\r%79s\r100%%\n","");
rswindell's avatar
rswindell committed
	}
	FREE_AND_NULL(number);
	FREE_AND_NULL(offset);
	if(!(smb.status.attr&SMB_EMAIL) && chkhash && smb_open_hash(&smb)==SMB_SUCCESS) {
		hash_t hash;

		fprintf(stderr,"\nChecking %s Hashes\n\n",smb.file);

		off_t hash_length=filelength(fileno(smb.hash_fp));
		for(l=0; l < (ulong)hash_length; l+=sizeof(hash_t)) {
				fprintf(stderr,"\r%2lu%%  ",l ? (long)(100.0/((float)hash_length/l)) : 0);
			if(!fread(&hash,sizeof(hash),1,smb.hash_fp))
				break;
			if(hash.number==0 || hash.number > smb.status.last_msg)
				fprintf(stderr,"\r%sInvalid %s number (%u > %u)\n", beep, base_type, hash.number, smb.status.last_msg), badhash++, print_hash(&hash);
			else if(hash.time < 0x40000000 || hash.time > (ulong)now + (60 * 60))
				fprintf(stderr,"\r%sInvalid time (0x%08"PRIX32")\n", beep, hash.time), badhash++, print_hash(&hash);
			else if(hash.length < 1 || hash.length > 1024*1024*1024)
				fprintf(stderr,"\r%sInvalid length (%"PRIu32")\n", beep, hash.length), badhash++, print_hash(&hash);
			else if(hash.source >= SMB_HASH_SOURCE_TYPES)
				fprintf(stderr,"\r%sInvalid source type (%u)\n", beep, hash.source), badhash++, print_hash(&hash);
		fprintf(stderr,"\r%79s\r100%%\n","");
	totalmsgs+=smb.status.total_msgs;
	totalmsgbytes+=(acthdrblocks*SHD_BLOCK_LEN)+(actdatblocks*SDT_BLOCK_LEN);
	totaldelmsgs+=deleted;
	totallzhsaved+=lzhsaved;
deuce's avatar
deuce committed
	printf("%-35.35s (=): %"PRIu32"\n"
		,"Status Total"
		,smb.status.total_msgs);
	printf("%-35.35s (=): %lu\n"
		,"Total Indexes"
		,total);
	printf("%-35.35s (=): %lu\n"
		,"Active Indexes"
		,total-delidx);
	printf("%-35.35s (=): %lu\n"
		,"Active Headers"
		,headers-deleted);
	printf("%-35.35s ( ): %-8lu %13s bytes used\n"
		,"Active Header Blocks"
		,acthdrblocks,ultoac(acthdrblocks*SHD_BLOCK_LEN,str));
	printf("%-35.35s ( ): %-8lu %13s bytes used\n"
		,"Active Data Blocks"
		,actdatblocks,ultoac(actdatblocks*SDT_BLOCK_LEN,str));
	if(lzhblocks)
		printf("%-35.35s ( ): %-8lu %13s bytes saved\n"
			,"Active LZH Compressed Data Blocks"
			,lzhblocks,ultoac(lzhsaved,str));
	printf("%-35.35s ( ): %lu\n"
		,"Header Records"
		,headers);
	printf("%-35.35s ( ): %lu\n"
		,"Deleted Indexes"
		,delidx);
	printf("%-35.35s ( ): %lu\n"
		,"Deleted Headers"
		,deleted);
	printf("%-35.35s ( ): %-8lu %13s bytes used\n"
		,"Deleted Header Blocks"
		,delhdrblocks,ultoac(delhdrblocks*SHD_BLOCK_LEN,str));
	packable+=(delhdrblocks*SHD_BLOCK_LEN);
	printf("%-35.35s ( ): %-8lu %13s bytes used\n"
		,"Deleted Data Blocks"
		,deldatblocks,ultoac(deldatblocks*SDT_BLOCK_LEN,str));
	packable+=(deldatblocks*SDT_BLOCK_LEN);
	if(oldest)
		printf("%-35.35s ( ): %lu days (%u max)\n"
			,"Oldest Message (import)"
			,oldest/(24*60*60), smb.status.max_age);
	if(largest)
		printf("%-35.35s ( ): %lu bytes (#%lu)\n"
			,"Largest Message (data)"
			,largest, largest_msgnum);
	if(orphan)
		printf("%-35.35s (!): %lu\n"
			,"Orphaned Headers"
			,orphan);
	if(idxzeronum)
		printf("%-35.35s (!): %lu\n"
			,"Zeroed Index Numbers"
			,idxzeronum);
	if(zeronum)
		printf("%-35.35s (!): %lu\n"
			,"Zeroed Header Numbers"
			,zeronum);
	if(idxofferr)
		printf("%-35.35s (!): %lu\n"
			,"Invalid Index Offsets"