{$X+,B-,V-,S-} {essential compiler directives}

UNIT nwConn;

{ nwConn unit as of 950301 / NwTP 0.6 API. (c) 1993,1995, R. Spronk }
{ Includes modifications to Attach/Detach by H. Jelonneck           }

INTERFACE

{ Primary Functions:                    Interrupt: comments:

Connection Services
-------------------

* AttachToFileServer                    (F100)
* AttachToFileServerWithAddress         (F100)
* DetachFromFileServer                  (F101)
. EnterLoginArea                        (F217/0A)
* GetConnectionInformation              (F217/16)
* GetConnectionNumber                   (DC..)
* GetInternetAddress                    (F217/13)
* GetObjectConnectionNumbers            (F217/15)
* GetWorkstationNodeAddress             (EE..)
* LoginToFileserver                     (F217/14) UNencrypted
* LoginEncrToFileserver                 (F217/18) encrypted
* Logout                                (F219)
* LogoutFromFileServer                  (F102)

  Secondary Functions:

* GetUserAtConnection
* GetObjectLoginControl
* GetObjectNodeControl
* ObjectCanLoginAt

Workstation Services
--------------------

* EndOfJob                              (D6)         to be rewritten to F218
* GetConnectionID                       (EF04)
* GetConnectionIDtable                  (EF03)       (1)
* GetDefaultConnectionID                (F002)
* GetEndOfJobStatus                     (BB..)
* GetFileServerName                     (EF04)
* GetNetwareErrorMode                   (DD..)
* GetNetwareShellVersion                (EA00)
* GetNumberOfLocalDrives                (DB..)
* GetPreferredConnectionID              (F001)
* GetPrimaryConnectionID                (F005)
* GetShowDots                           (E908)
* GetWorkstationEnvironment             (EAxx,xx>00) (2)
* SetEndOfJobStatus                     (BB..)
* SetNetwareErrorMode                   (DD..)
* SetPreferredConnectionID              (F000)
* SetPrimaryConnectionID                (F004)
* SetShowDots                           (E908)

  Secondary Functions:

* GetEffectiveConnectionID              (F001,F002,F005)
* IsConnectionIDinUse                   (EF03)


Not Implemented:
----------------

- GetStationsLoggedInformation          (F217/05) (3)
- Login                                 (F217/00) (4)
- MapUserToStationSet                   (F217/02) (5)

Notes: -Only functions marked with a * have been tested; others might work.
       -(1): This function returns the complete Connection ID table. The
             partial function IsConnectionIDInUse has been moved to the
             secondary function group.
       -(2): This function is an extension to EA00 GetNetwareShellVersion.
             A function that returns all returned information from the call
             EAxx,xx>00 is sometimes referred to as GetWShardwareAndOS.

       -NOT implemented in this API:
        (3): Replaced by F217/16 GetConnectionInformation.
        (4): This function has been replaced by F217/14 LoginToFileServer.
        (5): Replaced by F217/15 GetObjectConnectionNumbers.

       -NW 386 can support up to 250 connections, NW 286 Max 100.
       -Type TconnectionList=array[1..250] of byte (Declared in unit nwMisc)

}

Uses nwIntr,nwMisc,nwBindry;


Const MaxServers=8;

Type TServerNameTableEntry = Array [1..48] OF Char;
     TServerNameTable = Array[1..MaxServers] OF TServerNameTableEntry;

     TConnectionIDTableEntry=
       Record
       SlotInUse        : Byte;
       OrderNumber      : Byte;
       ServerAddress    : TinternetworkAddress;
       ReceiveTimeOut   : Word;
       RouterAddress    : TnodeAddress;
       PacketSeqNbr     : Byte;
       ConnectionNumber : Byte;
       ConnectionStatus : Byte;
       MaxTimeOut       : Word;
       WConnectionNumber: Word;
       MajorNWrev       : Byte;
       ServerFlags      : Byte;
       MinorNWrev       : Byte;
       END;
     TConnectionIDTable = Array [1..MaxServers] OF TConnectionIDTableEntry;

     TloginControl=Record
                   AccountDisabled           :boolean;
                   AccountExpirationDate     :TNovTime; { dmy valid only }

                   MinimumPasswordLength     :byte;
                   PasswordControlFlag       :byte;
                   DaysBetweenPasswordChanges:word;
                   PasswordExpirationDate    :TnovTime;
                   LastLoginTime             :TnovTime; {dmy, hms valid only }
                   GraceLoginsRemaining      :Byte;
                   MaxGraceLoginsAllowed     :byte;
                   BadLoginCount             :byte;
                   AccountResetTime          :TnovTime; {dmy, hms valid only }
                   LastIntruderAddress       :TinterNetworkAddress;

                   MaxConcurrentConnections  :byte;
                   LoginTimes                :array[1..42] of byte;

                   DiskSpace                 :Longint;
                   end;

     TnodeControl=array[1..12] of record
                                  net :TnetworkAddress;
                                  node:TnodeAddress;
                                  end;

Var result:word;

{BB.. [2.0/2.1/3.x]}
Function SetEndOfJobStatus( EndOfJobFlag: Boolean ):Boolean;
{ When this function is called with EndOfJobFlag=False and control is returned
  to the root COMMAND.COM, COMMAND.COM will NOT perform an EOJ action.     }

{BB.. [2.0/2.1/3.x]}
Function GetEndOfJobStatus(Var EndOfJobFlag: Boolean ):Boolean;

{F218 [2.15c+]}
FUNCTION EndOfJob(All : Boolean):boolean;
{ Forces an end of job }

{E908 (shell 3.00+)}
Function SetShowDots( Show:Boolean):Boolean;

{E908 (shell 3.00+)}
Function GetShowDots(Var Shown:Boolean):Boolean;

{F219 [2.15c+]}
Function Logout:boolean;
{ Logout from all file servers, remains attached to Server, effective EOJ }

{DB.. [2.0/2.1/3.x]}
Function GetNumberOfLocalDrives( Var drives:Byte ):Boolean;

{DC.. [2.0/2.1/3.x]}
Function GetConnectionNumber(Var ConnectionNbr:byte):boolean;
{ Returns connection number of requesting WS }

