/*
 * This function will set up a session to the TSM server and return the
 * session handler
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <termio.h>
#include <unistd.h>

#include "dsmrc.h"
#include "dsmapitd.h"
#include "dsmapifp.h"

#include "../tsmpipe.h"

dsBool_t compressEnabled=bFalse; // Is compression enabled

/* Print out TSM Error Code */
char *tsm_printerr(dsUint32_t dsmHandle, dsInt16_t rc) {
	char rcStr[DSM_MAX_RC_MSG_LENGTH];
	char *s;

	s = malloc(sizeof(rcStr));

	if (rc == DSM_RC_WILL_ABORT) {
		dsUint16_t reason;

		rc = dsmEndTxn(dsmHandle, DSM_VOTE_COMMIT, &reason);

		if (rc == DSM_RC_CHECK_REASON_CODE)
			rc = reason;
	}

	dsmRCMsg(dsmHandle,rc,rcStr);

	strtok(rcStr, "\n");

	sprintf(s,"[rc=%s]",rcStr);

	return s;
}

/* Check the TSM API Version */
int tsm_checkapi(void) {
	dsmApiVersionEx apiLibVer;
	dsUint32_t apiVersion;
	dsUint32_t applVersion = (10000 * DSM_API_VERSION) + (1000 * DSM_API_RELEASE) + (100 * DSM_API_LEVEL) + DSM_API_SUBLEVEL;

	memset(&apiLibVer,0x00,sizeof(dsmApiVersionEx));
	apiLibVer.stVersion = apiVersionExVer;
	dsmQueryApiVersionEx(&apiLibVer);

	apiVersion = (10000 * apiLibVer.version) + (1000 * apiLibVer.release) + (100 * apiLibVer.level) + apiLibVer.subLevel;

	if (apiVersion < applVersion) {
		printf("The Tivoli Storage Manager API library Version (%d.%d.%d.%d) is at a lower version than the application version (%d.%d.%d.%d)\n",
			apiLibVer.version,
			apiLibVer.release,
			apiLibVer.level,
			apiLibVer.subLevel,
			DSM_API_VERSION,
			DSM_API_RELEASE,
			DSM_API_LEVEL,
			DSM_API_SUBLEVEL);

		printf("Please upgrade the API accordingly.\n");

		return 0;
	}

	return 1;
}

/* Initialise a session to the TSM server */
dsUint32_t tsm_initsess(char *options, char *password) {
	dsInt16_t rc=0;
	dsUint32_t dsmHandle=0;
	dsmApiVersionEx applApi;
	dsmInitExIn_t initIn;
	dsmInitExOut_t initOut;
	optStruct dsmOpt;
	ApiSessInfo dsmSessInfo;

	if (! tsm_checkapi()) {
		exit(2);
	}

	memset(&applApi,0x00,sizeof(dsmApiVersionEx));
	applApi.stVersion = apiVersionExVer;
	applApi.version   = DSM_API_VERSION;
	applApi.release   = DSM_API_RELEASE;
	applApi.level     = DSM_API_LEVEL;
	applApi.subLevel  = DSM_API_SUBLEVEL;

	memset(&initIn,0x00,sizeof(dsmInitExIn_t));
	initIn.stVersion           = dsmInitExInVersion;
	initIn.apiVersionExP       = &applApi;
	initIn.clientNodeNameP     = NULL;
	initIn.clientOwnerNameP    = NULL;
	initIn.clientPasswordP     = password;
	initIn.applicationTypeP    = NULL;
	initIn.configfile          = NULL;
	initIn.options             = options;
	initIn.userNameP           = NULL;
	initIn.userPasswordP       = NULL;
	//initIn.dirDelimiter        = dirDelimiter;
	//initIn.useUnicode          = useUnicode;
	//initIn.bEncryptKeyEnabled  = encrypt;
	//initIn.encryptionPasswordP = encryptKey;

	memset(&initOut,0x00,sizeof(dsmInitExOut_t));
	initOut.stVersion = dsmInitExOutVersion;

	rc = dsmInitEx(&dsmHandle, &initIn, &initOut);

	/* If the TSM password has expired, change it. */
	if (rc == DSM_RC_REJECT_VERIFIER_EXPIRED) {
		rc = dsmChangePW(dsmHandle,NULL,NULL);

		if (rc != DSM_RC_OK) {
			printf("%s: dsmChangePW() failed %s\n",__func__,tsm_printerr(dsmHandle,rc));
			dsmTerminate(dsmHandle);

			return 0;
		}

	} else if (rc != DSM_RC_OK) {
		printf("%s: dsmInitEx() failed %s\n",__func__,tsm_printerr(dsmHandle, rc));
		dsmTerminate(dsmHandle);

		return 0;
	}

	// Make sure if we have compression enabled, that we also have COMPRESSAlways YES
	memset(&dsmOpt,0x00,sizeof(dsmOpt));
	rc = dsmQuerySessOptions(dsmHandle, &dsmOpt);

	memset(&dsmSessInfo,0x00,sizeof(ApiSessInfo));
	dsmSessInfo.stVersion = ApiSessInfoVersion; /* Init struct version */
	rc = dsmQuerySessInfo(dsmHandle, &dsmSessInfo);

	rc = 1;
	switch (dsmSessInfo.compression) {
		case COMPRESS_YES : compressEnabled = bTrue; break;
		case COMPRESS_NO : compressEnabled = bFalse; break;
		case COMPRESS_CD : compressEnabled = dsmOpt.compression; break;
		default: rc=0;
	}

	if (!rc || (compressEnabled && ! dsmOpt.compressalways)) {
		debugLog(0,__func__,!rc ? "ERROR: Error Querying Session Capabilities" : "ERROR: COMPRESSion is ENABLED, but not COMPRESSAlways Yes",1);
	}

	return dsmHandle;
}

