dbTool.cpp   [plain text]


/* Copyright (c) 2002-2006 Apple Computer, Inc.
 *
 * dbTool.cpp - DL/DB tool.
 */
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <strings.h>
#include <ctype.h>
#include <Security/cssm.h>
#include <Security/cssmapple.h>
#include <Security/cssmapplePriv.h>
#include "cspwrap.h"
#include "common.h"
#include "dbAttrs.h"
#include "dbCert.h"
#include "cspdlTesting.h"


static void usage(char **argv)
{
	printf("usage: %s dbFileName command [options]\n", argv[0]);
	printf("Commands:\n");
	printf("   r   Dump Schema Relations\n");
	printf("   k   Dump all keys\n");
	printf("   c   Dump certs\n");
	printf("   a   Dump all records\n");
	printf("   d   Delete records (interactively)\n");
	printf("   D   Delete records (noninteractively, requires really arg)\n");
	printf("   i   Import bad cert and its (good) private key\n");
	printf("Options:\n");
	printf("   v   verbose\n");
	printf("   q   quiet\n");
	printf("   R   really! (for D command)\n");
	printf("   d   dump data\n");
	printf("   c=certFile\n");
	printf("   k=keyFile\n");
	exit(1);
}


static unsigned indentVal = 0;
static void indentIncr()
{
	indentVal += 3;
}

static void indentDecr()
{
	if(indentVal) {
		indentVal -= 3;
	}
}

static void doIndent()
{
	unsigned i;
	for(i=0; i<indentVal; i++) {
		printf(" ");
	}
}

#define NORM_KEY_LEN	20

/* print an attribute name, padding out to NORM_KEY_LEN columns */
static void printName(
	const CSSM_DB_ATTRIBUTE_INFO *attrInfo)
{
	switch(attrInfo->AttributeNameFormat) {
		case CSSM_DB_ATTRIBUTE_NAME_AS_STRING:
		{
			char *attrName = attrInfo->Label.AttributeName;
			printf("%s", attrName);
			int len = strlen(attrName);
			if(len > NORM_KEY_LEN) {
				return;
			}
			int numSpaces = NORM_KEY_LEN - len;
			for(int i=0; i<numSpaces; i++) {
				putchar(' ');
			}
			break;
		}
		case CSSM_DB_ATTRIBUTE_NAME_AS_INTEGER:
		{
			/* OSType, endian dependent... */
			char *cp = (char *)&(attrInfo->Label.AttributeID);
			for(unsigned i=0; i<4; i++) {
				putchar(*cp++);
			}
			printf("                ");
			break;
		}
		default:
			printf("Unknown attribute name format (%u)\n", 
				(unsigned)attrInfo->AttributeNameFormat);
			break;
	}
}

/*
 * Attempt to print a numeric value as a string, per a NameValuePair table.
 * If the value is in fact a collection of legal values (per the nameValues
 * array), the value will just be printed in hex.  
 */
static void printValueAsString(
	unsigned val, 
	const NameValuePair *nameValues)
{
	if(nameValues != NULL) {
		while(nameValues->name != NULL) {
			if(nameValues->value == val) {
				printf("%s", nameValues->name);
				return;
			}
			nameValues++;
		}
	}
	/* Oh well */
	printf("0x%x", val);
}

static void safePrint(
	uint8 *cp, 
	uint32 len)
{
	for(unsigned i=0; i<len; i++) {
		printf("%c", *cp++);
	}
}

/* See if a blob is printable. Used for BLOB and UINT32 types, the latter of 
 * which is sometimes used for OSType representation of attr name. */
bool isPrintable(
	const CSSM_DATA *dp)
{
	bool printable = true;
	uint8 *cp = dp->Data;
	for(unsigned i=0; i<dp->Length; i++) {
		if(*cp == 0) {
			if(i != (dp->Length - 1)) {
				/* data contains NULL character before end */
				printable = false;
			}
			/* else end of string */
			break;
		}
		if(!isprint(*cp)) {
			printable = false;
			break;
		}
		cp++;
	}
	return printable;
}

