{$A+}

unit KOLRas;

interface

uses
  Windows, KOL, RAS;

type

  PRASObj =^TRASObj;
  TKOLRAS = PRASObj;
  TOnErrorEvent = procedure (Sender: PRASObj; Error: Integer) of object;
  TOnConnectingEvent = procedure (Sender: PRASObj; Msg: Integer; State: Integer; Error: Longint) of object;

  TRASObj = object(TObj)
  private
    FOnConnecting: TOnConnectingEvent;  // event for asynchronous dialing
    FOnError:      TOnErrorEvent;       // error event
    FRASHandle:    THRasConn;           // connection handle
    FRASName:      string;              // name of the RAS service
    fState:        TRASConnState;
    fError:        longint;
    fTimer:        PTimer;
    connecting:    boolean;
    function  GetConnected: Boolean;
    function  GetParams(Server: string; var DialParams: TRasDialParams): Boolean;
    function  GetPassword: string;
    procedure GetRASHandle;
    function  GetUsername: string;
    procedure SetRASName( Value: string );
    function  GetStatusString: string;
    function  GetErrorString: string;
    procedure OnTimer(Sender: PObj);
  public
    destructor  Destroy; virtual;                       // and destroy it
    procedure   Connect;                                // make a connection
    procedure   DisConnect(force: boolean);             // close the connection
    property    Connected: Boolean read GetConnected;   // is service connected?
    property    Status: TRASConnState read fState;      // current RAS state
    property    Error: longint read fError;             // last RAS error
    property    RASHandle: THRASConn read fRASHandle;
    property    StatusString: string read GetStatusString;
    property    ErrorString: string read GetErrorString;
    property    Password: string read GetPassword;      // get the password
    property    RASName: string read FRASName write SetRASName; // name of RAS service
    property    Username: string read GetUsername;      // username
    property    OnConnecting: TOnConnectingEvent read FOnConnecting write FOnConnecting; // asynch dialing event
    property    OnError: TOnErrorEvent read FOnError write FOnError;                     // error event
  end;

function  GetStatString(s: longint): string;
function  GetErrString(e: longint): string;
function  NewRASObj: PRASObj;
function  GetRASConnected(Handles: PList): PStrList;  // get all existing connections
function  GetRASNames: PStrList;                      // get all possible connections
function  IsRASConnected( const r: string ): Boolean; // test if a connection is available
procedure HangUp( const RASName: string );

implementation

var RASSave: PRASObj;
    CBkSave: TOnConnectingEvent;

procedure RASCallback(Msg: Integer; State: TRasConnState; Error: Longint); stdcall;
begin
  if assigned(RASSave) then begin
     RASSAve.fState := State;
     RASSave.fError := Error;
     if Assigned(CBkSave) then begin
        CBkSave( RASSave, Msg, State, Error );
     end;
     if (Assigned(RASSave.FOnError)) and (Error<>0) then begin
       RASSave.FOnError( RASSave, Error );
     end;
     if State = $2000 then begin
        RASSave.fTimer.Enabled := True;
        RASSave.connecting := false;
     end;
  end;
end;

function NewRASObj;
begin
  New(Result, create);                       // create the component first
  Result.FRASHandle := 0;                    // internal RAS handle
  Result.FRASName := '';                     // no default RAS name
  Result.fTimer := NewTimer(1000);           // watchdog timer
  Result.fTimer.Enabled := True;
  Result.fTimer.Enabled := False;
  Result.fTimer.OnTimer := Result.OnTimer;
  RASSave := Nil;
  CBkSave := Nil;
end;

destructor TRASObj.Destroy;
begin
  DisConnect(True);
  RASSave := Nil;
  CBkSave := Nil;
  fTimer.Free;
  inherited Destroy;    // next destroy the object
end;

procedure TRASObj.Connect;
var DialParams: TRasDialParams;  // local dial parameters
begin
  if not Connected then begin                       // only if the service is not connected
    if GetParams( FRASName, DialParams ) then begin // get actual dial parameters
      connecting := true;    
      RASSave := @self;                              // save the object itself
      CbkSave := FOnConnecting;
      RasDial(nil, nil, DialParams, 0, @RASCallback, FRASHandle ); // call with a callback function
    end;
  end;
end;

procedure TRASObj.DisConnect;
var s: TRasConnStatus;
begin
  if Connected or force then begin     // only if a connection is available
    if FRASHandle<>0 then begin        // only if a vaild handle is available
      RasHangup( FRASHandle );         // hangup the RAS service
      s.dwSize := sizeof(s);
      repeat
         sleep(0);
      until RasGetConnectStatus( FRASHandle, s ) = ERROR_INVALID_HANDLE;
      FRASHandle := 0;
    end;
  end;
