/*
 * The Initial Developer of the Original Code is International
 * Business Machines Corporation. Portions created by IBM
 * Corporation are Copyright (C) 2007 International Business
 * Machines Corporation. All Rights Reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the Common Public License as published by
 * IBM Corporation; either version 1 of the License, or (at your option)
 * 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
 * Common Public License for more details.
 *
 * You should have received a copy of the Common Public License
 * along with this program; if not, a copy can be viewed at
 * http://www.opensource.org/licenses/cpl1.0.php.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <tss/platform.h>
#include <tss/tss_defines.h>
#include <tss/tss_typedef.h>
#include <tss/tss_structs.h>
#include <tss/tss_error.h>
#include <tss/tspi.h>

#include <getopt.h>

// Local TCSD
#define SERVER    NULL


/*

 TPM PCR Read

 Usage:
  tpm_pcrread -p index 
 
 */

int hex2bin(void *dest, const void *src, size_t n);
void printhex(char *str, unsigned char *buf, int len);

/* options */
const struct option long_option[] = {
	{"help", no_argument, NULL, 'h'},
	{"list", no_argument, NULL, 'l'},
	{"binary", no_argument, NULL, 'b'},
	{"firmware", no_argument, NULL, 'f'},
	{"kernel", no_argument, NULL, 'k'},
	{"output", required_argument, NULL, 'o'},
	{"mode", required_argument, NULL, 'm'},
	{0, 0, 0, 0}
};
const char short_option[] = "hlbfko:m:";

struct biosEvent {
	UINT32   pcrIndex;
	UINT32   eventType;
	BYTE     digest[20];
	UINT32   eventDataSize;
	// BYTE[]   event
}; 

void usage() {
	printf("Usage: iml [options]\n");
	printf("\t-h, --help\n");
	printf("\t\tDisplay command usage info.\n");

	printf("\t-l, --list\n");
	printf("\t\tList IML (Integrity Measurment Log)\n");
	
	printf("\t-b, --binary\n");
	printf("\t\tList IML (Integrity Measurment Log) in binary format (BIOS ACPI Table format)\n");
	
	printf("\t-o, --output FILE\n");
	printf("\t\tFilename to write eventlog(IML) to.  Default is STDOUT.\n");
}

#define BUFSIZE 256
// State
int pcr4_grub=0;
int pcr5_grub=0;