#define MAX_BLOB_TO_PRINT	12
static void printBlob(
	const CSSM_DATA *data)
{
	unsigned toPrint = data->Length;
	if(toPrint > MAX_BLOB_TO_PRINT) {
		toPrint = MAX_BLOB_TO_PRINT;
	}
	for(unsigned i=0; i<toPrint; i++) {
		unsigned dat = data->Data[i];
		printf("%02X ", dat);
	}
	if(toPrint < data->Length) {
		printf("...");
	}
}

static void printAttrData(
	const CSSM_DB_ATTRIBUTE_INFO *attrInfo,
	const CSSM_DATA *attrData,
	const NameValuePair *nameValues)		// optional
{
	void *data = attrData->Data;
	
	switch(attrInfo->AttributeFormat) {
	
		case CSSM_DB_ATTRIBUTE_FORMAT_STRING:
			putchar('\'');
			safePrint(attrData->Data, attrData->Length);
			putchar('\'');
			break;
		case CSSM_DB_ATTRIBUTE_FORMAT_SINT32:
		case CSSM_DB_ATTRIBUTE_FORMAT_UINT32:
		{
			unsigned val = *(unsigned *)data;
			printValueAsString(val, nameValues);
			break;
		}
		case CSSM_DB_ATTRIBUTE_FORMAT_BLOB:
		{
			printf("BLOB length %u : ", (unsigned)attrData->Length);
			/* see if it happens to be a printable string */
			if(isPrintable(attrData)) {
				putchar('\'');
				safePrint(attrData->Data, attrData->Length);
				putchar('\'');
			}
			else {
				printBlob(attrData);
			}
			break;
		}
		case CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32:
		{
			printf("multi_int[");
			uint32 numInts = attrData->Length / sizeof(uint32);
			uint32 *uip = (uint32 *)data;
			for(unsigned i=0; i<numInts; i++) {
				if(i > 0) {
					printf(", ");
				}
				printValueAsString(*uip++, nameValues);
			}
			printf("]");
			break;
		}
		case CSSM_DB_ATTRIBUTE_FORMAT_TIME_DATE:
			putchar('\'');
			safePrint(attrData->Data, attrData->Length);
			putchar('\'');
			break;
			
		default:
			printf("***UNKNOWN FORMAT (%u), Length %u",
				(unsigned)attrInfo->AttributeFormat, (unsigned)attrData->Length);
			break;
	}
}

/* free attribute(s) allocated by DL */
static void freeAttrs(
	CSSM_DB_RECORD_ATTRIBUTE_DATA *recordAttrs)
{
	unsigned i;
	
	for(i=0; i<recordAttrs->NumberOfAttributes; i++) {
		CSSM_DB_ATTRIBUTE_DATA_PTR attrData = &recordAttrs->AttributeData[i];
		if(attrData == NULL) {
			/* fault of caller, who allocated the CSSM_DB_ATTRIBUTE_DATA */
			printf("***freeAttrs screwup: NULL attrData\n");
			return;
		}
		unsigned j;
		for(j=0; j<attrData->NumberOfValues; j++) {
			CSSM_DATA_PTR data = &attrData->Value[j];
			if(data == NULL) {
				/* fault of MDS, who said there was a value here */
				printf("***freeAttrs screwup: NULL data\n");
				return;
			}
			CSSM_FREE(data->Data);
			data->Data = NULL;
			data->Length = 0;
		}
		CSSM_FREE(attrData->Value);
		attrData->Value = NULL;
	}
}

static void dumpDataBlob(
	const CSSM_DATA *datap)
{
	doIndent();
	printf("Record data length %lu ", datap->Length);
	if(datap->Length != 0) {
		printf(" : ");
		printBlob(datap);
	}
	printf("\n");
}

