/***************************************************************************
  Author: Sandip Patel <psandip@novell.com>
***************************************************************************/

#include <stdio.h>
#include <ldap.h>
#include <ldap_ssl.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#if defined(N_PLAT_NLM) && defined(LIBC)
#include <screen.h>
#endif

#define LDAPPORT 389
#define LDAPSSLPORT 636
#define CERTFILETYPE "der"
#define RAND_MOD 100

static unsigned long count = 0, pcount = 0;
pthread_mutex_t mtex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mtex2 = PTHREAD_MUTEX_INITIALIZER;
int ldapPort = LDAPPORT, fileEncoding;
char *loginDN = NULL;
char *ldapHost = NULL;
char *password = NULL;
char *logFileName = NULL;
char *ldapUserContainer = NULL;
int version = LDAP_VERSION3;
unsigned long numOperations = 100, logInterval = 100, totExecutionTime = 0, maxEndTime = 0;
unsigned int opeDelay = 0;
char testBaseDN[512], certFile[64];
double ttps = 0;
struct timeval startIt, endIt, lapsedIt; 

int user_ldapbind(unsigned long);
int ssl_user_ldapbind(unsigned long);
unsigned long get_counter();

static char usage[] =
"\nUsage:\nLDAPBind -h <LDAP hostname/IP> -p <LDAP port number 389(NON-SSL)/636(SSL)> -D <LDAP bind user DN> -w <LDAP bind user password> -b <LDAP bind base DN(Users should be present in this container with format cn=user<0 to 100>,ou=YourBaseOU,o=YourBaseOrg)> -t <Number of threads(unsigned integer)> -o <Total number of operations(unsigned long)>/-e <Total test execution time in minutes> -l <Log file name> -i <Log interval(number of operations)> -d <time delay between two operations in milliseconds(1000msec = 1sec)>\n"
"\nExamplei(-o number of operations):\nLDAPBind -h gnulinux.in -p 389 -D cn=admin,o=gnulinux -w secret -b ou=users,o=LDAPOrgContainer -t 10 -o 100000 -l LDAPBind_Test.log -i 10000 -d 1000\n"
"\nExample(-e time duration):\nLDAPBind -h gnulinux.in -p 636 -D cn=admin,o=gnulinux -w secret -b ou=users,o=LDAPOrgContainer -t 10 -e 345600 -l LDAPBind_Test.log -i 10000 -d 1000\n"
"\nNote: Download/Copy the <LDAP hostname/IP>.der SSL certificate file in current dir\n";