{DD.. [2.0/2.1/3.x]}
Function SetNetwareErrorMode( errMode:Byte):boolean;
{ Sets the shell's handling mode for dealing with netware errors. }

{DD.. [2.0/2.1/3.x]}
Function GetNetwareErrorMode(Var errMode:Byte):boolean;

{E3../0A [2.0/2.1/3.x]}
Function EnterLoginArea( LoginSubDirName:String;
                         numberOfLocalDrives:Byte ):boolean;
{ Changes the login directory. Used by boot-proms. }

{F217/13 [2.15c+]}
Function GetInternetAddress( ConnNbr : byte;
                         var IntNetAddress:TinternetworkAddress):boolean;

{F217/14 [2.15c+] UNENCRYPTED}
Function LoginToFileServer( objName:String; objType:word;
                            password : string              ):boolean;

{F217/18 [2.15c+] ENCRYPTED}
FUNCTION LoginEncrToFileServer(ObjName: String; ObjType: Word;
                               PassWord: String            ): Boolean;

{F217/15 [2.15c+]}
Function GetObjectConnectionNumbers( objName:String; objType:Word;
                                     Var numberOfConnections: Byte;
                                     Var connections: TconnectionList ):boolean;
{ returns a list of connectionnumbers where objects of the desired type and
  name are logged in. }

{F217/16 [2.15c+]}
Function GetConnectionInforMation (ConnectionNbr:byte;
                                   Var objName:String;
                                   Var objType:Word;
                                   Var objId:LongInt;
                                   Var LoginTime:TnovTime ):boolean;

{EA00 [2.0/2.1/3.x]}
Function GetNetwareShellVersion( Var MajorVersion,MinorVersion,
                                     RevisionLevel :Byte       ):Boolean;
{ Returns information about a WS environment. Queries shell. }

{EAxx,xx>00 [2.0/2.1/3.x]}
Function GetWorkstationEnvironment(Var OStype,OSversion,
                              HardwareType,ShortHWType:String):Boolean;

{EE.. [2.0/2.1/3.x]}
FUNCTION GetWorkstationNodeAddress( var physicalNodeAddress: TNodeAddress ):boolean;
{ Get the physical address of the workstation (6 bytes hi-endian) }


{EF03 [2.0/2.1/3.x]}
Function GetConnectionIDTable( ConnectionID: Byte ; Var TableEntry: TConnectionIDTableEntry ):boolean;
{ Returns a copy of the entry in the shells' ConnectionID table corresponding
  with the given ConnectionID. }

{EF04 [2.0/2.1/3.x]}
Function GetConnectionID( serverName: String; Var ConnectionID: Byte):boolean;


{EF04 [2.0/2.1/3.x]}
Function GetFileServerName( ConnectionID : byte; var ServerName : string):boolean;
{ get name of file server. file server number must be in range [1..MaxServers] }

{F000 [2.0/2.1/3.x]}
Function SetPreferredConnectionID( ConnectionID :byte ):boolean;

{F001 [2.0/2.1/3.x]}
Function GetPreferredConnectionID(var connID : byte):boolean;

{F002 [2.0/2.1/3.x]}
FUNCTION GetDefaultConnectionID(var connID :byte):boolean;

{F004 [2.0/2.1/3.x]}
FUNCTION SetPrimaryConnectionID( primaryConnectionID :Byte ):boolean;

{F005 [2.0/2.1/3.x]}
FUNCTION GetPrimaryConnectionID(var connID :byte ):boolean;

{F100 [2.0+]}
Function AttachToFileServerWithAddress(ServerName:string;
                                       ServerAddr:TinternetworkAddress;
                                       Var ConnectionID:Byte):Boolean;

{F100 [2.0/2.1/3.x] (also calls EF03,EF04)}
Function AttachToFileServer(ServerName : String; Var ConnectionID:Byte):Boolean;
{ Create an attachment between a server and a workstation. }

{F101 [2.0/2.1/3.x]}
Function DetachFromFileServer( ConnectionID:byte ):boolean;
{ removes server from shell's server table. Relinquishes the
  fileserver connection number and breaks the connection.         }

{F102 [2.0/2.1/3.x]}
Function LogoutFromFileServer(var ConnectionID: byte):boolean;
{logout from one file server}

{***** secondary Functions, Result variable is not used *******************}

{EF03 [2.0/2.1/3.x] secondary Function }
Function IsConnectionIDinUse( ConnectionID: Byte ):boolean;

Function GetUserAtConnection( ConnectionNbr:byte; var username: string):boolean;
{This function provides a short method of obtaining just the USERID.}

Function GetEffectiveConnectionID(Var connId:byte):boolean;
{What server are the requests currently sent to? }

Function GetObjectLoginControl(ObjName:string; ObjType:word;
                               VAR LoginControlInfo:TloginControl):boolean;

Function GetObjectNodeControl( ObjName:string; ObjType:word;
                              {i/o} Var seqNbr:integer;
                              {out} Var NodeControlInfo:TnodeControl):boolean;

Function ObjectCanLoginAt(ObjName:String; ObjType:Word;
                          LoginTime:TnovTime            ):Boolean;
{ -If the fields hour,min,sec and dayOfWeek are filled, the time
   will be checked against the login timerestrictions.
  -If the fields year,month,day are filled ( >0 ), the date
   will be checked with the expiration date of the account and
   with the Account disabled Flag. }

IMPLEMENTATION{=============================================================}

Type TPConnectionIDTPtr=^TConnectionIDTable;
     TPServerNTPtr=^TServerNameTable;

{F000 [2.0/2.1/3.x]}
Function SetPreferredConnectionID( ConnectionID :byte ):boolean;
{ The preferred server is the default server to which the request
  packets are sent.
  Calls are routed to the preferred server. (IF explicitly set!).
  If the preferred server was not set then the requests are routed to
  the server that is attached to the current drive. If the current
  drive is a local drive then the requests will be sent to the primary
  server (mostly the server the shell initially attached to.)            }
var regs : TTregisters;
begin
 regs.ax := $F000;
 regs.dl := ConnectionID; { 1..MaxServers, 0 to clear }
 RealModeIntr($21,regs);
 result:=0;
 SetPreferredConnectionID:=TRUE;
end;

{F004 [2.0/2.1/3.x]}
FUNCTION SetPrimaryConnectionID( primaryConnectionID :Byte ):boolean;
var regs : TTregisters;
begin
 regs.ax := $F004;
 regs.dl := primaryConnectionID; { 1..MaxServers, or 0 to clear }
 RealModeIntr($21,regs);
 result:=0;
 SetPrimaryConnectionID:=TRUE;
end;

{F005 [2.0/2.1/3.x]}
FUNCTION GetPrimaryConnectionID(var connID :byte ):boolean;
{ returns connection number of the primary file server (1..MaxServers) }
var regs : TTregisters;
begin
 regs.ax := $F005;
 RealModeIntr($21,regs);
 connID := regs.al;
 if connId>MaxServers
  then result:=$FF
  else result:=$00;
 GetPrimaryConnectionID:=(result=0);
end;

{F002 [2.0/2.1/3.x]}
FUNCTION GetDefaultConnectionID(var connID :byte):boolean;
{ Returns the connection ID of the file server to which
  the packets are currently being sent.                                    }
var regs : TTregisters;
begin
 regs.ax := $F002;
 RealModeIntr($21,regs);
 connID := regs.al; { 1..MaxServers }
 if connId>MaxServers
  then result:=$FF
  else result:=$00;
 GetDefaultConnectionID:=(result=0);
end;

{F001 [2.0/2.1/3.x]}
Function GetPreferredConnectionID(var connID : byte):boolean;
var regs : TTregisters;
begin
 regs.ax := $F001;
 RealModeIntr($21,regs);
 connID := regs.al; { 1..MaxServers, or 0 if the preferred server was not set }
 { The preferred coneection is reset to 0 by an EOJ. }
 if connId>MaxServers
  then result:=$FF
  else result:=$00;
 GetPreferredConnectionID:=(result=0);
end;



{EF04 [2.0/2.1/3.x]}
Function GetConnectionID( serverName: String; Var ConnectionID: Byte):boolean;
Type ptarr=^arr;
     arr=Array[0..MaxServers*32] of Byte;
Var regs       : TTregisters;
    NameTable  : Array [1..MaxServers*48] of Byte;
    ServerNames: Array [1..MaxServers] of String;
    t          : Byte;
begin
UpString(ServerName);
regs.ax := $EF04;
RealModeIntr($21,regs);
{ get pointer to shell's server name table. }
move(nwPtr(regs.es, regs.si)^,NameTable,MaxServers*48);
For t := 0 to 7
  do ZstrCopy(ServerNames[t+1],NameTable[1+ t*48],48);

t:=1;
While ((t<9) and (ServerNames[t]<>ServerName))
 do inc(t);
If t=9
 then Result:=$FC { invalid server name }
 else begin
      ConnectionID:=t;
      { ServerName found. Is ConnectionID valid ? }
      regs.ax:=$EF03;
      RealModeIntr($21,regs);
      IF (ptarr(nwPtr(regs.es,regs.si))^[(ConnectionID-1)*32] = $00 ) {= $FF ?? }
       then begin
            ConnectionID:=0;
            result:=$FC { ConnectionID invalid => servername invalid }
            end
       else result:=$00;
      end;
GetConnectionID:=(result=0);
end;


{EF04 [2.0/2.1/3.x]}
Function GetFileServerName( ConnectionID : Byte; Var ServerName : String):boolean;
{ Get the name of file server, associated with the ConnectionID.
  The File server number must be in the range [1..MaxServers].
  The function will fail (result=$FF) if connID falls outside of this range. }
Type ptarr=^arr;
     arr=Array[0..MaxServers*32] of Byte;
Var regs       : TTregisters;
    NameTable  : Array [1..MaxServers*48] of Byte;
    ServerNames: Array [1..MaxServers] of String;
    t          : Byte;
begin
regs.ax := $EF04;
RealModeIntr($21,regs);
{ Get pointer to shell's server name table. }
move(nwPtr(regs.es, regs.si)^,NameTable,MaxServers*48);
For t := 0 to 7
  do ZstrCopy(ServerNames[t+1],NameTable[1+ t*48],48);

if ((ConnectionID<1) or (ConnectionID>MaxServers))
  then ServerName:=''
  else ServerName := ServerNames[ConnectionID];
IF ServerName=''
 then result:=$FF
 else begin { The name is valid, but is the ConnectionID valid ? }
      regs.ax:=$EF03;
      RealModeIntr($21,regs);
      IF (ptarr(nwPtr(regs.es,regs.si))^[(ConnectionID-1)*32] = $00 ) {= $FF ?? }
       then begin
            result:=$FF; { ConnectionID invalid => servername invalid }
            ServerName:='';
            end
       else result:=$00;
      end;
GetFileServerName:=(result=0);
end;


Function AttachToFileServerWithAddress(ServerName:string;
                                       ServerAddr:TinternetworkAddress;
                                       Var ConnectionID:Byte):Boolean;
{ Create an attachment between a server and a workstation. }
{ Does not Login the workstation. }
{ After attaching, and beFore logging in, you must set the preferred server
  to the ConnectionID of the server. }
{ Will not report an error if you're already attached to
 -or even logged on to- the target server. }
{ Attaches to the server whose address is supplied. The server name will
  be placed in the server name tables, even if the servername is incorrect or
  the supplied servername isn't associated with the supplied address. }
{ Based on the InsertServer Function in LOGON.PAS by Barry Nance, and
  on Rose, p.262 }
Var  ConnectionIDTPtr : TPConnectionIDTPtr;
     ServerNTPtr      : TPServerNTPtr;
     NewServerSlot,i  : Byte;
     OldConnId        : Byte;
     ServIsAttached   : Boolean;
     AccessLevel      : Byte;
     ObjID            : Longint;

     Regs:TTRegisters;

     NewServer:Boolean;

     Var cid:byte;

BEGIN
{ If server known, take adress from shells' tables.
  If server not known, try to read its' adress from a servers' bindery.
  This will fail if you're not connected to at least one server.
  Once an adress has been found, AttachToFileServerWithAdress is called. }

ServerAddr.socket:=swap($0451); { swapped hi-lo}
UpString(ServerName);

regs.ax:=$EF03;
RealModeIntr($21,regs);
ConnectionIDTPtr:=nwPtr(regs.es,regs.si); { Ptr to TConnectionIDTable }

{ Determine whether the suplied server is already known/attached to }

ConnectionID:=0;
REPEAT
 inc(ConnectionID)
UNTIL (ConnectionID>MaxServers)
      or ((ConnectionIDTPtr^[ConnectionID].SlotInUse>0)
          and IsEqualNetworkAddress(ConnectionIDTPtr^[ConnectionID].ServerAddress,ServerAddr)
         );

NewServer:=(ConnectionID>MaxServers);

{ If the server is a new server, put it in the sorted ConnectionIDTable }
IF NewServer
 then begin
      { Determine free slot to insert new server }
      NewServerSlot := 1;
      WHILE (ConnectionIDTPtr^[NewServerSlot].SlotInUse <> $00)
            AND (NewServerSlot <= MaxServers)
        do inc(NewServerSlot);
      IF NewServerSlot > MaxServers
       then begin
            Result:=$7C;
            AttachToFileServerWithAddress := False;
            exit;
            end;

      With ConnectionIDTPtr^[NewServerSlot]
       do begin
          ServerAddress:=ServerAddr;
          OrderNumber := 0;
          For i := 1 TO MaxServers
           do begin
              IF (ConnectionIDTPtr^[i].SlotInUse <> $00)
                 and (ConnectionIDTPtr^[i].OrderNumber>=OrderNumber)
               then OrderNumber:=ConnectionIDTPtr^[i].OrderNumber+1;
              end;
          SlotInUse := $FF; { Must be set to $FF before attaching }
          end;
      ConnectionID:=NewServerSlot;
      end
 else { NOT a new server.. }
      IF (ConnectionIDTPtr^[ConnectionID].ConnectionNumber > 0)
          AND (ConnectionIDTPtr^[ConnectionID].ConnectionNumber < $FF)
          AND (ConnectionIDTPtr^[ConnectionID].ConnectionStatus = $FF)
       then Begin { ServerIsKnown }
            GetPreferredConnectionID (OldConnId);
            SetPreferredConnectionID (ConnectionID);
            ServIsAttached := GetBinderyAccessLevel (AccessLevel, ObjID);
            SetPreferredConnectionID (OldConnID);
            IF ServIsAttached  { ServerIsAlreadyAttached / caller may even be looged on }
             then begin
                  result:=0;
                  AttachToFileServerWithAddress := True;
                  exit;
                  end;
            End;

{ Create an attachment }
With Regs
do begin
   AX := $F100;
   DL := ConnectionID;
   RealModeIntr($21,Regs);
   Result := AL;
   { F8 already attached to server; F9 No Free connection slots at server;
     FA no more server slots;       FE Server Bindery Locked;
     FF No response from server }
   end;

IF NewServer
 then begin
      if Result<>$00 { F9/FA/FE/FF error at server/no response from server }
       then Begin    { Note that the combination of a 'new' server and err. F8 is impossible }
            ConnectionIDTPtr^[NewServerSlot].SlotInUse:=$00;
            { Invalid server, Free slot again }
            end
       else begin
            { Valid server, sort ConnectionID table }
            With ConnectionIDTPtr^[NewServerSlot]
             do begin
                SlotInUse:=$00; { temporarily set to 0, For sorting purposes }
                OrderNumber := 1;
                For i := 1 TO MaxServers
                 do begin
                    IF ConnectionIDTPtr^[i].SlotInUse <> $00
                     then begin
                          IF IsLowerNetworkAddress(ConnectionIDTPtr^[i].ServerAddress, ServerAddress)
                           then inc(OrderNumber)
                           else inc(ConnectionIDTPtr^[i].OrderNumber)
                          end;
                    end;
                SlotInUse:=$FF;
                end;
            { Put new servers' name in server Name Table }
            regs.ax := $EF04;
            RealModeIntr($21,regs);
            ServerNTPtr:=nwPtr(regs.es, regs.si); { pointer to shell's server name table. }
            FillChar(ServerNTPtr^[NewServerSlot],48,#0);
            If ServerName[0]>#47
            then ServerName[0]:=#47;
            Move(ServerName[1],ServerNTPtr^[NewServerSlot],Length (ServerName));
            end;
      end;

AttachToFileServerWithAddress:=(result=0);
{ Valid completion codes:
  7C Maximum number of attached servers exceeded.
  F8 already attached to server;
  F9 No Free connection slots at specified server;
  FA no more server slots;
  FF No response from server
  FC No Free slots in shells' ConnectionID table; }
end;


Function AttachToFileServer(ServerName : String; Var ConnectionID:Byte):Boolean;
{ Create an attachment between a server and a workstation. }
{ !! you have to be attached to at least 1 server before calling this function. }
{ Does not Login the workstation. }
{ After attaching, and beFore logging in, you must set the preferred server
  to the ConnectionID of the server. }
{ Will not report an error if you're already attached to
 -or even logged on to- the target server. }
Var  ConnectionIDTPtr : TPConnectionIDTPtr;
     OldConnId        : Byte;
     ServIsAttached   : Boolean;
     AccessLevel      : Byte;
     ObjID            : Longint;

     PropValue        :Tproperty;
     MoreSegments     :boolean;
     PropFlags        :Byte;

     Regs:TTRegisters;

     ServAddr:TinternetworkAddress;
BEGIN
{ If server known, take adress from shells' tables.
  If server not known, try to read its' address from a servers' bindery.
  This will fail if you're not connected to at least one server.
  Once an adress has been found, AttachToFileServerWithAdress is called. }
UpString(ServerName);

regs.ax:=$EF03;
RealModeIntr($21,regs);
ConnectionIDTPtr:=nwPtr(regs.es,regs.si); { Ptr to TConnectionIDTable }

{ Determine whether the suplied server is already known/attached to }
IF GetConnectionID(ServerName,ConnectionID)
 then Begin
      IF (ConnectionIDTPtr^[ConnectionID].ConnectionNumber > 0)
         AND (ConnectionIDTPtr^[ConnectionID].ConnectionNumber < $FF)
         AND (ConnectionIDTPtr^[ConnectionID].ConnectionStatus = $FF)
       then Begin { ServerIsKnown }
            GetPreferredConnectionID (OldConnId);
            SetPreferredConnectionID (ConnectionID);
            ServIsAttached := GetBinderyAccessLevel (AccessLevel, ObjID);
            SetPreferredConnectionID (OldConnID);
            result:=0;
            IF ServIsAttached  { ServerIsAlreadyAttached / caller may even be looged on }
             then begin
                  AttachToFileServer := True;
                  exit;
                  end
             else ServAddr:=ConnectionIDTPtr^[ConnectionID].ServerAddress;
            end
      End
 Else begin
      IF ReadPropertyValue(ServerName,OT_FILE_SERVER,'NET_ADDRESS',1,PropValue,moreSegments,propFlags)
       then begin
            result:=0;
            Move(PropValue,ServAddr,SizeOf(TinternetworkAddress));
            end
       else begin
            Result:=$FC;
            AttachToFileServer:=False;
            exit;
            end;
     End;

if result=0
 then AttachToFileServerWithAddress(ServerName,ServAddr,ConnectionID);

AttachToFileServer:=(result=0);
{ Valid completion codes:
  7C Maximum number of attached servers exceeded.
  7D Bindery read error (The supplied server can't be located/doesn't exist)
  F8 already attached to server;
  F9 No Free connection slots at specified server;
  FA no more server slots;
  FE Server Bindery Locked;
  FF No response from server
  FC No Free slots in shells' ConnectionID table; }
END;


{F101 [2.0/2.1/3.x]}
Function DetachFromFileServer( ConnectionID:Byte ):boolean;
{ removes server from shell's server table. Relinquishes the
  fileserver connection number and breaks the connection.
  The function will fail (result=$FF) if connID falls outside of the range [1..MaxServers].}
Type ArrPtr=^Tarr;
     Tarr=Array[0..MaxServers*48] of Byte;
Var regs : TTregisters;
begin
if (ConnectionID<1) or (ConnectionID>MaxServers)
 then result:=$FF
 else begin
      regs.ax := $F101;
      regs.dl := ConnectionID; { 1..MaxServers }
      RealModeIntr($21,regs);
      result := regs.al;
     { returncodes: 00 successful; FF Connection Doesn't exist }
     end;
DetachFromFileServer:=(result=0);
end;


{EF03 [2.0/2.1/3.x]}
Function GetConnectionIDTable( ConnectionID: Byte ; Var TableEntry: TConnectionIDTableEntry ):boolean;
{ Returns a copy of the entry in the shells' ConnectionID table corresponding
  With the given ConnectionID. All fields are returned lo-hi, except Net and Node
  addresses.
  The function will fail (result=$FF) if connID falls outside of the range [1..MaxServers].}
Type ptarr=^tarr;
     tarr=Array[0..MaxServers*32] of Byte;
Var regs:TTregisters;
begin
If ((ConnectionID<1) or (ConnectionID>MaxServers))
 then Result:=$FF
 else begin
      regs.ax:=$EF03;
      RealModeIntr($21,regs);
      move( ptarr(nwPtr(regs.es,regs.si))^[(ConnectionID-1)*32], TableEntry, 32 );
      With TableEntry
       do begin
          ServerAddress.socket:=swap(ServerAddress.socket); { Force lo-hi }
          ReceiveTimeOut:=swap(ReceiveTimeOut); { Force lo-hi }
          MaxTimeOut:=swap(MaxTimeOut); { Force lo-hi }
          WconnectionNumber:=swap(WconnectionNumber); { force lo-hi }
          end;
      Result:=$00;
      end;
GetConnectionIDTable:=(Result=0);
end;


{DC.. [2.0/2.1/3.x]}
Function GetConnectionNumber(Var ConnectionNbr:byte):boolean;
{ returns connection number of requesting WS (1..100) }
var regs:TTRegisters;
begin
regs.Ah:=$DC;
RealModeIntr($21,regs);
ConnectionNbr:=Regs.AL; { logical WS connection # }
{ cl= first digit of logical conn #, ch= second digit of conn# }
result:=0;
GetConnectionNumber:=true;
end;


{F217/16 [2.15c+]}
Function GetConnectionInformation (ConnectionNbr:byte;
                                   Var objName:String;
                                   Var objType:Word;
                                   Var objId:LongInt;
                                   Var LoginTime:TnovTime ):boolean;
Type TReq=Record
          PacketLength  : Word;
          FunctionVal   : Byte;
          _ConnectionNo : Byte;
          End;
     Trep=Record
          _objId        :LongInt;  { hi-lo }
          _ObjType     : word;     { hi-lo }
          _ObjName     : Array [1..48] of Byte;
          _LoginTime    : TnovTime;
          Reserved:word;
          End;
     TPreq=^Treq;
     TPrep=^Trep;
Var i,x: Integer;
Begin
With TPreq(GlobalReqBuf)^
Do Begin
   PacketLength := 2;
   FunctionVal := $16;
   _ConnectionNo := ConnectionNbr;
   End;
F2SystemCall($17,SizeOf(Treq),SizeOf(TRep),result);
If Result = 0
 Then Begin
      With TPrep(GlobalReplyBuf)^
      Do Begin
         ZstrCopy(ObjName,_objName,48);
         ObjId:=Lswap(_objId);
         ObjType:=swap(_objType);
         logintime:=_logintime;
         End;
      End;
{ patch to have a NIL object return an error. }
if objName='' then result:=$FD; { no_such_connection }
GetConnectionInformation:=(result=0);
End { GetConnectInfo };



{F217/14 [2.15c+,unencrypted]}
Function LoginToFileServer( objName:String; objType:word;
                            password : string             ):boolean;
Type Treq=record
          len      :word;
          subFunc  :byte;
          _objType :Word; { hi-lo }
          _objName :String[47]; { asciiz?  }
          _objPassw:String[127]; { allowed to be '' }
          end;
     TPreq=^Treq;
Begin
WITH TPreq(GlobalReqBuf)^
do begin
   len:=SizeOf(Treq)-2;
   subFunc:=$14;
   _objType:=swap(objType);
   PStrCopy(_objName,objName,47); _objName[47]:=#0; UpString(_objName);
   PStrCopy(_objPassw,password,127); UpString(_objPassw);
   end;
F2SystemCall($17,SizeOf(Treq),0,result);
LoginToFileServer:=(result=0)
end;


{F217/18 [3.x]}
FUNCTION LoginEncrToFileServer(ObjName: String; ObjType: Word; PassWord: String): Boolean;
{ assumes the current effective server = the server to login to. }


   FUNCTION LoginEncrypted(ObjName : String; ObjType : Word; VAR key : TencryptionKey): Boolean;
   Type Treq=RECORD
             BufLen  : Word;
             _func   : Byte;
             _key    : TencryptionKey;
             _ObjType: Word;
             _ObjName: String[48];
             End;
        TPreq=^Treq;
   Begin
   With TPreq(GlobalReqBuf)^
    do Begin
       _func := $18;
       _key  := key;
       _ObjType := Swap(objType);
       PstrCopy(_ObjName,ObjName,48); UpString(_ObjName);
       if ObjName[0]<#48
         then _objName[0]:=objName[0]
         else _objname[0]:=#48;
       BufLen:=ord(_ObjName[0])+12;
       End;
   F2SystemCall($17,SizeOf(Treq),0,result);
   LoginEncrypted:=(result=0);
   End;

VAR
  key : TencryptionKey;
  ObjId:LongInt;

Begin
UpString(password);
if password[0]>#127
 Then password[0]:=#127;

IF GetEncryptionKey(key)
 Then Begin
      IF GetBinderyObjectId(objName,objType,ObjId)
       Then Begin
            EncryptPassword(objId,password,key);
            LoginEncrypted(ObjName, ObjType, key);
            End;
      End
 Else LoginToFileServer(ObjName, ObjType, Password);

LoginEncrToFileServer:= (result=0);
End;


{F219 [2.15c+]}
Function Logout:boolean;
{logout from all file servers, remains attached to Server, effective EOJ }
begin
 F2SystemCall($19,0,0,result);
 result:=$00;
 Logout:=true;
end;


{F102 [2.0/2.1/3.x]}
Function LogoutFromFileServer(var ConnectionID: byte):boolean;
{logout from one file server}
var regs : TTregisters;
begin
 regs.ah := $F1;
 regs.al := $02;
 regs.dl := ConnectionID;
 RealModeIntr($21,regs);
 result:=00;
 LogoutFromFileServer:=True;
end;

{EE.. [2.0/2.1/3.x]}
FUNCTION GetWorkstationNodeAddress( var physicalNodeAddress: TNodeAddress ):boolean;
{ Get the physical station address (6 bytes hi-endian) }
Var Regs              :TTRegisters;
Begin
 {Get the physical address from the Network Card}
 Regs.Ah := $EE;
 RealModeIntr($21,Regs);
 result:=Regs.AL;
 {nw node= CX BX AX hi-endian}
 physicalNodeAddress[1]:=Regs.CH;
 physicalNodeAddress[2]:=Regs.CL;
 physicalNodeAddress[3]:=Regs.bh;
 physicalNodeAddress[4]:=Regs.bl;
 physicalNodeAddress[5]:=Regs.ah;
 physicalNodeAddress[6]:=Regs.al;
 result := 0;
 GetWorkstationNodeAddress:=true;
End;


{F217/13 [2.15c+]}
Function GetInternetAddress( ConnNbr : byte;
                         Var IntNetAddress:TinterNetworkAddress
                                                               ):boolean;
Type  TReq=record
           length      : word;
           subfunction : byte;
           connection  : byte;
           end;
      TRep=record
           network : LongInt; { array [1..4] of byte } { hi-lo }
           node    : array [1..6] of byte;             { hi-lo }
           socket  : word; { array [1..2] of byte }    { hi-lo }
           end;
      TPreq=^Treq;
      TPrep=^Trep;
BEGIN
With TPreq(GlobalreqBuf)^
do begin
   length := 2;
   subfunction := $13;
   connection := ConnNbr;
   end;
F2SystemCall($17,SizeOf(Treq),SizeOf(TRep),result);
if result = 0
then With TPrep(GlobalreplyBuf)^
      do begin
         move(network,IntNetAddress.net,4); {_is_ and stays hi-lo }
         move(node,IntNetAddress.node,6);  { _is_ and stays hi-lo }
         IntNetAddress.socket:=swap(socket);    { force lo-hi }
         end;
GetInternetAddress:=(result=0);
end;

{D6.. [2.0/2.1/3.x]}
FUNCTION EndOfJob(All : Boolean):boolean;
{ forces an end of job
  If All is TRUE, then ends all jobs, otherwise ends a single job.
  Ending a job unlocks and clears all locked or logged files and records.
  It close all open network and local files and resets error and lock modes.
  It also resets the workstation environment.                               }
Var NovRegs:TTRegisters;
BEGIN
with NovRegs
do begin
   AH := $D6;
   if All
    then BX := $FFFF
    else BX := $00;
   end;
RealModeIntr($21,NovRegs);
Result:=$00;
EndOfJob:=True;
end;

{$IFDEF NewCalls}

{F218 [2.15c+]}
FUNCTION EndOfJob(All : Boolean):boolean;
{ forces an end of job
  If All is TRUE, then ends all jobs, otherwise ends a single job.
  Ending a job unlocks and clears all locked or logged files and records.
  It close all open network and local files and resets error and lock modes.
  It also resets the workstation environment.                               }
Type Treq=record
          len:word;
          _all:word;
          end;
          { ??? ERR: unclear how the req buffer should be... }
     TPreq=^Treq;
BEGIN
With TPreq(GlobalReqBuf)^
 do begin
    if All
     then _all := $FFFF
     else _all := $0000;
    len:=2;
    end;
F2SystemCall($18,2,0,result);
Result:=$00;
EndOfJob:=True;
end;

{$ENDIF}


{F217/0A [2.0/2.1/3.x]}
Function EnterLoginArea( LoginSubDirName:String;
                         numberOfLocalDrives:Byte ):boolean;
{ Changes the login directory. Used by boot-proms.
  LoginSubDirName contains the name of a sub directory under SYS:LOGIN
  (e.g. 'V330' means login.exe is to be executed in directory SYS:LOGIN\V330)}
Type Treq=record
          len:word;
          subFunc:byte;
          _numLocDr:Byte;
          _subDirName:String[255];
          end;
     TPreq=^Treq;
Begin
WITH TPreq(GlobalReqBuf)^
do begin
   len:=SizeOf(Treq)-2;
   subFunc:=$0A;
   _numLocDr:=numberOfLocalDrives;
   PstrCopy(_subDirName,LoginSubDirName,255); UpString(_subDirName);
   end;
F2SystemCall($17,Sizeof(Treq),0,result);
EnterLoginArea:=(result=0)
end;

{F217/15 [2.15c+]}
Function GetObjectConnectionNumbers( objName:String; objType:Word;
                                     Var numberOfConnections: Byte;
                                     Var connections: TconnectionList ):boolean;
{ returns a list of connectionnumbers where objects of the desired type and
  name are logged in.
  Tconnectionlist is defined as an array[1..100] of byte. Max connections for
  Netware 286 = 100. Netware 386 allows more than 100 connections.
  If you pass a bad Objectname or the object is not logged in, the errorcode
  is NOT set to NO_SUCH_OBJECT ($FC), but GetObjectConnectionNumbers returns 0.}
Type Treq=record
          len:word;
          subFunc:byte;
          _objType:Word; { hi-lo}
          _objName:String[47];
          end;
     Trep=record
          _NbrOfConn:Byte;
          _connList:TconnectionList
          end;
     TPreq=^Treq;
     TPrep=^Trep;
Begin
WITH TPreq(GlobalReqBuf)^
do begin
   len:=SizeOf(Treq)-2;
   subFunc:=$15;
   PstrCopy(_objName,objName,47); _objname[47]:=#0; UpString(_objName);
   _objType:=swap(objType);
   end;
F2SystemCall($17,SizeOf(Treq),SizeOf(Trep),result);
With TPrep(GlobalReplyBuf)^
do begin
   connections:=_connList;
   NumberOfConnections:=_NbrOfConn;
   end;
getObjectConnectionNumbers:=(result=0)
end;


{EA00 [2.0/2.1/3.x]}
Function GetNetwareShellVersion( Var MajorVersion,MinorVersion,
                                     RevisionLevel :Byte       ):Boolean;
{ Returns information about a WS environment. Queries shell.
  See also: GetWorkstationEnvironment                                   }

Var regs:TTRegisters;
    tmp1,tmp2:word;
Begin
With regs
do begin
   AX:=$EA00;
   GetGlobalBufferAddress(tmp1,tmp2,ES,DI);
   { Set ES:DI to real-mode address of GlobalReplyBuffer }
   { Returned value NOT used, but registers need a valid value anyway. }
   RealModeIntr($21,regs);
   MajorVersion:=BH;
   MinorVersion:=BL;
   { shell version>=3.00 : }
   { CH = Shell Type. 0=conventional, 1= expanded, 2= extended }
   RevisionLevel:=CL; { 1=A,2=B etc. }
   end;
Result:=$00;
GetNetwareShellVersion:=True;
end;

{EAxx,xx>00 [2.0/2.1/3.x] (shell version >=3.00) }
Function GetWorkstationEnvironment(Var OStype,OSversion,
                              HardwareType,ShortHWType:String):Boolean;
Type Treply=record
            stringz4:array[1..4*32] of char;
            end;
     TPreply=^Treply;
Var regs:TTRegisters;
    sNo,k:Byte;
    tmp1,tmp2:word;
Begin
With regs
do begin
   AX:=$EA01;
   BX:=$00;
   GetGlobalBufferAddress(tmp1,tmp2,ES,DI);
   { set ES:DI to real-mode address of GlobalReplyBuffer }
   RealModeIntr($21,regs);
   end;
OStype:='';
OSVersion:='';
HardwareType:='';
ShortHWtype:='';
sNo:=0;k:=0;
With TPreply(GlobalReplyBuf)^
do begin
   while sNo<4
   do begin
      inc(k);
      while ((k<128) and (stringz4[k]<>#0))
      do begin
         Case sNo of
          0:OStype:=OStype+stringz4[k];
          1:OSversion:=OSversion+stringz4[k];
          2:HardwareType:=HardwareType+stringz4[k];
          3:ShortHWtype:=ShortHWtype+stringz4[k];
         end; {case}
         inc(k);
         end;
      inc(Sno);
      end;
   end;
Result:=$00;
GetWorkstationEnvironment:=True;
end;

{DD.. [2.0/2.1/3.x]}
Function SetNetwareErrorMode( errMode:Byte):boolean;
{ Sets the shell's handling mode for dealing with netware errors.
  0: default, INT 24 handler 'Abort, Retry, Fail';
  1: a netware error number is returned in AL;
  2: the netware error number is translated to a DOS error number,
     this number is returned.
  An EOJ resets the errormode to 0.                                      }
Var regs:TTregisters;
begin
Regs.AH:=$DD;
Regs.DL:=errMode;
RealModeIntr($21,Regs);
{ regs.al now contains previous error mode }
Result:=$00;
SetNetWareErrorMode:=True;
end;

{DD.. [2.0/2.1/3.x]}
Function GetNetwareErrorMode(Var errMode:Byte):boolean;
Var regs:TTregisters;
begin
Regs.AH:=$DD;
Regs.DL:=0;
RealModeIntr($21,Regs);
{ regs.al now contains previous error mode }
errMode:=regs.al;
regs.ah:=$DD;
RealModeIntr($21,regs); { reset old error mode }
Result:=$00;
GetNetWareErrorMode:=True;
end;



{BB.. [2.0/2.1/3.x]}
Function SetEndOfJobStatus( EndOfJobFlag: Boolean ):Boolean;
{ When this function is called with EndOfJobFlag=False and control is returned
  to the root COMMAND.COM, COMMAND.COM will NOT perform an EOJ action.     }
Var regs:TTRegisters;
begin
regs.AH:=$BB;
If EndOfJobFlag
 then regs.AL:=$01
 else regs.AL:=$00;
RealModeIntr($21,Regs);
{ AL now contains previous EOJ Flag }
Result:=$00;
SetEndOfJobStatus:=True;
end;

{BB.. [2.0/2.1/3.x]}
Function GetEndOfJobStatus(Var EndOfJobFlag: Boolean ):Boolean;
Var regs:TTRegisters;
begin
regs.AH:=$BB;
regs.al:=$00;
RealModeIntr($21,Regs);
{ AL now contains previous EOJ Flag }
EndOfJobFlag:=(regs.al<>0);
regs.ah:=$BB;
RealModeIntr($21,regs); { reset old eoj-status }
Result:=$00;
GetEndOfJobStatus:=True;
end;

{E908 (shell 3.00+)}
Function SetShowDots( Show:Boolean):Boolean;
Var regs:TTregisters;
begin
regs.ax:=$E908;
if Show
 then regs.bl:=$01
 else regs.bl:=$00;
RealModeIntr($21,Regs);
Result:=$00;
SetShowDots:=True;
end;

{E908 (shell 3.00+)}
Function GetShowDots(Var Shown:Boolean):Boolean;
Var regs:TTregisters;
begin
regs.ax:=$E908;
RealModeIntr($21,Regs);
Shown:=(regs.bl<>0);
regs.ax:=$E908;
RealModeIntr($21,regs); {reset old 'show dots' parameter}
Result:=$00;
GetShowDots:=True;
end;

{DB.. [2.0/2.1/3.x]}
Function GetNumberOfLocalDrives( Var drives:Byte ):Boolean;
Var regs:TTregisters;
begin
regs.ah:=$DB;
RealModeIntr($21,Regs);
drives:=Regs.AL;
Result:=$00;
GetNumberOfLocalDrives:=TRUE;
end;


{=======SECONDARY FUNCTIONS===================================================}


{EF03 [2.0/2.1/3.x] secondary Function }
Function IsConnectionIDinUse( ConnectionID: Byte ):boolean;
{ This function returns FALSE if connId isn't in the range [1..MaxServers] }
Type ptarr=^arr;
     arr=Array[0..MaxServers*32] of Byte;
Var regs:TTregisters;
begin
If ((ConnectionID<1) or (ConnectionID>MaxServers))
 then IsConnectionIDInUse:=FALSE { NWTP04: TRUE }
 else begin
      regs.ax:=$EF03;
      RealModeIntr($21,regs);
      IsConnectionIDinUse:=(ptarr(nwPtr(regs.es,regs.si))^[(ConnectionID-1)*32]
                            <> $00 )
      end;
end;

Function GetUserAtConnection( ConnectionNbr:byte; var username: string):boolean;
{This function provides a shorter method of obtaining just the USERID.}
var id:LongInt;
    typ:word;
    time:TnovTime;
begin
 getUserAtConnection:=GetConnectionInformation(ConnectionNbr,username,typ,id,time);
end;


Function GetEffectiveConnectionID(Var connId:byte):boolean;
begin
if NOT (GetPreferredConnectionID(connId) and (connId<>0))
 then if NOT (GetDefaultConnectionID(ConnId) and (connId<>0))
       then GetPrimaryConnectionID(ConnId);
GetEffectiveConnectionID:=(result=$00);
end;


Function GetObjectLoginControl(ObjName:string; ObjType:word;
                               VAR LoginControlInfo:TloginControl):boolean;
{ Caller must have access to the bindery property LOGIN_CONTROL.
  Default: you need to be supervisor-equivalent or the object the property
           is associated with. (reading your 'own' information)

  PasswordcontrolFlag:
  00 User is allowed to change PW.
  01 User is NOT allowed to change PW.
  02 User is allowed to change PW, but the new password must be unique.
  03 User is NOT allowed to change PW, and a new password, to be changed
     by the supervisor, must be unique.
}
Var LCpropVal:Tproperty;
    lc:record
       _AccExpDate          :array[1..3] of byte; {yy mm dd}
       _AccDisabled         :boolean;
       _PWexpDate           :array[1..3] of byte; {yy mm dd}
       _GraceLoginsRemaining:byte;
       _DaysBetwPWchanges   :word; {hi-lo}
       _MaxGraceLogins      :byte;
       _minPWlen            :byte;
       _unknown1            :byte; {! = hi-byte of maxConcConn }
       _MaxConcConn         :byte;
       _loginTimes          :array[1..42] of byte;
       _LastLoginTime       :array[1..6] of byte; {yy mm dd hh mm ss}
       _PWcontrol           :byte;
       _unknown2            :byte; { not used }
       _MaxDiskSpace        :Longint; { hi-lo }
       _unknown3            :Byte; {! = hi-byte of bad login count }
       _badLoginCount       :byte;
       _AccountResetTime    :LongInt; { minutes since 1/1/1985 }
       _lastIntruderAddress :TinterNetworkAddress;
       end        ABSOLUTE LCpropVal;
    moreSegments:boolean;
    propFlags:byte;

    Procedure Min2NovTime(m:Longint; Var time:TnovTime);
    Const darr:array[1..12] of word=(0,31,59,90,120,151,181,212,243,273,304,334);
    Var d,dr:word;
        i,Lastleap:byte;
    begin
    d:=(m div 1440);
    i:=0;
    lastLeap:=84;
    while d>((3+(i*4))*365)+31+28
     do begin
        dec(d);
        lastLeap:=85+3+(i*4);
        inc(i);
        end;
    WITH time
     do begin
        year:=(d DIV 365)+85;
        dr:=(d MOD 365);
        month:=1;
        while (month<12) and (dr>darr[month+1]) do inc(month);
        day:=(dr-darr[month]);
        if (day=28) and (month=2) and (lastLeap=year)
         then inc(day);
        dr:=(m mod 1440);
        hour:=(dr div 60);
        min:=(dr mod 60);
        sec:=0;
        end;
    end;
begin
IF nwBindry.ReadPropertyValue(ObjName,ObjType,'LOGIN_CONTROL',1,
                              LCpropval,moreSegments,propFlags)
 then begin
      FillChar(LoginControlInfo,SizeOf(LoginControlInfo),#0);
      With LoginControlInfo
       do begin
          AccountDisabled           :=lc._AccDisabled;
          move(lc._AccExpDate[1],AccountExpirationDate.year,3);
          move(lc._PWexpDate[1],PasswordExpirationDate.year,3);
          MinimumPasswordLength     :=lc._minPWlen;
          PasswordControlFlag       :=lc._PWcontrol;
          DaysBetweenPasswordChanges:=swap(lc._DaysBetwPWchanges);
          Move(lc._lastLoginTime[1],LastLoginTime.year,6);
          GraceLoginsRemaining      :=lc._GraceLoginsRemaining;
          MaxGraceLoginsAllowed     :=lc._maxGraceLogins;
          BadLoginCount             :=lc._badLoginCount;
          Min2NovTime(Lswap(lc._AccountResetTime),AccountResetTime);
          LastIntruderAddress       :=lc._LastIntruderAddress;
          LastIntruderAddress.socket:=swap(LastIntruderAddress.socket); {force lo-hi}
          MaxConcurrentConnections  :=lc._MaxConcConn;
          Move(lc._LoginTimes[1],LoginTimes[1],42);

          DiskSpace                 :=Lswap(lc._MaxDiskSpace);
          end;
      result:=$00;
      end
 else result:=nwBindry.result;
GetObjectLoginControl:=(result=0);
end;

Function ObjectCanLoginAt(ObjName:String; ObjType:Word;
                          LoginTime:TnovTime            ):Boolean;
{ Caller must have access to the bindery property LOGIN_CONTROL.
  Default: you need to be supervisor-equivalent or the object the property
           is associated with. (reading your 'own' information)

  -If one or more of the fields hour,min,sec,dayOfWeek contain a value >0,
   the supplied time will be checked against the login timerestrictions.
   (this means that checking '00:00 on sundays' is impossible)
  -If one or more of the fields year,month,day contain a value >0 , the
   date will be checked with the expiration date of the account and
   with the Account disabled Flag. }
Var CanLog:Boolean;
    Info:Tlogincontrol;
    half_hrs:word;
begin
IF GetObjectLoginControl(ObjName,ObjType,Info)
 then begin
      if (logintime.month>0) and (loginTime.day>0)
       then CanLog:=((NOT Info.AccountDisabled) and
                      IsLaterNovTime(Info.AccountExpirationDate,loginTime))
       else CanLog:=true;
      if (logintime.hour>0) or (loginTime.min>0)
         or (logintime.sec>0) or (logintime.DayOfWeek>0)
        then begin
             half_hrs:=(loginTime.DayOfWeek * 48)+(LoginTime.hour *2);
             if LoginTime.min>=30
              then inc(half_hrs);
             If half_hrs>=336
              then result:=$122
              else CanLog:=CanLog AND
                           ((Info.LoginTimes[(half_hrs DIV 8)+1]
                            AND (1 SHL (half_hrs MOD 8)) ) >0)
             end;
      end
 else begin
      CanLog:=(result=$FB); {no such property}
      result:=0;
      end;
ObjectCanLoginAt:=(result=0) and CanLog;
end;

Function GetObjectNodeControl( ObjName:string; ObjType:word;
                              {i/o} Var seqNbr:integer;
                              {out} Var NodeControlInfo:TnodeControl):boolean;
Var NCpropVal:Tproperty;
    moreSegments:boolean;
    propFlags:byte;
begin
if seqNbr=$FBFB
 then result:=$EC
 else begin
      if seqNbr<1 then seqNbr:=1;
      IF nwBindry.ReadPropertyValue(ObjName,ObjType,'NODE_CONTROL',seqNbr,
                                    NCpropval,moreSegments,propFlags)
       then begin
            Move(NCpropVal,NodeControlInfo,120);
            if moreSegments
             then inc(seqNbr)
             else seqNbr:=Integer($FBFB);
            end
       else result:=nwBindry.result;
      end;
GetObjectNodeControl:=(result=0);
{ $EC No more records (no such segment);
  $FB No restrictions found (No such property) }
end;


end. { end of unit nwConn }

