#include <windows.h>
#include <lm.h>
#include <stdio.h>
#include <stdlib.h>
#pragma hdrstop



#define MAXLEN 256
#define MAXUSERS 256
#define MAXCLIENTS 256



HANDLE hSuicide = NULL; // if this event gets set, the user requested us to die



BOOL __stdcall ConsoleHandler( DWORD ConsoleEvent )
{
	switch ( ConsoleEvent )
	{
		case CTRL_C_EVENT:
			printf( "*Ctrl-C*" );
			SetEvent( hSuicide );
			return true;
		case CTRL_BREAK_EVENT:
			printf( "*Ctrl-Break*" );
			SetEvent( hSuicide );
			return true;
		case CTRL_CLOSE_EVENT:
			printf( "*Window closing*" );
			SetEvent( hSuicide );
			return true;
		default:
			return false;
	}
}



int __cdecl comp( const void *e, const void *f )
{
	return _wcsicmp( *(const wchar_t **) e, *(const wchar_t **) f );
}



int cull( wchar_t *server, bool simulate, DWORD timeout, bool killAnonymous,
	int nUsers, wchar_t *users[], int nClients, wchar_t *clients[] )
{
	SESSION_INFO_502 *buf, *cur;
	DWORD read, total, resumeh, rc, rc2, i;
	wchar_t *p, tt[MAXLEN];

	resumeh = 0;
	printf( "%-16.16s  %-16.16s  %-16.16s  %8.8s  %s\n",
		"Client", "User", "Transport", "Idle", "Killed?" );
	printf( "%-16.16s  %-16.16s  %-16.16s  %8.8s  %s\n",
		"----------------", "----------------",
		"----------------", "--------", "-------" );
	do
	{
		buf = NULL;
		rc = NetSessionEnum( (char *) server, NULL, NULL, 502,
			(BYTE **) &buf, 2048, &read, &total, &resumeh );
		if ( rc != ERROR_MORE_DATA && rc != ERROR_SUCCESS )
			break;

//		printf( "\ngot %lu entries out of %lu\n", read, total );
		for ( i = 0, cur = buf; i < read; ++ i, ++ cur )
		{
			// prettify the transport name
			p = (wchar_t *) cur->sesi502_transport;
			if ( _wcsnicmp( p, L"\\device\\", 8 ) == 0 )
				p += 8;
			// Note: the capital S in the format string will expect Unicode
			// strings, as this is a program written/compiled for ANSI.
			printf( "%-16.16S  %-16.16S  %-16.16S  %8lu",
				cur->sesi502_cname, cur->sesi502_username, p, cur->sesi502_idle_time );

			// decide if we kill this session
			p = (wchar_t *) cur->sesi502_cname;
			// do we check clients? if yes, is it a match?
			if ( nClients == 0 || bsearch( &p, clients, nClients, sizeof( clients[0] ), comp ) == NULL )
			{
				p = (wchar_t *) cur->sesi502_username;
				// do we kill anonymous sessions? is it one?
				if ( ( killAnonymous && *p == L'\0' ) || // anonymous
				// do we kill named sessions? if so, do we have a match in the user list?
				( ! killAnonymous && ( nUsers == 0 || bsearch( &p, users, nUsers, sizeof( users[0] ), comp ) == NULL ) ) )
				{
					// do we require a certain idle time?
					if ( timeout == 0 || cur->sesi502_idle_time >= timeout )
					{
						if ( simulate )
							rc2 = NERR_Success;
						else
						{
							// client name for NetSessionDel() must be UNC
							wcscpy( tt, L"\\\\" );
							wcscat( tt, (wchar_t *) cur->sesi502_cname );
							rc2 = NetSessionDel( (char *) server, (char *) tt, cur->sesi502_username );
						}
						if ( rc2 == NERR_Success )
							printf( "  *gone*" );
						else
							printf( "  =%lu=", rc2 );
					}
				}
			}
			putchar( '\n' );
		}

		if ( buf != NULL )
			NetApiBufferFree( buf );

	} while ( rc == ERROR_MORE_DATA );

	if ( rc != ERROR_SUCCESS )
		printf( "NSE() returned %lu\n", rc );

	putchar( '\n' );

	return rc;
}