end;

function TRASObj.GetConnected: Boolean;
begin
  Result := IsRASConnected( FRASName );      // test if a service with this name is established
  if (Result) and (FRASHandle=0) then begin  // if no handle is available
    GetRASHandle;                            // try to read the handle
  end;
end;

function TRASObj.GetParams(Server: string; var DialParams: TRasDialParams): Boolean;
var DialPassword: LongBool;
    RASResult:    LongInt;
begin
  Result := true;                                     // result is first vaild
  FillChar( DialParams, SizeOf(TRasDialParams), 0);   // clear the result record
  DialParams.dwSize := Sizeof(TRasDialParams);        // set the result array size
  StrPCopy(DialParams.szEntryName, Server);           // set the ras service name
  DialPassword := true;                               // get the dial password
  RASResult := RasGetEntryDialParams(nil, DialParams, DialPassword); // read the ras parameters
  if (RASResult<>0) then begin                        // if the API call was not successful
    Result := false;                                  // result is not vaild
    if (Assigned(FOnError)) then begin                // if an error event is assigned
      FOnError( @self, RASResult );                    // call the error event
    end;
  end;
end;

function TRASObj.GetPassword: string;
var DialParams: TRasDialParams;                   // dial parameters for this service
begin
  if GetParams( FRASName, DialParams ) then begin // if read of dial parameters was successful
    Result := DialParams.szPassword;              // copy the password string
  end else begin                                  // if read was not successful
    Result := '';                                 // return an empty string
  end;
end;

procedure TRASObj.GetRASHandle;
const cMaxRas = 100;                           // maximum number of ras services
var BufferSize: LongInt;                       // used for size of result buffer
    RASBuffer:  array[1..cMaxRas] of TRasConn; // the API result buffer itself
    RASCount:   LongInt;                       // number of found ras services
    i:          Integer;                       // loop counter
begin
  FRASHandle := 0;                             // first no handle is available
  FillChar( RASBuffer, SizeOf(RASBuffer), 0 ); // clear the API Buffer
  RASBuffer[1].dwSize := SizeOf(TRasConn);     // set the API buffer size for a single record
  BufferSize := SizeOf(TRasConn) * cMaxRas;    // calc complete buffer size
  if RasEnumConnections(@RASBuffer[1], BufferSize, RASCount) = 0 then begin
    for i := 1 to RASCount do begin            // for all found ras services
      if RASBuffer[i].szEntryName = RASName then begin // if the actual name is available
        FRASHandle := RASBuffer[i].hrasconn;   // save the found ras handle
      end;
    end;
  end;
end;

function TRASObj.GetUsername: string;
var DialParams: TRasDialParams;                   // dial parameters for this service
begin
  if GetParams( FRASName, DialParams ) then begin // if read of dial parameters was successful
    Result := DialParams.szUserName;              // copy the user name string
  end else begin                                  // if read was not successful
    Result := '';                                 // return an empty string
  end;
end;

function TRASObj.GetStatusString;
begin
   result := GetStatString(fState);
end;

function GetStatString;
begin
   result := 'unexpected status: ' + int2str(s);
   case s of
    0: result := '';
    1: result := 'port is opened';
    2: result := 'call in progress';
    3: result := 'device is connected';
    4: result := 'all devices is connected';
    5: result := 'authentication';
    6: result := 'authnotify';
    7: result := 'authretry';
    8: result := 'authcallback';
    9: result := 'authchangepassword';
   10: result := 'authproject';
   11: result := 'linkspeed';
   12: result := 'authack';
   13: result := 'reauthenticate';
   14: result := 'authenticated';
   15: result := 'prepareforcallback';
   16: result := 'waitformodemreset';
   17: result := 'waitforcallback';
   18: result := 'projected';
   19: result := 'startauthentication';
   20: result := 'callbackcomplete';
   21: result := 'logonnetwork';
$1000: result := 'interactive';
$1001: result := 'retryauthentication';
$1002: result := 'callbacksetbycaller';
$1003: result := 'password is expired';
$2000: result := 'connected';
$2001: result := 'disconnected';
   end;
end;

function TRASObj.GetErrorString;
begin
   result := GetErrString(fError);
end;

