/*
   BRESET.CPP V1.2
   ===============
   Date Created  : 1/18/93
   Created by    : John Leon
   Dates Modified: 1/28/93 - added batch mode switch (see below)
                   2/5/94  - minor cosmetic changes to code
   Last Mod by   : John Leon
   Tools Used    : 1.) Borland C++ 3.1
                   2.) Netware C Interface for DOS
                   3.) BTC Class Library V1.8
                   4.) Novell's Brequest
   Model         : SMALL.
   Purpose       : To do a Btrieve reset on one of the following:
                   1.) Your workstation (specify -1 on command line),
                   2.) A single workstation (by specifying connection #), or
                   3.) All workstations on a network (no command line para-
                       meter and no BRESET.NOT file in default directory),
                   4.) All but those in a list (we'll exclude ALL connections
                       for each name in the list), by specifying no command
                       line parameter and having an ascii file named
                       BRESET.NOT in the current directory.  This file must
                       contain the user names (case not significant) of all
                       those to be excluded from reset.
   Requirements  : 1.) Must have Brequest loaded (and obviously must have the
                       Btrieve NLM running on your server).
                   2.) MUST be run from a network drive,
                   3.) User running this program must have supervisory rights
                       in the directory from which this program is run, and
                   4.) User must have console operator privileges.
   Comments      : 1.) See declaration of BTRV() in BTC.HPP.
                   2.) A "batch mode" switch exists (pass a parameter of /b)
                       to bypass the warning mechanism when performing #3 or
                       #4 under PURPOSE, which would otherwise require a user
                       response.
                   3.) As written, program supports only versions of Netware
                       up to 250 connections.  More specifically, there is
                       a limit on how many connections to exclude from reset
                       equal to EXCLUDELIMIT.
*/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dir.h>        //For getdisk().
#include <conio.h>      //For wherex(), wherey(), gotoxy(), clreol().
#include <io.h>         //For access().
extern "C"  {
#include <nwconn.h>
#include <nwdir.h>
#include <ntt.h>
#include <nwconsol.h>
#include <nwbindry.h>
}
#pragma hdrstop
#include "btc.hpp"      //For BtrieveIsLoaded(), CreateBTFile(), and classes
                        //CFileSpec and BFile.


/* DEFINES & CONSTANTS */
/* ------------------------------------------------------------------------ */
#define TEMPFILE "Breset.BTR"
#define EXCLUDEFILE "Breset.NOT"
#define SUPERVISOR_DIR_RIGHTS_386 0x0100
const int USERNAMELENGTH = 48, EXCLUDELIMIT = 250, MAXCONNECTIONS = 250;


/* GLOBALS */
/* ------------------------------------------------------------------------ */
char PositionBlock[128], FileBuffer[5];
int  BufferSize = 5;
WORD myConnNumber;


/* PROTOTYPES */
/* ------------------------------------------------------------------------ */
int  UserIsLoggedIn();
int  CheckRights();
int  MakeBTRNLMConnection();
int  SecondChance();
int  ResetWithExcludeList();
void ResetAllOtherStations();
int  ResetSingleStation(WORD connNumber);
int  PerformOneReset(const char *stationNumberString);
int  ConnectionIsActive(int connID);