static void dumpRecordAttrs(
	const CSSM_DB_RECORD_ATTRIBUTE_DATA *recordAttrs,
	const NameValuePair 				**nameValues,		// parallel to recordAttrs
	const CSSM_DATA						*recordData = NULL)	// optional data
{
	unsigned valNum;
	unsigned dex;

	for(dex=0; dex<recordAttrs->NumberOfAttributes; dex++) {
		const CSSM_DB_ATTRIBUTE_DATA *attrData = &recordAttrs->AttributeData[dex];
		doIndent();
		printName(&attrData->Info);
		printf(": ");
		if(attrData->NumberOfValues == 0) {
			printf("<<not present>>\n");
			continue;
		}
		for(valNum=0; valNum<attrData->NumberOfValues; valNum++) {
			printAttrData(&attrData->Info, &attrData->Value[valNum], nameValues[dex]);
			if(valNum < (attrData->NumberOfValues - 1)) {
				printf(", ");
			}
		}
		printf("\n");
	}
	if(recordData) {
		dumpDataBlob(recordData);
	}
}

static void dumpRelation(
	CSSM_DL_DB_HANDLE 		dlDbHand, 
	const RelationInfo		*relInfo,
	CSSM_BOOL				dumpData)
{
	CSSM_QUERY						query;
	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
	CSSM_RETURN						crtn;
	CSSM_HANDLE						resultHand;
	CSSM_DB_ATTRIBUTE_DATA			*attrs;
	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
	unsigned 						attrDex;
	unsigned 						recNum = 0;
	uint32							numAttrs = relInfo->NumberOfAttributes;
	CSSM_DATA						data = {0, NULL};
	CSSM_DATA_PTR					datap = NULL;
	
	if(dumpData) {
		datap = &data;
	}
	
	/* build an attr array from known schema */
	attrs = (CSSM_DB_ATTRIBUTE_DATA *)CSSM_MALLOC(
		sizeof(CSSM_DB_ATTRIBUTE_DATA) * numAttrs);
	memset(attrs, 0, sizeof(CSSM_DB_ATTRIBUTE_DATA) * numAttrs);
	for(attrDex=0; attrDex<numAttrs; attrDex++) {
		attrs[attrDex].Info = relInfo->AttributeInfo[attrDex];
	}
	recordAttrs.DataRecordType = relInfo->DataRecordType;
	recordAttrs.NumberOfAttributes = numAttrs;
	recordAttrs.AttributeData = attrs;
	
	/* just search by recordType, no predicates */
	query.RecordType = relInfo->DataRecordType;
	query.Conjunctive = CSSM_DB_NONE;
	query.NumSelectionPredicates = 0;
	query.SelectionPredicate = NULL;
	query.QueryLimits.TimeLimit = 0;			// FIXME - meaningful?
	query.QueryLimits.SizeLimit = 1;			// FIXME - meaningful?
	query.QueryFlags = 0;		// CSSM_QUERY_RETURN_DATA...FIXME - used?

	crtn = CSSM_DL_DataGetFirst(dlDbHand,
		&query,
		&resultHand,
		&recordAttrs,
		datap,
		&record);
	switch(crtn) {
		case CSSM_OK:
			break;		// proceed
		case CSSMERR_DL_ENDOFDATA:
			printf("%s: no record found\n", relInfo->relationName);
			CSSM_FREE(attrs);
			return;
		default:
			printError("DataGetFirst", crtn);
			CSSM_FREE(attrs);
			return;
	}
	printf("%s:\n", relInfo->relationName);
	printf("   record %d; numAttrs %d:\n", 
		recNum++, (int)recordAttrs.NumberOfAttributes);
	indentIncr();
	
	dumpRecordAttrs(&recordAttrs, relInfo->nameValues, datap);
	CSSM_DL_FreeUniqueRecord(dlDbHand, record);
	freeAttrs(&recordAttrs);
	if(datap) {
		CSSM_FREE(datap->Data);
	}
	
	/* now the rest of them */
	/* hopefully we don't have to re-init the recordAttr array */
	for(;;) {
		crtn = CSSM_DL_DataGetNext(dlDbHand,
			resultHand, 
			&recordAttrs,
			datap,
			&record);
		switch(crtn) {
			case CSSM_OK:
				printf("   record %d; numAttrs %d:\n", 
					recNum++, (int)recordAttrs.NumberOfAttributes);
				dumpRecordAttrs(&recordAttrs, relInfo->nameValues, datap);
				CSSM_DL_FreeUniqueRecord(dlDbHand, record);
				freeAttrs(&recordAttrs);
				if(datap) {
					CSSM_FREE(datap->Data);
				}
				break;		// and go again 
			case CSSMERR_DL_ENDOFDATA:
				/* normal termination */
				break;
			default:
				printError("DataGetNext", crtn);
				break;
		}
		if(crtn != CSSM_OK) {
			break;
		}
	}
	indentDecr();
	CSSM_FREE(attrs);
}