/* List objects that are in TSM */
int tsm_sessioninfo(dsUint32_t dsmHandle) {
	extern int verbose;
	dsInt16_t rc=0;
	optStruct dsmOpt;
	ApiSessInfo dsmSessInfo;
	dsmApiVersionEx apiLibVer;
	char t[50];

	memset(&apiLibVer,0x00,sizeof(dsmApiVersionEx));
	apiLibVer.stVersion = apiVersionExVer;
	dsmQueryApiVersionEx(&apiLibVer);

	printf("Application Version:\n");
	printf(" TSMPIPE Version:                %s\n",
			_TSMPIPE_VERSION);
	printf(" TSMPIPE API Version:            %d.%d.%d.%d\n",
			DSM_API_VERSION,
			DSM_API_RELEASE,
			DSM_API_LEVEL,
			DSM_API_SUBLEVEL);

	printf(" TSM Library:                    %d.%d.%d.%d\n",
			apiLibVer.version,
			apiLibVer.release,
			apiLibVer.level,
			apiLibVer.subLevel);
	printf("\n");

	memset(&dsmOpt,0x00,sizeof(dsmOpt));
	rc = dsmQuerySessOptions(dsmHandle, &dsmOpt);

	if (rc)
		printf("%s: ERROR dsmQuerySessOptions() unable to query Session Options. RC = %d\n",__func__,rc);
	else {
		printf("dsmQuerySessOptions:\n");
		printf(" DSMI_DIR:                       %s\n",dsmOpt.dsmiDir);
		printf(" DSMI_CONFIG:                    %s\n",dsmOpt.dsmiConfig);
		printf(" SErvername:                     %s\n",dsmOpt.serverName);

		switch (dsmOpt.commMethod) {
			case DSM_COMM_TCP : strcpy(t,"TCP/IP"); break;
			case DSM_COMM_NAMEDPIPE : strcpy(t,"NAMED PIPE"); break;
			case DSM_COMM_SHM : strcpy(t,"SHARED MEMORY"); break;
			case DSM_COMM_TCPIP6 : strcpy(t,"TCP/IP v6"); break;
			default: strcpy(t,"UNKNOWN");
		}

		printf(" COMMmethod:                     %s\n",t);
		if (verbose)
			fprintf(stderr,"%s: commMethod: [%d]\n",__func__,dsmOpt.commMethod);

		printf(" TCPServerAddress:               %s\n",dsmOpt.serverAddress);
		if (verbose)
			fprintf(stderr,"%s: serverAddress: [%s]\n",__func__,dsmOpt.serverAddress);
		printf(" NODEName:                       %s\n",dsmOpt.nodeName);
		printf(" COMPRESSIon:                    %s\n",dsmOpt.compression ? "YES" : "NO");
		printf(" COMPRESSAlways:                 %s\n",dsmOpt.compressalways ? "YES" : "NO");
		printf(" PASSWORDAccess:                 %s\n",dsmOpt.passwordAccess ? "GENERATE" : "PROMPT");
	}

	memset(&dsmSessInfo,0x00,sizeof(ApiSessInfo));
	dsmSessInfo.stVersion = ApiSessInfoVersion; /* Init struct version */
	rc = dsmQuerySessInfo(dsmHandle, &dsmSessInfo);

	if (rc)
		printf("%s: ERROR dsmQuerySessInfo() unable to query Session Info. RC = %d\n",__func__,rc);
	else {
		printf("\n");
		printf("dsmQuerySessInfo:\n");
		printf(" Server Information:\n");
		printf("  Name:                          %s\n",dsmSessInfo.adsmServerName);
		printf("  Host:                          %s\n",dsmSessInfo.serverHost);
		printf("  Port:                          %u\n",dsmSessInfo.serverPort);
		printf("  Date:                          %s\n",dsmDateToStr(dsmSessInfo.serverDate));
		printf("  Type:                          %s\n",dsmSessInfo.serverType);
		printf("  Version:                       %i.%i.%i.%i\n",
			dsmSessInfo.serverVer,
			dsmSessInfo.serverRel,
			dsmSessInfo.serverLev,
			dsmSessInfo.serverSubLev);
		printf("  Archive Retention Protection : %s\n",dsmSessInfo.archiveRetentionProtection ?  "YES" : "NO");
		printf("\n");
		printf(" Client Information:\n");
		printf("  Node:                          %s\n",dsmSessInfo.id);
		printf("  Node Type:                     %s\n",dsmSessInfo.nodeType);
		printf("  Filespace delimiter:           %c\n",dsmSessInfo.fsdelim);
		printf("  HL & LL delimiter:             %c\n",dsmSessInfo.hldelim);

		switch (dsmSessInfo.compression) {
			case COMPRESS_YES : strcpy(t,"ON"); break;
			case COMPRESS_NO : strcpy(t,"OFF"); break;
			case COMPRESS_CD : strcpy(t,"CLIENT"); break;
			default: strcpy(t,"UNKNOWN");
		}
		printf("  Client compression:            %s\n",t);

		switch (dsmSessInfo.archDel) {
			case ARCHDEL_YES : strcpy(t,"YES"); break;
			case ARCHDEL_NO : strcpy(t,"NO"); break;
			default: strcpy(t,"UNKNOWN");
		}
		printf("  ARCHIVE Delete:                %s\n",t);

		switch (dsmSessInfo.backDel) {
			case BACKDEL_YES : strcpy(t,"YES"); break;
			case ARCHDEL_NO : strcpy(t,"NO"); break;
			default: strcpy(t,"UNKNOWN");
		}
		printf("  BACKUP delete:                 %s\n",t);

		printf("  MAX objects per transactions:  %u\n", dsmSessInfo.maxObjPerTxn);
		printf("  LAN FREE Enabled:              %s\n",dsmSessInfo.lanFreeEnabled ? "YES" : "NO");
		printf("  Deduplication:                 %s\n",dsmSessInfo.dedupType ==  dedupClientOrServer ? "Client Or Server" : "Server Only");
		printf("\n");
		printf(" General session info:\n");
		printf("  Owner:                         %s\n",dsmSessInfo.owner);
		printf("  API Config file:               %s\n",dsmSessInfo.confFile);
		printf("\n");
		printf(" Policy Information:\n");
		printf("  Domain name:                   %s\n",dsmSessInfo.domainName);
		printf("  Policyset name:                %s\n",dsmSessInfo.policySetName);
		printf("  Policy activation date:        %s\n",dsmDateToStr(dsmSessInfo.polActDate));
		printf("  Default management class:      %s\n",dsmSessInfo.dfltMCName);
		printf("  BACKUP retention grace:        %u days\n",dsmSessInfo.gpBackRetn);
		printf("  ARCHIVE retention grace:       %u days\n",dsmSessInfo.gpArchRetn);
		printf("\n");
	}

	return 1;
}