int main(int argc, char **argv)
{ 
	char c, cmd[1024];
	unsigned int threadCount = 0, maxThreads = 10;
	double tps = 0;
	struct timeval start, end, lapsed; 
	void *exit_status = NULL;
	#if defined(N_PLAT_NLM) && defined(LIBC)
    	setscreenmode(SCR_NO_MODE);              /* Don't clear screen on exit */
    	#endif
	struct timeval timeOut = {300,0};

	if(argc == 1){
		fprintf(stdout, "\n%s\n", usage);
		return -1;
	}

	/* Read the passed Arguments */
	while ((c = getopt( argc, argv, "h:p:D:w:b:t:o:e:l:i:d:")) != -1){
    		switch(c) {
    			case 'h':
				ldapHost = strdup(optarg);
				break;
			case 'p':
				ldapPort = atoi(optarg);
				break;
			case 'D':
				loginDN  = strdup(optarg);
				break;
			case 'w':
				password = strdup(optarg);
				break;
			case 'l':
				logFileName  = strdup(optarg);
				break;
			case 't':
				maxThreads = atoi(optarg);
				break;
			case 'i':
				logInterval = strtoul(optarg, NULL, 0);
				break;
			case 'b':
				sprintf(testBaseDN, "%s", optarg);	
				break;
			case 'o':
				numOperations  = strtoul(optarg, NULL, 0);
				break;
			case 'e':
				totExecutionTime  = strtoul(optarg, NULL, 0);
				break;
			case 'd':
				opeDelay  = strtoul(optarg, NULL, 0);
				break;
		/*	case '?':
				if (optopt == 'D' || optopt == 'w' || optopt == 'h' || optopt == 'p' || optopt == 'l' || optopt == 'u' || optopt == 't' || optopt == 'i'|| optopt == 'b' || optopt == 'o' || optopt == 'e' || optopt =='d')
        	   			fprintf (stderr, "Option -%c requires an argument.\n", optopt);
        	 		else if (isprint (optopt))
        	  	 		fprintf (stderr, "Unknown option `-%c'.\n", optopt);
        	 		else
        	   			fprintf (stderr, "Unknown option character `\\x%x'.\n",optopt);
        	 		return 1;	*/
			default:
        	  		//fprintf (stderr, "Unrecognized option `-%c'.\n", optopt);
        	 		return 1;
		}
	}

	if(totExecutionTime) {
		numOperations = 4294967295; //Max unsigned long value(32bit)
	} else {
		totExecutionTime = 525600; //Minutes of 365 days
	}
	

	if(ldapPort == LDAPSSLPORT) {
		sprintf(certFile, "%s.%s", ldapHost, CERTFILETYPE);
	   	//sprintf(cmd, "./getcert %s %d %s %s %s %s", ldapHost, LDAPSSLPORT, loginDN, password, certFile, CERTFILETYPE);
		//if(system(cmd) == -1)
		//{
		//	fprintf(stderr, "Error while downloading the ssl certificate file\n");
		//	return -1; 
		//}
		
		if ( (0 == strcmp(CERTFILETYPE, "DER")) || (0 == strcmp(CERTFILETYPE, "der")))
       			fileEncoding = LDAPSSL_CERT_FILETYPE_DER;
    		else if ((0 == strcmp(CERTFILETYPE, "B64")) ||(0 == strcmp(CERTFILETYPE, "b64")))
			fileEncoding = LDAPSSL_CERT_FILETYPE_B64;
    		else
    		{
       			fprintf(stdout, "\nInvalid certificate file type.\n");
       			fprintf(stdout, "\n%s", usage);
      			exit(-1);
    		}
	}

	if(logFileName == NULL) {
		logFileName  = strdup("LDAPBind.log");
	}
	
	pthread_t threads[maxThreads];
	
	ldap_set_option( NULL, LDAP_OPT_NETWORK_TIMEOUT, &timeOut);
	
	gettimeofday (&start, NULL); 
	maxEndTime = start.tv_sec + (totExecutionTime * 60); 
	gettimeofday (&startIt, NULL); 
	for(threadCount=1; threadCount <= maxThreads; threadCount++)
	{
		pthread_create(&threads[threadCount],NULL,(void *)(ldapPort==LDAPSSLPORT?ssl_user_ldapbind:user_ldapbind),(void *)threadCount);
		pthread_mutex_lock( &mtex1 );
		count++;
		pthread_mutex_unlock( &mtex1 );
	}
	for(threadCount=1; threadCount <= maxThreads; threadCount++)
	{
		pthread_join(threads[threadCount], (void *)exit_status);
	}
	gettimeofday (&end, NULL); 

	if (start.tv_usec > end.tv_usec) 
	{  
		end.tv_usec += 1000000;  
		end.tv_sec--; 
	} 
	lapsed.tv_sec = end.tv_sec - start.tv_sec;
	lapsed.tv_usec = end.tv_usec - start.tv_usec; 
	tps = (double)(count-maxThreads)/lapsed.tv_sec;
		
	fprintf(stdout, "\nFinalStatus: TotalOperations=%lu OverallElapsedSeconds=%lu OverallElapsedMicroSeconds=%lu OverallTPS=%lf AverageTPS=%lf", count-maxThreads, lapsed.tv_sec, lapsed.tv_usec, tps, ttps/(numOperations/logInterval));
	fprintf(stdout, "\nTestEnd: LDAP Bind Reliability Test\n");
	return (0);
}