/*
 * Given a record type and a CSSM_DB_UNIQUE_RECORD, fetch and parse all the 
 * attributes we can.
 */
static void fetchParseRecord(
	CSSM_DL_DB_HANDLE				dlDbHand,
	CSSM_DB_RECORD_ATTRIBUTE_DATA	*inRecordAttrs,
	CSSM_DB_UNIQUE_RECORD_PTR		record,
	const CSSM_DATA_PTR				datap,
	CSSM_BOOL						dumpData)
{
	const RelationInfo *relInfo = NULL;
	
	/* infer RelationInfo from recordType */
	switch(inRecordAttrs->DataRecordType) {
		case CSSM_DL_DB_RECORD_PUBLIC_KEY:
		case CSSM_DL_DB_RECORD_PRIVATE_KEY:
		case CSSM_DL_DB_RECORD_SYMMETRIC_KEY:
			relInfo = &allKeysRelation;
			break;
		case CSSM_DL_DB_RECORD_GENERIC_PASSWORD:
		case CSSM_DL_DB_RECORD_INTERNET_PASSWORD:
		case CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD:
			relInfo = &genericKcRelation;
			break;
		case CSSM_DL_DB_RECORD_CERT:
			relInfo = &certRecordRelation;
			break;
		case CSSM_DL_DB_RECORD_X509_CERTIFICATE:
			relInfo = &x509CertRecordRelation;
			break;
		case CSSM_DL_DB_RECORD_X509_CRL:
			relInfo = &x509CrlRecordRelation;
			break;
		case CSSM_DL_DB_RECORD_USER_TRUST:
			relInfo = &userTrustRelation;
			break;
		case CSSM_DL_DB_RECORD_UNLOCK_REFERRAL:
			relInfo = &referralRecordRelation;
			break;
		case CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE:
			relInfo = &extendedAttrRelation;
			break;
		case DBBlobRelationID:
			relInfo = NULL;
			doIndent();
			printf("--- No attributes ---\n");
			if(dumpData) {
				dumpDataBlob(datap);
			}
			return;
		default:
			doIndent();
			printf("<<unparsed>>\n");
			if(dumpData) {
				doIndent();
				printf("Record blob (length %ld): ", datap->Length);
				printBlob(datap);
				printf("\n");
			}
			return;
	}
	
	CSSM_DB_ATTRIBUTE_DATA			*attrs = NULL;
	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
	unsigned 						attrDex;
	uint32							numAttrs = relInfo->NumberOfAttributes;
	CSSM_RETURN						crtn;
	CSSM_DATA 						recordData = {0, NULL};
	CSSM_DATA_PTR					recordDataP = dumpData ? &recordData : NULL;
	
	/* build an attr array from known schema */
	attrs = (CSSM_DB_ATTRIBUTE_DATA *)CSSM_MALLOC(
		sizeof(CSSM_DB_ATTRIBUTE_DATA) * numAttrs);
	memset(attrs, 0, sizeof(CSSM_DB_ATTRIBUTE_DATA) * numAttrs);
	for(attrDex=0; attrDex<numAttrs; attrDex++) {
		attrs[attrDex].Info = relInfo->AttributeInfo[attrDex];
	}

	/* from inRecordAttrs, not the relInfo, which could be a typeless template */
	recordAttrs.DataRecordType = relInfo->DataRecordType;
	recordAttrs.NumberOfAttributes = numAttrs;
	recordAttrs.AttributeData = attrs;

	crtn = 	CSSM_DL_DataGetFromUniqueRecordId(dlDbHand,
		record,
		&recordAttrs,
		recordDataP);
	if(crtn) {
		printError("CSSM_DL_DataGetFromUniqueRecordId", crtn);
		goto abort;
	}
	dumpRecordAttrs(&recordAttrs, relInfo->nameValues, recordDataP);
	freeAttrs(&recordAttrs);
	if(recordData.Data) {
		CSSM_FREE(recordData.Data);
	}
abort:
	if(attrs) {
		CSSM_FREE(attrs);
	}
	return;
}
	