/*
 * Query TSM for a list of objects.
 *
 * We use a callback to process the list, the callback should return:
 * -1 upon error condition, application should exit.
 *  0 if tsm_queryfile() should skip processing the remaining matches.
 *  1 otherwise.
 */
dsInt16_t tsm_queryfile(dsUint32_t dsmHandle, dsmQueryType qType, tsm_query_callback usercb, void *userdata, qryArchiveData qaData, qryBackupData qbData, dsBool_t friendly) {
	extern int verbose;
	dsInt16_t rc=0;
	dsmQueryBuff *qDataP;

	DataBlk qResp;
	memset(&qResp,0x00,sizeof(DataBlk));
	qResp.stVersion = DataBlkVersion;

	switch (qType) {
		case qtArchive:
			qDataP = &qaData;

			qryRespArchiveData qaResp;

			qResp.bufferLen = sizeof(qaResp);
			qResp.bufferPtr = (char *) &qaResp;

			qaResp.stVersion = qryRespArchiveDataVersion;

			if (verbose)
				fprintf(stderr,"%s: Query filespace %s\n",__func__,dsmObjnameToStr(*qaData.objName));

			break;

		case qtBackup:
			qDataP = &qbData;

			qryRespBackupData qbResp;

			qResp.bufferLen = sizeof(qbResp);
			qResp.bufferPtr = (char *) &qbResp;

			qbResp.stVersion = qryRespBackupDataVersion;

			if (verbose)
				fprintf(stderr,"%s: Query filespace %s\n",__func__,dsmObjnameToStr(*qbData.objName));
			if (verbose > 1) {
				fprintf(stderr,"%s: PIT Date: %s\n",__func__,dsmDateToStr(qbData.pitDate));
				fprintf(stderr,"%s: OBJSTATE : %s\n",__func__,qbData.objState==DSM_ACTIVE ? "DSM_ACTIVE" : "DSM_ANY_MATCH");
			}

			break;

		default:
			fprintf(stderr,"%s: UNKNOWN Type %d\n",__func__,qType);
			exit(1);
	}

	rc = dsmBeginQuery(dsmHandle, qType, qDataP);
	if (rc != DSM_RC_OK) {
		printf("%s: dsmBeginQuery() failed %s\n",__func__,tsm_printerr(dsmHandle,rc));
		return rc;
	}

	/* Loop and apply our callback to the result */
	while ((rc = dsmGetNextQObj(dsmHandle, &qResp)) == DSM_RC_MORE_DATA) {
		int cbret;

		if(verbose > 1) {
			dsmObjName *rObjName;

			if (qType == qtArchive) {
				qryRespArchiveData *qr = (void *) qResp.bufferPtr;
				rObjName = &(qr->objName);

			} else if (qType == qtBackup) {
				qryRespBackupData *qr = (void *) qResp.bufferPtr;
				rObjName = &(qr->objName);

			} else {
				fprintf(stderr,"%s: UNKNOWN Type %d\n",__func__,qType);

				return DSM_RC_UNKNOWN_ERROR;
			}

			fprintf(stderr,"%s: Match file %s\n",__func__,dsmObjnameToStr(*rObjName));
		}

		if (usercb == NULL)
			continue;

		cbret = usercb(qType,&qResp,userdata,friendly);
		if (cbret < 0) {
			return DSM_RC_UNKNOWN_ERROR;

		} else if(cbret == 0) {
			break;
		}
	}

	if (rc != DSM_RC_FINISHED && rc != DSM_RC_MORE_DATA) {
		printf("%s: dsmGetNextQObj() failed %s\n",__func__,tsm_printerr(dsmHandle,rc));
		return rc;
	}

	rc = dsmEndQuery(dsmHandle);
	if (rc != DSM_RC_OK) {
		printf("%s: dsmEndQuery() failed %s\n",__func__,tsm_printerr(dsmHandle,rc));
		return rc;
	}

	return DSM_RC_OK;
}