/* MAIN */
/* ======================================================================== */
int main(int argc, char *argv[])
{

                                  /* Get preliminary checks out of the way. */

if ( !BtrieveIsLoaded() )  {
   printf("\nPlease load Brequest before running this program\n");
   exit(1);
}

if ( argc > 2 )  {
   printf("\nToo many command-line parameters ... aborting\n");
   exit(2);
}

if ( !GetDirectoryHandle(getdisk()) )  {
   printf("\nThis program must be run from a network drive ... aborting\n");
   exit(3);
}
                                  /* Save connection ID of PC running the
                                     program.  May need it later. */

myConnNumber = GetConnectionNumber();
if ( !myConnNumber )  {
   printf("\nThis program requires a Novell network connection, supervisor\n");
   printf("privileges in the current directory, console operator privileges,\n");
   printf("and must be run from a network drive ... aborting\n");
   exit(4);
}

if ( !UserIsLoggedIn() )  {
   printf("\nYou must be logged into your network to run this program\n");
   exit(5);
}

                                  /* OK ... Have confirmed user is logged into
                                     the net, has Btrieve loaded, and has
                                     default drive set to a remote (network)
                                     drive.  Now confirm they have supervisor
                                     directory rights in the default direc-
                                     tory, that the mapped volume is not just
                                     remote but is on a Netware 3.0+ server,
                                     and that they have console operator
                                     security equivalence. */

if ( CheckConsolePrivileges() || CheckRights() )  {
   printf("You have insufficient network rights to run this program.\n");
   printf("This program requires console operator privilege level and\n");
   printf("supervisory rights to the current directory ... aborting.\n");
   exit(6);
}

                                   /* Do just enough Btrieve I/O to get a
                                      Btrieve NLM connection.  Create a
                                      Btrieve file on the fly, open it in
                                      exclusive mode, close it, then delete
                                      the darn thing. */

if ( MakeBTRNLMConnection() )  {
   printf("\nCould not confirm your own connection to BTRIEVE NLM"
          " ... aborting\n");
   exit(7);
}

                                /* If an exclusion list (BRESET.NOT) exists,
                                   be prepared to process it. */

boolean excludeListPresent = (access(EXCLUDEFILE, 0) == 0) ? true : false;

                                /* Here's the main switch.  Could get 1 of
                                   following 4 cases, so act accordingly!
                                   BRESET -1  (reset yourself)
                                   BRESET xxx (xxx = a connection number)
                                   BRESET     (BRESET.NOT file present or not)
                                   BRESET /B  (batch mode - no warnings) */
switch (argc)  {
   case 1:  if ( !SecondChance() )  {  // No parameter given, so give warning.
               if ( excludeListPresent )
                  ResetWithExcludeList();
               else  {
                  ResetAllOtherStations();
                  ResetSingleStation(myConnNumber);
               }
            }
            break;
   case 2:  char argv1[10];
            strcpy(argv1, argv[1]);
            if ( strlen(argv1) > 3 )  {
               printf("\nInvalid command line parameter ... aborting\n");
               break;
            }
            strupr(argv1);
            if ( (!strncmp(argv1, "/B", 2)) || (!strncmp(argv1, "-B", 2)) ||
                 (!strncmp(argv1, "B", 1)) )  {
               // We're in batch mode.  No warnings, Jack.
               if ( excludeListPresent )
                  ResetWithExcludeList();
               else  {
                  ResetAllOtherStations();
                  ResetSingleStation(myConnNumber);
               }
               break;
            }
            // Not in batch mode, so test for parameter being -1 or a valid
            // connection, and just do it.
            PerformOneReset(argv1);
            break;
   default: printf("\nUnexpected program condition ... aborting\n");
   }

return 0;
}  //end MAIN


/* FUNCTION DEFINITIONS */
/* ------------------------------------------------------------------------ */

                                      /* Create a Btrieve file on the fly,
                                         open it, close it, then delete it.
                                         This action establishes the required
                                         connection with the Btrieve NLM. */
int MakeBTRNLMConnection()  {
   //Create an arbitrary file of record length 5, page size 512, no keys.
   CFileSpec *fSpec = new CFileSpec(5, 512, 0, NULL);
   BStatus = CreateBTFile(TEMPFILE, fSpec->Specs);
   delete fSpec;
   if ( BStatus )
      return 1;
   //Open the new file in exclusive mode, no owner name, data buffer 5 bytes.
   //Destructor called when object deleted will close the file.
   BFile *resetBTR = new BFile(TEMPFILE, Exclusive, "", 5);
   delete resetBTR;
   remove(TEMPFILE);
   return (BStatus ? 2 : 0);
}

                                /* Reset all connections NOT associated with
                                   usernames in the EXCLUDEFILE.  If a user
                                   name in the EXCLUDEFILE has multiple
                                   connections, NONE of the connections for
                                   that user name will be reset. */