static void deleteRecord(
	CSSM_DL_DB_HANDLE 			dlDbHand,
	CSSM_DB_UNIQUE_RECORD_PTR	record,
	CSSM_BOOL					interact)
{
	if(interact) {
		fpurge(stdin);
		printf("\nDelete this record [y/anything] ? ");
		char resp = getchar();
		if(resp != 'y') {
			return;
		}
	}
	CSSM_RETURN crtn;
	crtn = CSSM_DL_DataDelete(dlDbHand, record);
	if(crtn) {
		printError("CSSM_DL_DataDelete", crtn);
	}
	else if(interact) {
		printf("...record deleted\n\n");
	}
}

/*
 * In this case we search for CSSM_DL_DB_RECORD_ANY. The current schema results
 * in no single attribute which all interesting records have in common, so we
 * can't grab any attributes at GetFirst/GetNext time. Instead we have
 * to deal with the returned record per its record type. 
 */
static void dumpAllRecords(
	CSSM_DL_DB_HANDLE 		dlDbHand,
	CSSM_BOOL				deleteAll,
	CSSM_BOOL				interact,
	CSSM_BOOL				dumpData)
{
	CSSM_QUERY						query;
	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
	CSSM_RETURN						crtn;
	CSSM_HANDLE						resultHand;
	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
	CSSM_DATA						data = {0, NULL};
	CSSM_DATA_PTR					datap = NULL;
	
	if(dumpData) {
		datap = &data;
	}
	
	recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_ANY;
	recordAttrs.NumberOfAttributes = 0;
	recordAttrs.AttributeData = NULL;
	
	/* just search by recordType, no predicates */
	query.RecordType = CSSM_DL_DB_RECORD_ANY;
	query.Conjunctive = CSSM_DB_NONE;
	query.NumSelectionPredicates = 0;
	query.SelectionPredicate = NULL;
	query.QueryLimits.TimeLimit = 0;			// FIXME - meaningful?
	query.QueryLimits.SizeLimit = 1;			// FIXME - meaningful?
	query.QueryFlags = 0;		// CSSM_QUERY_RETURN_DATA...FIXME - used?

	crtn = CSSM_DL_DataGetFirst(dlDbHand,
		&query,
		&resultHand,
		&recordAttrs,
		datap,	
		&record);
	switch(crtn) {
		case CSSM_OK:
			break;		// proceed
		case CSSMERR_DL_ENDOFDATA:
			printf("CSSM_DL_DB_RECORD_ANY: no record found\n");
			return;
		default:
			printError("DataGetFirst", crtn);
			return;
	}

	/* could be anything; check it out */
	if(interact) {
		doIndent();
		printValueAsString(recordAttrs.DataRecordType, recordTypeNames);
		printf("\n");
		indentIncr();
		fetchParseRecord(dlDbHand, &recordAttrs, record, datap, dumpData);
		indentDecr();
	}
	if(deleteAll && (recordAttrs.DataRecordType != DBBlobRelationID)) {
		/* NEVER delete a DBBlob */
		deleteRecord(dlDbHand, record, interact);
	}
	CSSM_DL_FreeUniqueRecord(dlDbHand, record);
	
	/* now the rest of them */
	/* hopefully we don't have to re-init the recordAttr array */
	for(;;) {
		crtn = CSSM_DL_DataGetNext(dlDbHand,
			resultHand, 
			&recordAttrs,
			datap,
			&record);
		switch(crtn) {
			case CSSM_OK:
				if(interact) {
					doIndent();
					printValueAsString(recordAttrs.DataRecordType, recordTypeNames);
					printf("\n");
					indentIncr();
					fetchParseRecord(dlDbHand, &recordAttrs, record, datap, dumpData);
					indentDecr();
				}
				if(deleteAll && (recordAttrs.DataRecordType != DBBlobRelationID)) {
					/* NEVER delete a DBBlob */
					deleteRecord(dlDbHand, record, interact);
				}
				CSSM_DL_FreeUniqueRecord(dlDbHand, record);
				break;		// and go again 
			case CSSMERR_DL_ENDOFDATA:
				/* normal termination */
				break;
			default:
				printError("DataGetNext", crtn);
				break;
		}
		if(crtn != CSSM_OK) {
			break;
		}
	}
}

