home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
DP Tool Club 21
/
CD_ASCQ_21_040595.iso
/
dos
/
prg
/
pas
/
nwtp06
/
nwconn.pas
< prev
next >
Wrap
Pascal/Delphi Source File
|
1995-03-01
|
49KB
|
1,456 lines
{$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 }