/* Signal handler */
int intrupt = 0;
void Catch(int signo) {
	if(signo) {}; /* so compiler won't complain about unused arguments */

	intrupt = 1;
}

/* Uses sigaction to establish signal handler */
int install_sig_handler(int signum, void (*sig_handler)(int)) {
	struct sigaction action;
	int rc;

	action.sa_handler = sig_handler; /* signal handler function  */
	sigemptyset( &action.sa_mask );  /* mask of signals to block */

	action.sa_flags = SA_NOCLDSTOP;

	rc = sigaction(
		signum,   /*  I */ /* signal identifier            */
		&action,  /* *I */ /* new action for signal        */
		NULL );   /* *O */ /* previous action - not needed */

	return (rc);
}

int ReadPass(char *text, char *buffer, int length) {
	struct termio ttyState, ttyStateSave;
	register char *p;
	register int c;
	int rc;
	FILE *ttyFH;

	struct sigaction  action;

	/* Let's flush any prompt to the terminal that may be pending. */
	fflush(stdout);

	/* Open the console input device */
	if((ttyFH = fopen("/dev/tty", "r")) == NULL)
		return(-1);
	else
		setbuf(ttyFH, (char *)NULL);

	/* Reset the interrupt flag */
	intrupt = 0;

	/* Trap the "BREAK" interrupt */
	(void) sigaction( SIGINT, NULL, &action );    /* save current hdlr */
	(void) install_sig_handler( SIGINT, Catch );  /* install new hdlr  */

	/* Get current state */
	rc = ioctl(fileno(ttyFH), TCGETA, &ttyStateSave);
	if (rc == -1)
		return(-1);

	/* Copy the saved tty state into a work field */
	ttyState = ttyStateSave;

	/* Turn off ECHO */
	ttyState.c_lflag &= ~ECHO;
	rc = ioctl(fileno(ttyFH), TCSETA, &ttyState);
	if (rc == -1)
		return(-1);

	printf(text);

	/* Read the password (quietly) */
	for(p=buffer; !intrupt && (c = getc(ttyFH)) != '\n' && c != EOF; ) {
		if(p < buffer+length)
			*p++ = c;
	}
	*p = '\0';

	printf("\n");

	/* Restore the tty state settings */
	rc = ioctl(fileno(ttyFH), TCSETA, &ttyStateSave);
	if (rc == -1)
		return(-1);

	/* Reset the interrupt handler */
	(void) sigaction( SIGINT, &action, NULL );

	if(ttyFH != stdin)
		(void) fclose(ttyFH);
	if(intrupt)
		(void) kill(getpid(), SIGINT);

	return(strlen(buffer));
}