void fprintEventData(FILE* fp, BYTE* data,UINT32 len,UINT32 pcrindex, UINT32 type) {
	char buf[BUFSIZE]; 
	
	// String Data
	if (len < 0) {
		fprintf(fp,"[ERROR]");
		return;
	}
	else if (len < BUFSIZE ) { 
		memcpy(buf,data,len);
		buf[len]=0; // terminate
	}
	else {
		memcpy(buf,data,BUFSIZE);
		buf[BUFSIZE-1] = 0; // terminate
	}
	
	
	if (pcrindex == 10) { // Linux-IMA
		if (type == 2) {
			fprintf(fp,"[IMA-LKM:%s] ",buf);
		}
		else if (type == 1) {
			fprintf(fp,"[IMA-EXE:%s] ",buf);
		}
		else if (type == 0) {
			fprintf(fp,"[IMA:%s] ",buf);
		}
		else if ((type & 0xFFFF) == 4) {
			fprintf(fp,"[IMA-USR,0x%04x:%s] ",(type >> 16),buf);
		}
		else {
			fprintf(fp,"[???:%s] ",buf);
		}
	}
	else if (pcrindex <= 8) { // BIOS + Grub
		switch(type) {
			case 0:
				fprintf(fp,"[BIOS:EV_PREBOOT_CERT(EV_CODE_CERT)]");
				break;
			case 1:
				fprintf(fp,"[BIOS:EV_POST_CODE(EV_CODE_NOCERT)]");
				break;
			case 2:
				fprintf(fp,"[BIOS:EV_UNUSED(EV_XML_CONFIG)]");
				break;
			case 3:
				fprintf(fp,"[BIOS:EV_NO_ACTION]");
				break;
			case 4:
				if ((pcr4_grub > 1) && (pcrindex == 4)) {
					fprintf(fp,"[GRUB:EV_SEPARATOR, %s]",buf);
				}
				else if ((pcr5_grub > 0) && (pcrindex == 5)) {
					fprintf(fp,"[GRUB:EV_SEPARATOR, %s]",buf);
				}
				else if (pcrindex == 8) {
					fprintf(fp,"[GRUB:EV_SEPARATOR, %s]",buf);
				}
				else if (len == 4 ) { // V1.2
					fprintf(fp,"[BIOS:EV_SEPARATOR, %02x%02x%02x%02x]",
						(unsigned char) buf[0],
						(unsigned char) buf[1],
						(unsigned char) buf[2],
						(unsigned char) buf[3]);
				}
				else {
					fprintf(fp,"[BIOS:EV_SEPARATOR, %s]",buf);
				} 
				break;
			case 5:
				if ((pcr5_grub > 0) && (pcrindex == 5)) {
					fprintf(fp,"[GRUB:EV_ACTION, %s]",buf);
				}
				else {
					fprintf(fp,"[BIOS:EV_ACTION, %s]",buf);
				}
				break;
			case 6:
				if ((pcr4_grub > 1) && (pcrindex == 4) ){
					fprintf(fp,"[GRUB: measure MBR again]");
				}
				else {
					fprintf(fp,"[BIOS:EV_EVENT_TAG(EV_PLATFORM_SPECIFIC)]");
				}
				break;
			case 7:
				fprintf(fp,"[BIOS:EV_S_CRTM_CONTENTS]");
				break;
			case 8:
				fprintf(fp,"[BIOS:EV_S_CRTM_VERSION]");
				break;
			case 9:
				fprintf(fp,"[BIOS:EV_CPU_MICROCODE]");
				break;
				
			case 0x0a:
				fprintf(fp,"[BIOS:EV_PLATFORM_CONFIG_FLAG)]");
				break;
			case 0x0b:
				fprintf(fp,"[BIOS:EV_TABLE_OF_CONTENTS)]");
				break;
			case 0x0c:
				fprintf(fp,"[BIOS:EV_COMPACT_HASH]");
				break;
			case 0x0d:
				if (pcr4_grub == 0) {
					// BIOS
					fprintf(fp,"[BIOS:EV_IPL]");
					pcr4_grub=1;
				}
				else if (pcr4_grub == 1) {
					// GRUB
					fprintf(fp,"[GRUB:EV_IPL, Stage1(MBR)]");
					pcr4_grub=2;
				}
				else if (pcr4_grub == 2) {
					// GRUB
					fprintf(fp,"[GRUB:EV_IPL, Stage1.5]");
					pcr4_grub=3;
				}
				else if (pcr4_grub == 3) {
					// GRUB
					fprintf(fp,"[GRUB:EV_IPL, Stage1.5(filesystem)]");
					pcr4_grub=4;
				}
				else {
					// GRUB
					fprintf(fp,"[GRUB:EV_IPL]");
				}
				break;
			case 0x0e:
				if (pcr5_grub == 0) {
					fprintf(fp,"[BIOS:EV_IPL_PERTITION_DATA]");
					pcr5_grub=1;
				}
				else {
					fprintf(fp,"[GRUB:grub.conf]");
				}
				break;
			case 0x0f:
				fprintf(fp,"[BIOS:EV_NOHOST_CODE)]");
				break;
			case 0x10:
				fprintf(fp,"[BIOS:EV_NOHOST_CONFIG]");
				break;
			case 0x11:
				fprintf(fp,"[BIOS:EV_NOHOST_INFO]");
				break;

			// GRUB 
			case 0x1005:
				fprintf(fp,"[GRUB:ACTION, %s]",buf);
				break;
			case 0x1105:
				fprintf(fp,"[GRUB:KERNEL_OPT %s]",buf);
				break;
			case 0x1205:
				fprintf(fp,"[GRUB:KERNEL %s]",buf);
				break;
			case 0x1305:
				fprintf(fp,"[GRUB:INITRD %s]",buf);
				break;
			case 0x1405:
				fprintf(fp,"[GRUB:MODULE %s]",buf);
				break;

			default:
				fprintf(fp,"[Unknown BIOS Event:size=%d] ",len);
				break;
		}
	}
	else {
		fprintf(fp,"[Unknown Event:size=%d] ",len);
	}
}

void fprintBin(FILE* fp, BYTE* data,UINT32 len) {
	int i;
	for (i=0;i<len;i++){
		fputc(data[i],fp);
	}
}
	
