/*
 * Copyright (c) 2000 - 2003 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */

/*!
 * @header DSAuthenticate
 * Tool for testing authentication against DirectoryService API.
 * DIRT: the DIRectory Tool.
 */


#warning VERIFY the version string before each distinct build submission that changes the dirt tool
#define DIRTVERSION "20.2"

#import <Foundation/Foundation.h>
#import "DSAuthenticate.h"
#import "DSAuthenticateNT.h"
#import "DSException.h"
#import "DSStatus.h"
#import "dstools_version.h"
#import <unistd.h>

BOOL		doVerbose		= NO;
static BOOL sigIntRaised	= NO;

void catch_int(int sig_num);
void usage(void);

int main(int argc, char *argv[])
{
    tDirStatus				status				= eDSNoErr;
    NSAutoreleasePool      *pool				= [[NSAutoreleasePool alloc] init];
    DSAuthenticate		   *dsauth				= nil;
    NSString			   *username			= nil;
    NSString			   *password			= nil;
    NSString			   *sNodeToSearch		= nil;
	NSString			   *authMethod			= nil;
    BOOL					listNodesOnly		= NO;
    BOOL					searchLocalOnly		= NO;
    BOOL					useContactPath		= NO;
    BOOL					groupSearch			= NO; // Thus by default let's perform a user search.
    BOOL					reportStatusCodes   = NO;
    int						queryIterations		= 1;
    long					delayInSeconds		= 0;
    int						ch					= -1;
    char				   *localtime			= nil;
    time_t					nowtime;
    unsigned int			i					= 0;
    NSAutoreleasePool      *loopPool			= nil;
    DSException			   *dirtException		= nil;
    DSStatus			   *dsStat				= [[DSStatus sharedInstance] retain];
	
	if ( argc == 2 && strcmp(argv[1], "-appleversion") == 0 )
		dsToolAppleVersionExit( argv[0] );
	
    while ((ch = getopt(argc, argv, "u:p:nclvgxm:q:d:a:h?")) != -1)
	{
        switch (ch)
		{
			case 'u':
				username = [[NSString alloc] initWithCString:optarg];
				break;
			case 'p':
				password = [[NSString alloc] initWithCString:optarg];
				break;
			case 'n':
				// Only list the nodes where the username is found
				listNodesOnly = YES;
				break;
			case 'l':
				// Only search the localnode
				searchLocalOnly = YES;
				break;
			case 'g':
				// Search for a group name, instead of a user.
				groupSearch = YES;
				break;
			case 'm':
				// Only search the specified node name
				sNodeToSearch = [[NSString alloc] initWithCString:optarg];
				break;
			case 'q':
				queryIterations = atoi(optarg);
				// if iterations parameter < 0, then loop forever by never incrementing the loop counter
				if ((int)queryIterations < 0) {
					fprintf(stderr, "The number of iterations must be a non-negative integer.\n");
					exit(-1);
				}
				break;
			case 'd':
				delayInSeconds = atol(optarg);
				if ((int)queryIterations < 0) {
					fprintf(stderr, "The delay (seconds) must be a non-negative integer.\n");
					exit(-2);
				}
				break;
			case 'c':
				useContactPath = YES;
				break;
			case 'v':
				doVerbose = YES;
				break;
			case 'x':
				reportStatusCodes = YES;
				break;
			case 'a':
				authMethod = [[NSString alloc] initWithCString:optarg];
				break;
			case '?':
			case 'h':
			default:
				usage();
        }
    }
    
    if (queryIterations != 1)
	{
        signal(SIGINT, catch_int);
        signal(SIGTERM, catch_int);
    }

    if (username != nil && (password != nil || listNodesOnly))
	{
        NS_DURING
		if (!authMethod)
			dsauth = [[DSAuthenticate alloc] init];
		else if ([authMethod isEqualToString:NT_AUTH])
			dsauth = [[DSAuthenticateNT alloc] init];
		
        for (i=0; (queryIterations == 0 ? TRUE : i < queryIterations) && (sigIntRaised == NO); i++ )
		{
            NS_DURING
            loopPool = [[NSAutoreleasePool alloc] init];
            if (queryIterations != 1)
			{
                nowtime = time(NULL);
                localtime = ctime(&nowtime);
                localtime[strlen(localtime)-1] = 0;
                printf("%d:\n%s ---------------- \n", i, localtime);
            }
			
            // Open DirectoryService by instantiating the dsauth object.
            if (useContactPath == YES)
                [dsauth useContactSearchPath];
                
            if (groupSearch == YES)
                [dsauth searchForGroups];
            
            if (searchLocalOnly == YES) 
                status = [dsauth authUserOnLocalNode:username password:password];
            else if (sNodeToSearch != nil)
                status = [dsauth authOnNodePath:sNodeToSearch username:username password:password];
            else if (listNodesOnly == YES)
			{
                int iList;
                NSArray *nodeList = [dsauth getListOfNodesWithUser:username];
                [nodeList retain];
                printf("%s %s was found in:\n", groupSearch ? "Group" : "User", 
                                                [username cString]);
				int iListLimit = [nodeList count];
                for (iList = 0; iList < iListLimit; iList++)
				{
                    printf("%s\n", [[nodeList objectAtIndex:iList] cString]);
                }
                [nodeList release];
            }
            else
                status = [dsauth authUserOnSearchPath:username password:password];
            
            [loopPool release];
            loopPool = nil;
            if (queryIterations != 1)
			{
                nowtime = time(NULL);
                localtime = ctime(&nowtime);
                localtime[strlen(localtime)-1] = 0;
				char *statusString = [dsStat cStringForStatus:status];
                printf("%s ---------------- status: %s (%d)\n", localtime, statusString, status);
				free(statusString);
				statusString = nil;
                if (reportStatusCodes) fprintf(stderr, "%d\n",status);
            }
            fflush(stdout);
            if (delayInSeconds > 0 && (i+1 < queryIterations || queryIterations == 0))
                sleep(delayInSeconds);
            if ((queryIterations > 1 && i+1 < queryIterations) || queryIterations == 0)
                [dsauth reset];

            NS_HANDLER
                if (queryIterations == 1)
                {
                    [localException retain];
                    [loopPool release];
                    [[localException autorelease] raise];
                }
                else
                {
                    dirtException = (DSException*)localException;
                    printf("%s -- DS status: %s (%d)\n", [[dirtException reason] cString],
                                        [dirtException statusCString], [dirtException status]);
                    
                    // If this is a eDSServerTimeout or eDSCannotAccessSession error, then probably DS shut down.
                    // We attempt to open a new connection to the DS server:
                    if ([dirtException status] == eDSServerTimeout || [dirtException status] == eDSCannotAccessSession)
                    {
                        printf("RESTARTING Directory Services.\n");
                        fflush(stdout);
                        [dsauth release];
                        dsauth = [[DSAuthenticate alloc] init];
                        if (reportStatusCodes) fprintf(stderr, "%d\n", [dirtException status]);
                    }
                                        
                    dirtException = nil;
                }
            NS_ENDHANDLER
        } // End for loop
        
        NS_HANDLER
            if ([localException isKindOfClass:[DSException class]])
            {
                dirtException = (DSException*)localException;
                fprintf(stderr, "%s -- DS status: %s (%d)\n", [[dirtException reason] cString],
                                            [dirtException statusCString], [dirtException status]);
                status = [dirtException status];
            }
            else
            {
                fprintf(stderr, "Catching unknown exception: <%s> %s\n", [[localException name] cString], [[localException reason] cString]);
                fprintf(stderr, "Attempting to clean up.\n");
            }
        NS_ENDHANDLER
        // Clean up, close Dir Server
        if (dsauth != nil) [dsauth release];
    }
    else 
        usage();

    if (username != nil)
		[username release];
    if (password != nil)
		[password release];
    if (sNodeToSearch != nil)
		[sNodeToSearch release];
		
    [pool release];
	
    if (doVerbose)
	{
		char *statusString = [dsStat cStringForStatus: status];
		printf("exiting cleanly, status: %s (%d)\n", statusString, status);
		free(statusString);
		statusString = nil;
	}
		
    [dsStat release];
    return status;
}