/*
 * Set the TSM Node password
 *
 * We use a callback to process the list, the callback should return:
 */
int tsm_setpassword(char *options) {
	uint32 dsmHandle;
	bool_t done = bFalse;
	uint16 pw_trys = 0;
	uint16 len = 0;
	char input[INPUTLEN];
	char pw_cur[DSM_MAX_VERIFIER_LENGTH + 1];
	char pw_new [DSM_MAX_VERIFIER_LENGTH + 1];

	int rc;

	while (!done) {
		// Current Password
		rc = ReadPass("Enter your current password: ",input,INPUTLEN);
		len = strlen(input);
		if ((len > DSM_MAX_VERIFIER_LENGTH) || !len || (rc < 0)) {
			printf("Current password is invalid.  Please try again.\n");
			continue;
		} else {
			strcpy(pw_cur,input);
		}

		// New password
		rc = ReadPass("Enter your new password: ",input,INPUTLEN);
		len = strlen(input);
		if ((len > DSM_MAX_VERIFIER_LENGTH) || !len || (rc < 0)) {
			printf("New password is invalid.  Please try again.\n");
			continue;
		} else {
			strcpy(pw_new,input);
		}

		// Verify new password
		rc = ReadPass("Enter your new password again: ",input,INPUTLEN);
		len = strlen(input);
		if ((len > DSM_MAX_VERIFIER_LENGTH) || !len || (rc < 0)) {
			printf("New password is invalid.  Please try again.\n");
			continue;

		} else {
			// Compare new pw copies to make sure no typos from user.
			if ((strcmp(pw_new,input))) {
				pw_trys++;

				if (pw_trys > 3)
					debugLog(0,__func__,"ERROR: Passwords dont match, tried too many times.",3);
				else
					debugLog(0,__func__,"WARN: Your new passwords do not match, please try again...",0);

			} else {
				done = bTrue;
			}
		}
	}

	dsmHandle = tsm_initsess(options,pw_cur);
	if (! dsmHandle) {
		debugLog(0,__func__,"ERROR: Unable to create TSM session with your password.",2);
	}

	rc = dsmChangePW(dsmHandle,pw_cur,pw_new);
	if (rc)
		debugLog(0,__func__,"ERROR: Password change failed.",2);
	else
		printf("\nYour new password has been accepted and updated.\n");

	dsmTerminate(dsmHandle);

	return 1;
}