/* -*- Mode: C; tab-width: 4 -*-
 *
 * Copyright (c) 2003-2004 Apple Computer, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

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

#include	"CommonServices.h"
#include	"DebugServices.h"

#include	<guiddef.h>
#include	<ws2spi.h>

//===========================================================================================================================
//	Prototypes
//===========================================================================================================================

int  					main( int argc, char *argv[] );
DEBUG_LOCAL void		Usage( void );
DEBUG_LOCAL int			ProcessArgs( int argc, char *argv[] );
DEBUG_LOCAL OSStatus	InstallNSP( const char *inName, const char *inGUID, const char *inPath );
DEBUG_LOCAL OSStatus	RemoveNSP( const char *inGUID );
DEBUG_LOCAL OSStatus	EnableNSP( const char *inGUID, BOOL inEnable );
DEBUG_LOCAL OSStatus	ListNameSpaces( void );
DEBUG_LOCAL OSStatus	ReorderNameSpaces( void );

DEBUG_LOCAL WCHAR *		CharToWCharString( const char *inCharString, WCHAR *outWCharString );
DEBUG_LOCAL char *		GUIDtoString( const GUID *inGUID, size_t inLen, char *outString );
DEBUG_LOCAL OSStatus	StringToGUID( const char *inCharString, GUID *outGUID );

DEBUG_LOCAL BOOL gToolQuietMode = FALSE;

//===========================================================================================================================
//	main
//===========================================================================================================================

int main( int argc, char *argv[] )
{
	OSStatus		err;
	
	debug_initialize( kDebugOutputTypeMetaConsole );
	debug_set_property( kDebugPropertyTagPrintLevel, kDebugLevelVerbose );
	
	err = ProcessArgs( argc, argv );
	return( (int) err );
}

//===========================================================================================================================
//	Usage
//===========================================================================================================================

DEBUG_LOCAL void	Usage( void )
{
	fprintf( stderr, "\n" );
	fprintf( stderr, "NSP Tool 1.0d1\n" );
	fprintf( stderr, "  Name Space Provider Tool\n" );
	fprintf( stderr, "\n" );
	
	fprintf( stderr, "  -install <name> <guid> <path>   - Installs a Name Space Provider\n" );
	fprintf( stderr, "\n" );
	fprintf( stderr, "      <name> Name of the NSP\n" );
	fprintf( stderr, "      <guid> GUID of the NSP\n" );
	fprintf( stderr, "      <path> Path to the NSP file\n" );
	fprintf( stderr, "\n" );
	
	fprintf( stderr, "  -remove <guid>                  - Removes a Name Space Provider\n" );
	fprintf( stderr, "\n" );
	fprintf( stderr, "      <guid> GUID of the NSP\n" );
	fprintf( stderr, "\n" );
	
	fprintf( stderr, "  -enable/-disable <guid>         - Enables or Disables a Name Space Provider\n" );
	fprintf( stderr, "\n" );
	fprintf( stderr, "      <guid> GUID of the NSP\n" );
	fprintf( stderr, "\n" );
	
	fprintf( stderr, "  -list                           - Lists Name Space Providers\n" );	
	fprintf( stderr, "  -reorder                        - Reorders Name Space Providers\n" );
	fprintf( stderr, "  -q                              - Enable quiet mode\n" );
	fprintf( stderr, "  -h[elp]                         - Help\n" );
	fprintf( stderr, "\n" );
}

//===========================================================================================================================
//	ProcessArgs
//===========================================================================================================================

DEBUG_LOCAL int ProcessArgs( int argc, char* argv[] )
{	
	OSStatus			err;
	int					i;
	const char *		name;
	const char *		guid;
	const char *		path;
			
	if( argc <= 1 )
	{
		Usage();
		err = 0;
		goto exit;
	}
	for( i = 1; i < argc; ++i )
	{
		if( strcmp( argv[ i ], "-install" ) == 0 )
		{
			// Install
			
			if( argc <= ( i + 3 ) )
			{
				fprintf( stderr, "\n### ERROR: missing arguments for %s\n\n", argv[ i ] );
				Usage();
				err = kParamErr;
				goto exit;
			}
			name = argv[ ++i ];
			guid = argv[ ++i ];
			path = argv[ ++i ];
			
			if( *name == '\0' )
			{
				name = "DotLocalNSP";
			}
			if( *guid == '\0' )
			{
				guid = "B600E6E9-553B-4a19-8696-335E5C896153";
			}
			
			err = InstallNSP( name, guid, path );
			require_noerr( err, exit );
		}
		else if( strcmp( argv[ i ], "-remove" ) == 0 )
		{
			// Remove
			
			if( argc <= ( i + 1 ) )
			{
				fprintf( stderr, "\n### ERROR: missing arguments for %s\n\n", argv[ i ] );
				Usage();
				err = kParamErr;
				goto exit;
			}
			guid = argv[ ++i ];
			if( *guid == '\0' )
			{
				guid = "B600E6E9-553B-4a19-8696-335E5C896153";
			}
			
			err = RemoveNSP( guid );
			require_noerr( err, exit );
		}
		else if( ( strcmp( argv[ i ], "-enable" )  == 0 ) || 
				 ( strcmp( argv[ i ], "-disable" ) == 0 ) )
		{
			BOOL		enable;
			
			// Enable/Disable
			
			enable = ( strcmp( argv[ i ], "-enable" ) == 0 );
			if( argc <= ( i + 1 ) )
			{
				fprintf( stderr, "\n### ERROR: missing arguments for %s\n\n", argv[ i ] );
				Usage();
				err = kParamErr;
				goto exit;
			}
			guid = argv[ ++i ];
			
			err = EnableNSP( guid, enable );
			require_noerr( err, exit );
		}
		else if( strcmp( argv[ i ], "-list" ) == 0 )
		{
			// List
						
			err = ListNameSpaces();
			require_noerr( err, exit );
		}
		else if( strcmp( argv[ i ], "-reorder" ) == 0 )
		{
			// Reorder
			
			err = ReorderNameSpaces();
			require_noerr( err, exit );
		}
		else if( strcmp( argv[ i ], "-q" ) == 0 )
		{
			gToolQuietMode = TRUE;
		}
		else if( ( strcmp( argv[ i ], "-help" ) == 0 ) || 
				 ( strcmp( argv[ i ], "-h" ) == 0 ) )
		{
			// Help
			
			Usage();
			err = 0;
			goto exit;
		}
		else
		{
			fprintf( stderr, "\n### ERROR: unknown argment: \"%s\"\n\n", argv[ i ] );
			Usage();
			err = kParamErr;
			goto exit;
		}
	}
	err = kNoErr;
	
exit:
	return( err );
}

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	InstallNSP
//===========================================================================================================================

OSStatus	InstallNSP( const char *inName, const char *inGUID, const char *inPath )
{
	OSStatus		err;
	size_t			size;
	WSADATA			wsd;
	WCHAR			name[ 256 ];
	GUID			guid;
	WCHAR			path[ MAX_PATH ];
	
	require_action( inName && ( *inName != '\0' ), exit, err = kParamErr );
	require_action( inGUID && ( *inGUID != '\0' ), exit, err = kParamErr );
	require_action( inPath && ( *inPath != '\0' ), exit, err = kParamErr );
	
	size = strlen( inName );
	require_action( size < sizeof_array( name ), exit, err = kSizeErr );
	CharToWCharString( inName, name );
	
	err = StringToGUID( inGUID, &guid );
	require_noerr( err, exit );
	
	size = strlen( inPath );
	require_action( size < sizeof_array( path ), exit, err = kSizeErr );
	CharToWCharString( inPath, path );
	
	err = WSAStartup( MAKEWORD( 2, 2 ), &wsd );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
	
	err = WSCInstallNameSpace( name, path, NS_DNS, 1, &guid );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	WSACleanup();
	require_noerr( err, exit );
	
	if (!gToolQuietMode)
	{
		fprintf( stderr, "Installed NSP \"%s\" (%s) at %s\n", inName, inGUID, inPath );
	}
	
exit:
	if( err != kNoErr )
	{
		fprintf( stderr, "### FAILED (%d) to install \"%s\" (%s) Name Space Provider at %s\n", err, inName, inGUID, inPath );
	}
	return( err );
}

//===========================================================================================================================
//	RemoveNSP
//===========================================================================================================================

DEBUG_LOCAL OSStatus	RemoveNSP( const char *inGUID )
{
	OSStatus		err;
	WSADATA			wsd;
	GUID			guid;
	
	require_action( inGUID && ( *inGUID != '\0' ), exit, err = kParamErr );
	
	err = StringToGUID( inGUID, &guid );
	require_noerr( err, exit );
	
	err = WSAStartup( MAKEWORD( 2, 2 ), &wsd );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
	
	err = WSCUnInstallNameSpace( &guid );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	WSACleanup();
	require_noerr( err, exit );
	
	if (!gToolQuietMode)
	{
		fprintf( stderr, "Removed NSP %s\n", inGUID );
	}
		
exit:
	if( err != kNoErr )
	{
		fprintf( stderr, "### FAILED (%d) to remove %s Name Space Provider\n", err, inGUID );
	}
	return( err );
}

//===========================================================================================================================
//	EnableNSP
//===========================================================================================================================

DEBUG_LOCAL OSStatus	EnableNSP( const char *inGUID, BOOL inEnable )
{
	OSStatus		err;
	WSADATA			wsd;
	GUID			guid;
	
	require_action( inGUID && ( *inGUID != '\0' ), exit, err = kParamErr );
	
	err = StringToGUID( inGUID, &guid );
	require_noerr( err, exit );
	
	err = WSAStartup( MAKEWORD( 2, 2 ), &wsd );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
	
	err = WSCEnableNSProvider( &guid, inEnable );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	WSACleanup();
	require_noerr( err, exit );
	
	if (!gToolQuietMode)
	{
		fprintf( stderr, "Removed NSP %s\n", inGUID );
	}
		
exit:
	if( err != kNoErr )
	{
		fprintf( stderr, "### FAILED (%d) to remove %s Name Space Provider\n", err, inGUID );
	}
	return( err );
}

//===========================================================================================================================
//	ListNameSpaces
//===========================================================================================================================

DEBUG_LOCAL OSStatus	ListNameSpaces( void )
{
	OSStatus				err;
	WSADATA					wsd;
	bool					started;
	int						n;
	int						i;
	DWORD					size;
	WSANAMESPACE_INFO *		array;
	char					s[ 256 ];
	
	array 	= NULL;
	started	= false;
	
	err = WSAStartup( MAKEWORD( 2, 2 ), &wsd );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
	started = true;
	
	// Build an array of all the NSPs. Call it first with NULL to get the size, allocate a buffer, then get them into it.
	
	size = 0;
	n = WSAEnumNameSpaceProviders( &size, NULL );
	err = translate_errno( n != SOCKET_ERROR, (OSStatus) GetLastError(), kUnknownErr );
	require_action( err == WSAEFAULT, exit, err = kUnknownErr );
	
	array = (WSANAMESPACE_INFO *) malloc( size );
	require_action( array, exit, err = kNoMemoryErr );
	
	n = WSAEnumNameSpaceProviders( &size, array );
	err = translate_errno( n != SOCKET_ERROR, (OSStatus) GetLastError(), kUnknownErr );
	require_noerr( err, exit );
	
	fprintf( stdout, "\n" );
	for( i = 0; i < n; ++i )
	{
		fprintf( stdout, "Name Space %d\n", i + 1 );
		fprintf( stdout, "    NSProviderId:   %s\n", GUIDtoString( &array[ i ].NSProviderId, sizeof( s ), s ) );
		fprintf( stdout, "    dwNameSpace:    %d\n", array[ i ].dwNameSpace );
		fprintf( stdout, "    fActive:        %s\n", array[ i ].fActive ? "YES" : "NO" );
		fprintf( stdout, "    dwVersion:      %d\n", array[ i ].dwVersion );
		fprintf( stdout, "    lpszIdentifier: \"%s\"\n", array[ i ].lpszIdentifier );
		fprintf( stdout, "\n" );
	}
	err = kNoErr;
	
exit:
	if( array )
	{
		free( array );
	}
	if( started )
	{
		WSACleanup();
	}
	if( err != kNoErr )
	{
		fprintf( stderr, "### FAILED (%d) to list Name Space Providers\n", err );
	}
	return( err );
}

//===========================================================================================================================
//	ReorderNameSpaces
//===========================================================================================================================

DEBUG_LOCAL OSStatus	ReorderNameSpaces( void )
{
	OSStatus				err;
	WSADATA					wsd;
	bool					started;
	int						n;
	int						i;
	DWORD					size;
	WSANAMESPACE_INFO *		array;
	WCHAR					name[ 256 ];
	WCHAR					path[ MAX_PATH ];
	
	array 	= NULL;
	started	= false;
		
	err = WSAStartup( MAKEWORD( 2, 2 ), &wsd );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
	started = true;
	
	// Build an array of all the NSPs. Call it first with NULL to get the size, allocate a buffer, then get them into it.
	
	size = 0;
	n = WSAEnumNameSpaceProviders( &size, NULL );
	err = translate_errno( n != SOCKET_ERROR, (OSStatus) GetLastError(), kUnknownErr );
	require_action( err == WSAEFAULT, exit, err = kUnknownErr );
	
	array = (WSANAMESPACE_INFO *) malloc( size );
	require_action( array, exit, err = kNoMemoryErr );
	
	n = WSAEnumNameSpaceProviders( &size, array );
	err = translate_errno( n != SOCKET_ERROR, (OSStatus) GetLastError(), kUnknownErr );
	require_noerr( err, exit );
	
	// Find the "Tcpip" NSP.
	
	for( i = 0; i < n; ++i )
	{
		if( strcmp( array[ i ].lpszIdentifier, "Tcpip" ) == 0 )
		{
			break;
		}
	}
	require_action( i < n, exit, err = kNotFoundErr );
	
	// Uninstall it then re-install it to move it to the end.
	
	size = (DWORD) strlen( array[ i ].lpszIdentifier );
	require_action( size < sizeof_array( name ), exit, err = kSizeErr );
	CharToWCharString( array[ i ].lpszIdentifier, name );
	
	size = (DWORD) strlen( "%SystemRoot%\\System32\\mswsock.dll" );
	require_action( size < sizeof_array( path ), exit, err = kSizeErr );
	CharToWCharString( "%SystemRoot%\\System32\\mswsock.dll", path );
	
	err = WSCUnInstallNameSpace( &array[ i ].NSProviderId );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
	
	err = WSCInstallNameSpace( name, path, NS_DNS, array[ i ].dwVersion, &array[ i ].NSProviderId );
	err = translate_errno( err == 0, errno_compat(), WSAEINVAL );
	require_noerr( err, exit );
		
	// Success!
	
	fprintf( stderr, "Reordered \"Tcpip\" NSP to to the bottom of the NSP chain\n" );	
	err = kNoErr;
	
exit:
	if( array )
	{
		free( array );
	}
	if( started )
	{
		WSACleanup();
	}
	if( err != kNoErr )
	{
		fprintf( stderr, "### FAILED (%d) to reorder Name Space Providers\n", err );
	}
	return( err );
}

#if 0
#pragma mark -
#endif

//===========================================================================================================================
//	CharToWCharString
//===========================================================================================================================

DEBUG_LOCAL WCHAR *	CharToWCharString( const char *inCharString, WCHAR *outWCharString )
{
	const char *		src;
	WCHAR *				dst;
	char				c;
	
	check( inCharString );
	check( outWCharString );
	
	src = inCharString;
	dst = outWCharString;
	do
	{
		c = *src++;
		*dst++ = (WCHAR) c;
	
	}	while( c != '\0' );
	
	return( outWCharString );
}

//===========================================================================================================================
//	GUIDtoString
//===========================================================================================================================

DEBUG_LOCAL char *	GUIDtoString( const GUID *inGUID, size_t inLen, char *outString )
{
	check( inGUID );
	check( outString );
	
	_snprintf( outString, inLen, "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
		inGUID->Data1, inGUID->Data2, inGUID->Data3, 
		inGUID->Data4[ 0 ], inGUID->Data4[ 1 ], inGUID->Data4[ 2 ], inGUID->Data4[ 3 ], 
		inGUID->Data4[ 4 ], inGUID->Data4[ 5 ], inGUID->Data4[ 6 ], inGUID->Data4[ 7 ] );
	return( outString );
}

//===========================================================================================================================
//	StringToGUID
//===========================================================================================================================

DEBUG_LOCAL OSStatus	StringToGUID( const char *inCharString, GUID *outGUID )
{
	OSStatus			err;
	int					n;
	unsigned int		v[ 8 ];
	
	check( inCharString );
	check( outGUID );
	
	n = sscanf( inCharString, "%lX-%hX-%hX-%02X%02X-%02X%02X%02X%02X%02X%02X", 
		&outGUID->Data1, &outGUID->Data2, &outGUID->Data3, 
		&v[ 0 ], &v[ 1 ], &v[ 2 ], &v[ 3 ], &v[ 4 ], &v[ 5 ], &v[ 6 ], &v[ 7 ] );
	require_action( n == 11, exit, err = kFormatErr );
	
	outGUID->Data4[ 0 ] = (unsigned char) v[ 0 ];
	outGUID->Data4[ 1 ] = (unsigned char) v[ 1 ];
	outGUID->Data4[ 2 ] = (unsigned char) v[ 2 ];
	outGUID->Data4[ 3 ] = (unsigned char) v[ 3 ];
	outGUID->Data4[ 4 ] = (unsigned char) v[ 4 ];
	outGUID->Data4[ 5 ] = (unsigned char) v[ 5 ];
	outGUID->Data4[ 6 ] = (unsigned char) v[ 6 ];
	outGUID->Data4[ 7 ] = (unsigned char) v[ 7 ];
	err = kNoErr;

exit:
	return( err );
}