function GetErrString(e: longint): string;
begin
   result := 'unexpected error: ' + int2str(e);
   case e of
   000: result := '';
   600: result := 'operation is pending';
   601: result := 'invalid port handle';
   608: result := 'device does not exist';
   615: result := 'port not found';
   619: result := 'connection is terminated';
   628: result := 'port was disconnected';
   629: result := 'disconnected by remote';
   630: result := 'hardware failure';
   631: result := 'user disconnect';
   633: result := 'port is in use';
   638: result := 'PPP no address assigned';
   651: result := 'device error';
   676: result := 'line is busy';
   678: result := 'no answer';
   680: result := 'no dialtone';
   691: result := 'authentication failure';
   718: result := 'PPP timeout';
   720: result := 'PPP no CP configured';
   721: result := 'PPP no responce';
   732: result := 'PPP is not converging';
   734: result := 'PPP LCP terminated';
   735: result := 'PPP adress rejected';
   738: result := 'no PPP address assigned';
   742: result := 'no remote encription';
   743: result := 'remote requires encription';
   752: result := 'script syntax error';
   777: result := 'no answer timeout'; 
   797: result := 'modem is not found';
   end;
end;

procedure TRASObj.SetRASName( Value: string );
var DialParams: TRasDialParams;                   // dial parameters for this service
begin
  if GetParams( Value, DialParams ) then begin
    FRASName := Value;
    GetRASHandle;        // try to read an existing handle
  end;
end;

function GetRASConnected;
const cMaxRas = 100;                           // maximum number of ras services
var BufferSize: LongInt;                       // used for size of result buffer
    RASBuffer:  array[1..cMaxRas] of TRasConn; // the API result buffer itself
    RASCount:   LongInt;                       // number of found ras services
    i:          Integer;                       // loop counter
begin
  FillChar( RASBuffer, SizeOf(RASBuffer), 0 ); // clear the API Buffer
  RASBuffer[1].dwSize := SizeOf(TRasConn);     // set the API buffer size for a single record
  BufferSize := SizeOf(TRasConn) * cMaxRas;    // calc complete buffer size
  Result := NewStrList;
  if RasEnumConnections(@RASBuffer[1], BufferSize, RASCount) = 0 then begin
    for i := 1 to RASCount do begin            // for all found ras services
      Result.Add( RASBuffer[i].szEntryName );   // copy the name of the ras service
      if Handles <> nil then Handles.Add(pointer(RASBuffer[i].hrasconn));
    end;
  end;
  if assigned(RASSave) then begin
     if RASSAve.FRASHandle <> 0 then begin
        if RASSave.connecting then begin
           i := Result.IndexOf(RASSave.FRASName);
           if i = -1 then begin
              i := Result.Add(RASSave.FRASName);
              if Handles <> nil then Handles.Add(pointer(RASSave.FRASHandle));
           end;
           if Handles <> nil then Handles.Items[i] := pointer(RASSave.FRASHandle);
        end;
     end;
  end;
end;

function GetRASNames;
const cMaxRas = 100;                            // maximum number of ras services
var BufferSize: LongInt;                        // used for size of result buffer
    RASBuffer:  array[1..cMaxRas] of TRasEntryName; // the API result buffer itself
    RASCount:   LongInt;                        // number of found ras services
    i:          Integer;                        // loop counter
begin
  Result := Nil;
  FillChar( RASBuffer, SizeOf(RASBuffer), 0 );  // clear the API Buffer
  RASBuffer[1].dwSize := SizeOf(TRasEntryname); // set the API buffer size for a single record
  BufferSize := SizeOf(TRasEntryName) * cMaxRas;// calc complete buffer size
  if RasEnumEntries(nil, nil, @RASBuffer[1], BufferSize, RASCount) = 0 then begin
    Result := NewStrList;
    for i := 1 to RASCount do begin             // for all found ras services
      Result.Add( RASBuffer[i].szEntryName );    // copy the name of the ras service
    end;
  end;
end;

function IsRASConnected( const r: string ): Boolean;
var n: PStrList;       // result object for connected services
    i: Integer;        // loop counter
    p: PList;
begin
  Result := false;                   // first the result is false
  p := NewList;
  n := GetRasConnected(p);           // create the object for connected services
  for i := 0 to n.Count - 1 do begin // for all connected services
    if r = n.Items[i] then begin     // if the ras name was found
       Result := true;               // the result is true now
       Break;                        // break the loop, one is found
    end;
  end;
  n.Free;                            // destroy the object for connected services
  p.Free;
end;

procedure HangUP;
var e: PStrList;
    h: PList;
    i: integer;
begin
    h := NewList;
    e := GetRASConnected(h);
    i := e.IndexOf(RASName);
    if i > -1 then begin
       RASHangUp(integer(h.Items[i]));
    end;
    e.Free;
    h.Free;
end;

procedure TRASObj.OnTimer;
begin
   if not connected then begin
      fTimer.Enabled := False;
      Disconnect(True);
      if assigned(fOnConnecting) then begin
         fState := $2001;
         fError :=  619;
         fOnConnecting(@self, 0, $2001, 619);
      end;
   end;
end;

end.