int main( int argc, char *argv[] )
{
	wchar_t server[MAXLEN], tt[MAXLEN];
	int errors = 0, i;
	DWORD checkInterval = 0;
	bool haveCheckInterval = false;
	DWORD interval = 900, rc = WAIT_OBJECT_0 + 3;
	bool haveInterval = false;
	bool killAnonymous = false;
	bool haveKillAnonymous = false;
	bool simulate = false;
	bool haveSimulate = false;
	int nUsers = 0, nClients = 0;
	static wchar_t *users[MAXUSERS];
	static wchar_t *clients[MAXCLIENTS];

	if ( argc < 2 )
	{
		usage:
		puts( "killidle [-s \\\\server] [-c client] [-u user] [-n]" );
		puts( "  [-i max_idle_seconds] [-f check_frequency_seconds] [-d]" );
		puts( "" );
		puts( "\\\\server -- machine to check, with backslashes, please." );
		puts( "  Default: local machine." );
		puts( "" );
		puts( "client -- filter list to exclude all sessions from client." );
		puts( "  Default: (empty), no sessions excluded from the slaughter." );
		puts( "" );
		puts( "user -- filter list to exclude all sessions logged in as user." );
		puts( "  Default: (empty), no users excluded from the victims list." );
		puts( "" );
		puts( "max_idle_seconds -- kills sessions that are idle longer than this." );
		puts( "  Default: 900 seconds; a value of 0 does not check the idle time." );
		puts( "" );
		puts( "check_frequency_seconds -- length of the pause between two" );
		puts( "  consecutive examinations of the session list." );
		puts( "  Default: 0 seconds (run only once); else recommended minimum 900." );
		puts( "" );
		puts( "If -n is present, only sessions without a login name get killed." );
		puts( "  Obviously, this makes -u superfluous." );
		puts( "" );
		puts( "If -d is present, the sessiuon-close is only simulated. Useful to see" );
		puts( "  what kind of havoc your current settings would wreak." );
		puts( "" );
		puts( "You may supply multiple -c and -u switches to protect yourself and your pals" );
		puts( "from sudden disconnection. Note that this utility does _not_ check whether" );
		puts( "a victim has anything important open -- you may want to exclude your boss." );
		return 1;
	}

	server[0] = L'\0';

	for ( i = 1; i < argc; ++ i )
	{
		if ( *argv[i] != '-' && *argv[1] != '/' )
		{
			printf( "\"%s\": switches must start with a dash or a slash.\n",
				argv[i] );
			++ errors;
			continue;
		}

		switch ( argv[i][1] )
		{
			case 's':
				if ( i == argc - 1 ) // no further args?
				{
					printf( "\"%s\" requires the server name as an argument.\n", argv[i] );
					++ errors;
					break;
				}
				if ( *server != L'\0' )
				{
					printf( "\"%s\" can only be used once.\n", argv[i] );
					++ errors;
					++ i; // skip what probably is another server name
					break;
				}
				++ i; // advance arg index to account for server name
				mbstowcs( server, argv[i], MAXLEN );
				break;
			case 'c':
				if ( nClients >= MAXCLIENTS )
				{
					printf( "Only %d client names are allowed.\n", MAXCLIENTS );
					++ i;
					++ errors;
					break;
				}
				++ i;
				mbstowcs( tt, argv[i], MAXLEN );
				clients[nClients] = _wcsdup( tt );
				++ nClients;
				break;
			case 'u':
				if ( haveKillAnonymous )
				{
					printf( "\"%s\" cannot be used with -n.\n", argv[i] );
					++ errors;
					++ i;
					break;
				}
				if ( nUsers >= MAXUSERS )
				{
					printf( "Only %d usernames are allowed.\n", MAXUSERS );
					++ i;
					++ errors;
					break;
				}
				++ i;
				mbstowcs( tt, argv[i], MAXLEN );
				users[nUsers] = _wcsdup( tt );
				++ nUsers;
				break;
			case 'n':
				if ( haveKillAnonymous )
				{
					printf( "\"%s\" can only be used once.\n", argv[i] );
					++ errors;
					break;
				}
				if ( nUsers > 0 )
				{
					printf( "\"%s\" cannot be used with -u.\n", argv[i] );
					++ errors;
					break;
				}
				killAnonymous = true;
				haveKillAnonymous = true;
				break;
			case 'd':
				if ( haveSimulate )
				{
					printf( "\"%s\" can only be used once.\n", argv[i] );
					++ errors;
					break;
				}
				simulate = true;
				haveSimulate = true;
				break;
			case 'i':
				if ( i == argc - 1 ) // no further args?
				{
					printf( "\"%s\" requires the allowed idle time as an argument.\n", argv[i] );
					++ errors;
					break;
				}
				if ( haveInterval )
				{
					printf( "\"%s\" can only be used once.\n", argv[i] );
					++ errors;
					++ i; // skip next arg
					break;
				}
				++ i; // advance arg index to account value
				interval = strtol( argv[i], NULL, 10 );
				haveInterval = true;
				break;
			case 'f':
				if ( i == argc - 1 ) // no further args?
				{
					printf( "\"%s\" requires the time between checks as an argument.\n", argv[i] );
					++ errors;
					break;
				}
				if ( haveCheckInterval )
				{
					printf( "\"%s\" can only be used once.\n", argv[i] );
					++ errors;
					++ i; // skip next arg
					break;
				}
				++ i; // advance arg index to account value
				checkInterval = strtol( argv[i], NULL, 10 );
				haveCheckInterval = true;
				break;
			default:
				printf( "\"%s\" is not a valid switch, sorry.\n", argv[i] );
				++ errors;
				break;
		}
	}

	if ( errors )
	{
		printf( "\n%d command line errors. Pass GO, do not collect $200.\n\n",
			errors );
		goto usage;
	}

	// sort users
	if ( nUsers > 1 )
		qsort( users, nUsers, sizeof( users[0] ), comp );

	// sort clients
	if ( nClients > 1 )
		qsort( clients, nClients, sizeof( clients[0] ), comp );

	// create suicide event
	hSuicide = CreateEvent( NULL, true, false, NULL );
	if ( hSuicide == NULL )
	{
		printf( "Can't create wait event, gle = %lu\n", GetLastError() );
		exit( 1 );
	}

	// install Ctrl-C handler
	SetConsoleCtrlHandler( ConsoleHandler, true );

	do
	{
		i = cull( server, simulate, interval, killAnonymous, nUsers, users, nClients, clients );
		// wait on suicide event
		if ( checkInterval != 0 )
			rc = WaitForSingleObject( hSuicide, checkInterval * 1000 );
	} while ( checkInterval != 0 && rc == WAIT_TIMEOUT );

	CloseHandle( hSuicide );

	return i;
}