int ResetWithExcludeList()  {
   //Have previously confirmed that EXCLUDEFILE is present, but safety first.
   FILE *excludeFile;
   if ( (excludeFile = fopen(EXCLUDEFILE, "rt")) == NULL )  {
      printf("\nError opening %s ... aborting\n", EXCLUDEFILE);
      return 1;
   }

   char  excludeSingleName[USERNAMELENGTH];
   int   Counter = 0, numConnsExcluded = 0;
   WORD  timesUserConnected;
   WORD *excludeConnList = new WORD[EXCLUDELIMIT]; //Array of 250 conn numbers,
                                                   //enough to hold max #
                                                   //of connections to exclude.
   printf("\n    ** EXCLUDED FROM RESET **\n"
            "---------------------------------\n");
   while ( (fgets(excludeSingleName, USERNAMELENGTH, excludeFile) != NULL) &&
           (Counter < EXCLUDELIMIT) )  {
      //Get rid of the cr/lf combo at end of string read.
      excludeSingleName[strlen(excludeSingleName)-1] = '\0';
      strupr(excludeSingleName);
      if ( GetObjectConnectionNumbers(excludeSingleName,
                                      OT_USER,
                                      &timesUserConnected,
                                      &excludeConnList[numConnsExcluded],
                                      MAXCONNECTIONS) == 0 )
         if ( timesUserConnected )
            printf("%s on %u connections\n", excludeSingleName,
                                             timesUserConnected);
      numConnsExcluded += timesUserConnected;
      Counter++;
      }
   fclose(excludeFile);
   if ( !Counter )  {
      printf("Program aborted ... \n"
             "Could not interpret data in file %s", EXCLUDEFILE);
      return 2;
   }
   printf("---------------------------------\n"
          "Total # excluded connections: %d\n", numConnsExcluded);

   boolean resetSelf = true, okToReset;     //Assume OK to reset self for now.

   for (Counter = 0; Counter < numConnsExcluded; Counter++)  {
      if ( myConnNumber == excludeConnList[Counter] )  {
         resetSelf = false;
         break;
      }
   }

   FILE_SERV_INFO serverInfo;
   GetServerInformation(sizeof(serverInfo), &serverInfo);

   printf("\nResetting connection # ");
   int x = wherex(), y = wherey();
   int Counter1;

   for ( Counter = 1; Counter < (serverInfo.maxConnectionsSupported+1); Counter++ )  {

      if ( Counter != myConnNumber )  {     //Don't reset yourself mid-stream!
         Counter1 = 0;
         okToReset = true;

         //hmmm...potentially gotta traverse entire exclude list for each valid
         //conn number from 1 to maxConnectionsSupported just to see if it is
         //in the (unsorted) list of connections to be excluded ... but at
         //least we break if it's found in list before end of list.

                                  /* Important here to use EXCLUDELIMIT versus
                                     server's max # connections, as one can
                                     have a LARGE number of login IDs that has
                                     no relation to server's conn limit; thus,
                                     the exclude file can be larger than #
                                     connections permitted. */

         for (Counter1; Counter1 < EXCLUDELIMIT; Counter1++)  {
            if ( Counter == excludeConnList[Counter1] )  {
               okToReset = false;
               break;
            }
         }
         if ( okToReset )  {
            gotoxy(x, y);
            printf("%d", Counter);
            BTRV(BReset, PositionBlock, &FileBuffer, &BufferSize,
                 (void *)&Counter, -1);
         }
      }
   }

   gotoxy(1,y);
   clreol();
   printf("Done.\n");
   delete [] excludeConnList;

   if ( resetSelf )
      ResetSingleStation(myConnNumber);
   return 0;
}

                                /* To reset all stations but your own. */
void ResetAllOtherStations()  {
   FILE_SERV_INFO serverInfo;
   GetServerInformation(sizeof(serverInfo), &serverInfo);
   printf("\nResetting connection # ");
   int x = wherex(), y = wherey();
   for ( int Counter = 1; Counter < (serverInfo.maxConnectionsSupported+1); Counter++ )
      if ( Counter != myConnNumber )  {
         gotoxy(x, y);
         printf("%d", Counter);
         BTRV(BReset, PositionBlock, &FileBuffer, &BufferSize, (void *)&Counter, -1);
      }
   gotoxy(1,y);
   clreol();
   printf("Done.\n");
}