int main(
	int argc, 
	char **argv)
{
	int					arg;
	char				*argp;
	char				*dbFileName;
	char				cmd;
	CSSM_DL_DB_HANDLE	dlDbHand;
	CSSM_BOOL			verbose = CSSM_FALSE;
	CSSM_BOOL			quiet = CSSM_FALSE;
	char				*certFile = NULL;
	char				*keyFile = NULL;
	CSSM_BOOL			interact = CSSM_TRUE;
	CSSM_BOOL			dumpData = CSSM_FALSE;
	
	/* should be cmd line opts */
	CSSM_ALGORITHMS		keyAlg = CSSM_ALGID_RSA;
	CSSM_BOOL			pemFormat = CSSM_FALSE;
	CSSM_KEYBLOB_FORMAT	keyFormat = CSSM_KEYBLOB_RAW_FORMAT_NONE;
	CSSM_RETURN 		crtn = CSSM_OK;
	
	if(argc < 3) {
		usage(argv);
	}
	dbFileName = argv[1];
	cmd = argv[2][0];
	
	for(arg=3; arg<argc; arg++) {
		argp = argv[arg];
		switch(argp[0]) {
			case 'v':
				verbose = CSSM_TRUE;
				break;
			case 'q':
				quiet = CSSM_TRUE;
				break;
			case 'R':
				if(cmd == 'D') {
					interact = CSSM_FALSE;
				}
				break;
			case 'd':
				dumpData = CSSM_TRUE;
				break;
			case 'c':
				certFile = &argp[2];
				break;
			case 'k':
				keyFile = &argp[2];
				break;
		    case 'h':
		    default:
				usage(argv);
		}
	}
	
	dlDbHand.DLHandle = dlStartup();
	if(dlDbHand.DLHandle == 0) {
		exit(1);
	}
	if(cmd == 'i') {
		crtn = importBadCert(dlDbHand.DLHandle, dbFileName, certFile, 
			keyFile, keyAlg, pemFormat, keyFormat, verbose);
		goto done;
	}
	crtn = dbCreateOpen(dlDbHand.DLHandle, dbFileName, 
		CSSM_FALSE, CSSM_FALSE, NULL, &dlDbHand.DBHandle);
	if(crtn) {
		exit(1);
	}
	switch(cmd) {
		case 'r':
			dumpRelation(dlDbHand, &schemaInfoRelation, dumpData);
			break;
		case 'k':
			dumpRelation(dlDbHand, &allKeysRelation, dumpData);
			break;
		case 'c':
			dumpRelation(dlDbHand, &x509CertRecordRelation, dumpData);
			break;
		case 'a':
			dumpAllRecords(dlDbHand, CSSM_FALSE, CSSM_TRUE, dumpData);
			break;
		case 'd':
		case 'D':
			dumpAllRecords(dlDbHand, CSSM_TRUE, interact, dumpData);
			if(!interact) {
				/* we ignored errors.... */
				if(!quiet) {
					printf("...DB %s wiped clean\n", dbFileName);
				}
			}
			break;
		default:
			usage(argv);
	}
	CSSM_DL_DbClose(dlDbHand);
done:
	CSSM_ModuleDetach(dlDbHand.DLHandle);
	return crtn;
}