int main(int argc, char *argv[])
{
	TSS_RESULT result;
	TSS_HCONTEXT hContext;

	TSS_HTPM hTPM;

	//BYTE *blob;
	//UINT32 blobLength;
	//BYTE pcr[24];		// TODO
	
	UINT32 ulEventNumber = 0;
	TSS_PCR_EVENT * PcrEvents;
	//TSS_PCR_EVENT * pPcrEvent;

	int so;
	//int pcrindex;
	int opmode=1;
	int i,j;
	char *outfilename = NULL;
	int firmware = 1;
	int kernel = 1;
	
	/* Args */

	while (1) {
		so = getopt_long(argc, argv, short_option, long_option, 0);
		if (so == -1)
			break;	// END

		switch (so) {

		case 'l':	/* Plain text List */
			opmode=1;
			break;
		case 'b':	/* BIOS binary List */
			opmode=2;
			break;
		case 'f':	/* Firmware IML only */
			firmware=1;
			kernel=0;
			break;
		case 'k':	/* Kenrel IML only */
			firmware=0;
			kernel=1;
			break;
		case 'o':	/* output filemame */
			outfilename = optarg;
			break;
			
		case 'h':	/* help */
			usage();
			goto close;
		default:
			usage();
			goto close;
		}
	}

	if ((outfilename == NULL) && (opmode == 2)) {
		printf("ERROR: please set output file, since BIOS out is binary format\n");
		usage();
		goto close;
	}

	/* Connect to TCSD */

	result = Tspi_Context_Create(&hContext);
	if (result != TSS_SUCCESS) {
		printf("ERROR: Tspi_Context_Create failed rc=0x%x\n",
		       result);
		goto close;
	}

	result = Tspi_Context_Connect(hContext, SERVER);
	if (result != TSS_SUCCESS) {
		printf("ERROR: Tspi_Context_Connect failed rc=0x%x\n",
		       result);
		goto close;
	}


	/* Get TPM handles */
	result = Tspi_Context_GetTpmObject(hContext, &hTPM);
	if (result != TSS_SUCCESS) {
		printf("ERROR: Tspi_Context_GetTpmObject failed rc=0x%x\n",
		       result);
		goto close;
	}

	
	/* Get Log */
	
	result = Tspi_TPM_GetEventLog(hTPM,&ulEventNumber,&PcrEvents);
	if (result != TSS_SUCCESS) { // ERROR
		printf("ERROR: Tspi_TPM_GetEventLog failed rc=0x%x\n",
		       result);
		goto close;
	}
	else { // OK
		// Output the result
		FILE *fp = stdout;
		if (outfilename != NULL) {
			if ((fp = fopen(outfilename, "w")) == NULL) {
				printf("ERROR: File open failed, %s \n",
				       outfilename);
				result = -1;	// TODO
				goto free;
			}
		}
		
		if (opmode == 1 ){ // Plain text
			fprintf(fp," Idx PCR       Type    Digest                                EventData\n");
			fprintf(fp,"-----------------------------------------------------------------------\n");
			for (i=0;i<ulEventNumber;i++) {
								if ((firmware == 0 ) && (PcrEvents[i].ulPcrIndex <= 8)) {
					// SKIP
				}
				else if ((kernel == 0 ) && (PcrEvents[i].ulPcrIndex == 10)) {
					// SKIP
				}
				else {
					fprintf(fp,"%4d ", i);
					fprintf(fp,"%3d ", PcrEvents[i].ulPcrIndex);
					fprintf(fp,"0x%08x ", PcrEvents[i].eventType);
					for (j = 0; j < PcrEvents[i].ulPcrValueLength; j++)
						fprintf(fp,"%02x", PcrEvents[i].rgbPcrValue[j]);
					fprintf(fp," ");
		
					/* event Data */
					fprintEventData(fp,
						PcrEvents[i].rgbEvent,
						PcrEvents[i].ulEventLength,
						PcrEvents[i].ulPcrIndex,
					PcrEvents[i].eventType);
					
					fprintf(fp,"\n");
				}
			}		
		}
		else { // BIOS BINARY 
			// Ref PC Spec v1.2, p74
			// UINT32   pcrIndex
			// UINT32   eventType
			// BYTE[20] digest
			// UINT32   eventDataSize
			// BYTE[]   event
			struct biosEvent be;
			for (i=0;i<ulEventNumber;i++) {
				if ((firmware == 0 ) && (PcrEvents[i].ulPcrIndex <= 8)) {
					// SKIP
				}
				else if ((kernel == 0 ) && (PcrEvents[i].ulPcrIndex == 10)) {
					// SKIP
				}
				else {
					be.pcrIndex = PcrEvents[i].ulPcrIndex;
					be.eventType = PcrEvents[i].eventType;
					be.eventDataSize = PcrEvents[i].ulEventLength;
					memcpy(be.digest,PcrEvents[i].rgbPcrValue,20);
			
					fprintBin(fp,(BYTE *) &be,sizeof(struct biosEvent));
					fprintBin(fp,PcrEvents[i].rgbEvent,PcrEvents[i].ulEventLength);		
				}
			}	
		}
	}
	


      free:

    result = Tspi_Context_FreeMemory(hContext, (BYTE *)PcrEvents);      
	Tspi_Context_FreeMemory(hContext, NULL);

	/* Close TSS/TPM */
      close:
	Tspi_Context_Close(hContext);


	return result;
}