int ResetSingleStation(WORD connNumber)  {
   BStatus = BTRV(BReset, PositionBlock, &FileBuffer, &BufferSize,
                  (void *)&connNumber, -1);
   if ( !BStatus )  {
      printf("\nConnection # %u has been reset\n", connNumber);
      return 0;
   }
   else  {
      printf("\nError in attempt to reset connection # %u\n", connNumber);
      printf("Btrieve status code: %d\n", BStatus);
      return BStatus;
   }
}


int CheckRights()  {

   BYTE currentDrive = (BYTE)getdisk(), dirHandle;
   WORD serverConnID, dirRights;
   int cCode;

   GetDriveInformation(currentDrive, &serverConnID, &dirHandle);

   if (!IsV3Supported(serverConnID))  {
      printf("\nThis program requires Netware 386 3.0 or higher on current drive.\n");
      return 1;
   }

   if ( (cCode = GetEffectiveRights(serverConnID, dirHandle, "",
                                    &dirRights) ) != 0 )  {
      printf("\nCannot determine your directory rights.  Code: %d\n", cCode);
      return 2;
   }

   if ( (dirRights & SUPERVISOR_DIR_RIGHTS_386) != SUPERVISOR_DIR_RIGHTS_386 )  {
     printf("\nYou have insufficient directory rights to run this program.\n");
     return 3;
   }

return 0;
}


int ConnectionIsActive(int connID)  {
   char userName[USERNAMELENGTH];
   long objID;
   WORD objType;
   BYTE loginTime[7];
   GetConnectionInformation(connID, userName, &objType, &objID, loginTime);
   return ( objID ? 1 : 0 ); //objID of 0 means no object logged in on connID
}


int UserIsLoggedIn()  {          //Returns 0 if user not logged in to network.
   char userName[USERNAMELENGTH];
   long objID;
   WORD objType;
   BYTE loginTime[7];
   if ( !GetConnectionInformation(myConnNumber, userName, &objType,
                                  &objID, loginTime) )
      return 1;
   else
      return 0;    //Flip response for lexical sense ...such that
}                  //!UserIsLoggedIn makes sense.


int SecondChance()  {
   printf("\nWARNING!!  If you continue, ALL workstations on your network\n");
   printf(           "will receive a Btrieve reset, excluding only those\n");
   printf(           "user names present in the %s file.  If you suspect \n",
          EXCLUDEFILE);
   printf(           "ANY users are in Btrieve applications that should not\n");
   printf(           "be reset, you MUST ANSWER THE FOLLOWING PROMPT \"NO\"!!\n");
   printf("\nRESET WORKSTATIONS? (Y/N): \a\a");
   char response[5];
   gets(response);
   strupr(response);
   if ( !strlen(response) || (response[0] != 'Y') )  {
      printf("\nOperation aborted ...\n");
      return 1;
   }
   return 0;
}


int PerformOneReset(const char *stationNumberString)  {
   int stationNumber;
   if ( (stationNumber = atoi(stationNumberString)) == 0 )  {
      printf("\nInvalid parameter ... aborting\n");
      return 1;
   }
   if ( stationNumber == myConnNumber )  {
      printf("\nERROR: You have selected your own connection ID for reset.\n");
      printf("       If you wish to reset your own connection, enter -1 and\n");
      printf("       NOT your actual connection number.\n");
      return 2;
   }
   if ( stationNumber == -1 )  {
      printf("\nResetting your connection ... \n");
      ResetSingleStation(myConnNumber);
      return 0;
   }
   if ( stationNumber > 0 && stationNumber < 251 )
      if ( ConnectionIsActive(stationNumber) )  {
         ResetSingleStation(stationNumber);
         return 0;
      }
      else  {
         printf("\nConnection #%d is not in use ... aborting\n", stationNumber);
         return 3;
      }
   else  {
      printf("\nInvalid connection number specified ... aborting\n");
      return 4;
   }
}