void catch_int(int sig_num)
{
    static BOOL calledOnce = NO;

    if (calledOnce)
        exit(3);
    sigIntRaised = YES;
    fprintf(stderr, "\n\n-------------------- Cleaning up, please wait. --------------------\n");
    fprintf(stderr, "  If you wish to exit immediately, issue CTRL-C or signal again.\n\n");
    fflush(stderr);
    calledOnce = YES;
}

void usage(void) {
    printf("DIRT: The DIRectory Tool for testing authentication against the\n" );
    printf("      DirectoryServices API.\n");
    printf("Version %s\n\n", DIRTVERSION);
    printf("Usage: dirt [-c] [-g] [-l | -m path | -n] [-q query_iterations [-d seconds]]\n"
	   "            [-a auth_method] -u username [-p password]\n\n");
    printf(" -l\t\tQuery Local Node only\n");
    printf(" -c\t\tQuery the Contacts Search Policy\n\t\t(default is to use the Authentication Search Policy)\n");
    printf(" -m path\tQuery on Node named by given path\n");
    printf(" -q n\t\tPerform the specified query operation n times.\n\t\t(specify 0 to loop forever)\n");
    printf(" -d n\t\tSleep n seconds between each query iteration.  Default is 0.\n");
    printf(" -n\t\tOnly list Nodes in Search Policy where user (or group) is found\n\t\t(password not required)\n");
    printf(" -g\t\tQuery for groups instead of users.\n");
	printf(" -a auth_method\tUse specified authentication method. Available methods:\n\t\t\tnt --> SMB-NT\n");
    printf("\n");
    exit(0);
}