int user_ldapbind(unsigned long tid)
{
	int rc;
	unsigned long ct = tid;
	LDAP *ld;
	char dn[512], *defaultString;
	double tps;
	unsigned long tcount;
	struct timeval tstart, currTime;
	
	defaultString = strdup("Bind_User");
	gettimeofday(&currTime, NULL);
		
	while(ct <= numOperations && maxEndTime >= currTime.tv_sec) {  

		pthread_mutex_lock( &mtex1 );
		ct = get_counter();
		pthread_mutex_unlock( &mtex1 );

		if ((ld = ldap_init( ldapHost, ldapPort )) == NULL)
		{
			fprintf(stdout, "\nThread %lu: LDAP session initialization failed for operation %lu\n", tid, ct);
			continue;
		}
		srand(time(NULL));
		sprintf(dn, "cn=%s%d,%s", "user", rand()%RAND_MOD, testBaseDN);	
		
		if ((rc = ldap_simple_bind_s(ld, dn, password)) != LDAP_SUCCESS )
		{
			fprintf(stdout, "\nThread %lu: LDAP bind failed for operation %lu %s - %d %s\n", tid, ct, dn, rc, ldap_err2string( rc ));
			ldap_unbind_s( ld );   
			continue;
		}
		ldap_unbind_s( ld );   
		
		if(ct%logInterval == 0){
			pthread_mutex_lock( &mtex2 );
			tstart = startIt;	
			tcount = count;
			gettimeofday(&endIt, NULL);
			gettimeofday(&startIt, NULL);
			if (tstart.tv_usec > endIt.tv_usec) 
			{  
				endIt.tv_usec += 1000000;  
				endIt.tv_sec--; 
			} 
			
			lapsedIt.tv_sec = endIt.tv_sec - tstart.tv_sec;
			lapsedIt.tv_usec = endIt.tv_usec - tstart.tv_usec;
			tps = (double)(tcount-pcount)/lapsedIt.tv_sec;
			ttps+=tps;	
		
			if(tcount>numOperations)tcount=numOperations;
			fprintf(stdout, "\nCheckpoint Operations=%lu ElapsedSec=%lu ElapsedMicroSec=%lu TPS=%lf", tcount, lapsedIt.tv_sec, lapsedIt.tv_usec, tps);
			pcount = tcount;
			pthread_mutex_unlock( &mtex2 );
		}
		if(opeDelay) usleep(opeDelay*1000);
		gettimeofday(&currTime, NULL);
	}
	if(defaultString) free(defaultString);
}

int ssl_user_ldapbind(unsigned long tid)
{
	int rc;
	unsigned long ct = tid;
	LDAP *ld;
	char dn[512], *defaultString;
	double tps;
	unsigned long tcount;
	struct timeval tstart, currTime; 
	
	defaultString = strdup("Bind_User");
	gettimeofday(&currTime, NULL);
		
	while(ct <= numOperations && maxEndTime >= currTime.tv_sec) {  
 
		pthread_mutex_lock( &mtex1 );
		ct = get_counter();
		pthread_mutex_unlock( &mtex1 );

    		if ((rc = ldapssl_client_init( NULL,NULL )) != LDAP_SUCCESS)
    		{
        		fprintf(stdout, "\nThread %lu: ldapssl_client_init error for operation %lu: %d\n", tid, ct, rc);
        		continue;	
    		}

    		if ((rc =ldapssl_add_trusted_cert(certFile, fileEncoding)) != LDAP_SUCCESS)
    		{
        		fprintf(stdout, "\nThread %lu: ldapssl_add_trusted_cert error for operation %lu: %d\n", tid, ct, rc);
        		ldapssl_client_deinit();
        		continue;	
    		}

		if (( ld = ldapssl_init( ldapHost, LDAPSSLPORT, 1 )) == NULL)
		{
			fprintf(stdout, "\nThread %lu: LDAP session initialization failed for operation %lu: \n", tid, ct);
        		continue;	
		}
		
		srand(time(NULL));
		sprintf(dn, "cn=%s%d,%s", "user", rand()%RAND_MOD, testBaseDN);	
		
		if ((rc = ldap_simple_bind_s( ld, dn, password )) != LDAP_SUCCESS )
		{
			fprintf(stdout, "\nThread %lu: ldap_simple_bind_s error for operation %lu %s : %d %s\n", tid, ct, dn, rc, ldap_err2string( rc ));
			ldap_unbind_s( ld );   
        		continue;	
		}

		ldap_unbind_s( ld );
		
		ldapssl_client_deinit();
		
		if(ct%logInterval == 0){
			pthread_mutex_lock( &mtex2 );
			tstart = startIt;	
			tcount = count;
			gettimeofday (&endIt, NULL);
			gettimeofday (&startIt, NULL);
			if (tstart.tv_usec > endIt.tv_usec) 
			{  
				endIt.tv_usec += 1000000;  
				endIt.tv_sec--; 
			} 
			
			lapsedIt.tv_sec = endIt.tv_sec - tstart.tv_sec;
			lapsedIt.tv_usec = endIt.tv_usec - tstart.tv_usec;
			tps = (double)(tcount-pcount)/lapsedIt.tv_sec;
			ttps+=tps;	
		
			if(tcount>numOperations)tcount=numOperations;
			fprintf(stdout, "\nCheckpoint Operations=%lu ElapsedSec=%lu ElapsedMicroSec=%lu TPS=%lf", tcount, lapsedIt.tv_sec, lapsedIt.tv_usec, tps);
			pcount = tcount;
			pthread_mutex_unlock( &mtex2 );
		}
		if(opeDelay) usleep(opeDelay*1000);
		gettimeofday(&currTime, NULL);
	}
	if(defaultString) free(defaultString);
}

unsigned long get_counter()
{
	count++;
	return count;
}
