(c) BigSpeed Computing Inc. - Secure private networking

Delphi 7 example of secure chat and alert messaging application



unit uMain;


interface

uses
  ActiveX, Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, ComCtrls, ToolWin, Menus, ActnList, ImgList, StrUtils, Contnrs,
  Shdocvw_tlb, SHDocVwEvents, MSHTML_tlb, ExtCtrls, Registry,
  Variants, ShellAPI, Buttons, IniFiles, MMSystem,
  ForAll, uAbout, uConnect, uSettings,
  bsSocketLibrary_TLB;



const
  pcAlert = 10;
  pcChat = 20;
  pcHAVE_FILE_HEAD = 30;
  pcGIVE_FILE_BODY = 31;
  pcHAVE_FILE_BODY = 32;
  pcSTOP_FILE_BODY = 33;


type

  TPeer = class
    Socket: Longword;
    ListItem: TListItem;
    Address, UpFileName, DnFileName: String;
    Port, UpFileHandle, UpFileSize, UpFileCount: Integer;
    DnFileHandle, DnFileSize, DnFileCount: Integer;
    UpFileItem, DnFileItem: TListItem;
  end;


  TfMain = class(TForm)
    NorImages: TImageList;
    StatusBar: TStatusBar;
    dlgOpen: TOpenDialog;
    GroupBox3: TGroupBox;
    mmChat: TMemo;
    edChat: TEdit;
    btnChat: TButton;
    GroupBox5: TGroupBox;
    Label1: TLabel;
    lvTransfers: TListView;
    btnSend: TButton;
    btnBrowse: TButton;
    edFile: TEdit;
    GroupBox2: TGroupBox;
    lvPeers: TListView;
    btnSettings: TButton;
    btnMyIP: TButton;
    btnAbout: TButton;
    btnExit: TButton;
    btnRemove: TButton;
    Timer1: TTimer;
    btnAbort: TButton;
    GroupBox1: TGroupBox;
    edAlert: TEdit;
    btnAlert: TButton;
    btnConnect: TButton;
    btnCancel: TButton;

    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);

    procedure SetProperties;

    procedure LoadForm;
    procedure SaveForm;

    procedure btnExitClick(Sender: TObject);
    procedure btnAboutClick(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure btnSettingsClick(Sender: TObject);
    procedure btnConnectClick(Sender: TObject);
    procedure btnMyIPClick(Sender: TObject);
    procedure lvPeersChange(Sender: TObject; Item: TListItem; Change: TItemChange);
    procedure btnCancelClick(Sender: TObject);
    procedure btnRemoveClick(Sender: TObject);
    procedure btnChatClick(Sender: TObject);
    procedure btnBrowseClick(Sender: TObject);
    procedure btnSendClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure lvTransfersChange(Sender: TObject; Item: TListItem;
      Change: TItemChange);
    procedure btnAbortClick(Sender: TObject);
    procedure btnAlertClick(Sender: TObject);

  private
    { Private declarations }

    CallHan: Integer;
    ThePeer: TPeer;
    Peers: TObjectList;

    function SelectPeer: Boolean;
    procedure UpdateStatus;
    procedure UpdateAbortButton;
    procedure AddPeer(aPeer: TPeer);
    procedure RemovePeer(aPeer: TPeer);
    procedure FinishUpload(aPeer: TPeer);
    procedure FinishDownload(aPeer: TPeer);

    procedure SessionInvoked(Sender: TObject; aHandle: Integer);
    procedure SessionCreated(Sender: TObject; aHandle: Integer);
    procedure SessionRejected(Sender: TObject; aHandle: Integer);
    procedure SessionClosed(Sender: TObject; aHandle: Integer);
    procedure PacketReceived(Sender: TObject; aHandle: Integer);
    procedure PacketSent(Sender: TObject; aHandle: Integer);

    procedure RecvAlert;
    procedure RecvChat;
    procedure RecvHaveFileHead;
    procedure RecvGiveFileBody;
    procedure RecvHaveFileBody;
    procedure RecvStopFileBody;

  public
    { Public declarations }
    SockLib: TBSSockLibX;
  end;

var
  fMain: TfMain;


//function InitHook: Boolean; stdcall;
//function DoneHook: Boolean; stdcall;


implementation

{$R *.DFM}






procedure TfMain.FormCreate(Sender: TObject);
begin
  Peers := TObjectList.Create;

  LoadForm;

  try
    SockLib := TBSSockLibX.Create(Self);
    SockLib.Parent := Self;
    SockLib.OnSessionCreated := SessionCreated;
    SockLib.OnSessionInvoked := SessionInvoked;
    SockLib.OnSessionRejected := SessionRejected;
    SockLib.OnSessionClosed := SessionClosed;
    SockLib.OnPacketReceived := PacketReceived;
    SockLib.OnPacketSent := PacketSent;
  except
    MessageDlg('BigSpeed Socket Library control is not registered on your system!', mtError, [mbOk], 0);
    Application.Terminate;
  end;
end;




procedure TfMain.FormDestroy(Sender: TObject);
begin
  SockLib.Free;
  Peers.Free;
  SaveForm;
end;




procedure TfMain.LoadForm;
var
  Ini: TIniFile;
begin
  try
    Ini := TIniFile.Create(ExtractFilePath(Application.ExeName)+'settings.ini');
  except
    Ini := nil;
  end;
  if Ini = nil then Exit;

  try
    with Ini do
    begin
      Left := ReadInteger('Last', 'Left', Left);
      Top := ReadInteger('Last', 'Top', Top);
    end;
  finally
    Ini.Free;
  end;
end;





procedure TfMain.SaveForm;
var
  Ini: TIniFile;
begin
  try
    Ini := TIniFile.Create(ExtractFilePath(Application.ExeName)+'settings.ini');
  except
    Ini := nil;
  end;
  if Ini = nil then Exit;

  try
    with Ini do
    begin
      WriteInteger('Last', 'Left', Left);
      WriteInteger('Last', 'Top', Top);
    end;
  finally
    Ini.Free;
  end;
end;






procedure TfMain.SetProperties;
begin

  SockLib.SecurityMode := fSettings.SecurityMode;
  SockLib.PublicKey := fSettings.mmPublic.Text;
  SockLib.PrivateKey := fSettings.mmPrivate.Text;
  SockLib.SecretKey := fSettings.edSecret.Text;
  SockLib.Fingerprints := fSettings.lstFingerprints.Items.CommaText;

  if fSettings.cbxServer.Checked then
  begin
    if not SockLib.StartListening(StrToIntDef(fSettings.edPort.Text, 0)) then
      MessageBox(0, 'Cannot start listening', 'Error!', MB_OK or MB_ICONERROR);
  end
  else
  begin
    SockLib.StopListening;
  end;

  SockLib.ServerAddress := fSettings.edSrvAddr.Text;
  SockLib.ClientAddress := fSettings.edClnAddr.Text;
  SockLib.UseCompression := fSettings.cbxCompression.Checked;
  if fSettings.radSocks5.Checked then
    SockLib.SocksProtocol := '5'
  else
    SockLib.SocksProtocol := '4';

  SockLib.SocksServer         := Trim(fSettings.edSocksAddr.Text);
  SockLib.SocksPort           := StrToIntDef(fSettings.edSocksPort.Text, 0);
  SockLib.SocksUsername       := Trim(fSettings.edSocksUser.Text);
  SockLib.SocksPassword       := Trim(fSettings.edSocksPass.Text);

  UpdateStatus;
end;






function TfMain.SelectPeer: Boolean;
var
  i: Integer;
  Pr: TPeer;
begin
  Result := False;

  if lvPeers.Selected = nil then
  begin
    for i := 0 to lvPeers.Items.Count-1 do
    begin
      Pr := TPeer(lvPeers.Items[i].Data);
      if SockLib.SessionOpen(Pr.Socket) then
      begin
        lvPeers.Items[i].Selected := True;
        Break;
      end;
    end;

    if lvPeers.Selected = nil then
    begin
      ShowMessage('There is no connected peer!');
      Exit;
    end;
  end;

  Pr := TPeer(lvPeers.Selected.Data);
  if not SockLib.SessionOpen(Pr.Socket) then
  begin
    ShowMessage('The selected peer is not connected!');
    Exit;
  end;

  Result := True;
end;



procedure TfMain.UpdateStatus;
begin
  if CallHan > 0 Then
  begin
    //a call is in progress
    btnConnect.Enabled := False;
    btnCancel.Enabled := True;
  end
  else
  begin
    //a new call can be initiated
    btnConnect.Enabled := True;
    btnCancel.Enabled := False;
  end;

  if SockLib.SecurityMode > 0 then
    StatusBar.Panels[0].Text := ' Encryption: ON'
  else
    StatusBar.Panels[0].Text := ' Encryption: OFF';

  if SockLib.IsListening then
    StatusBar.Panels[1].Text := ' Server: ON'
  else
    StatusBar.Panels[1].Text := ' Server: OFF';

  if lvPeers.Items.Count = 0 then
    StatusBar.Panels[2].Text := ' No active connection'
  else
    StatusBar.Panels[2].Text := ' ' + IntToStr(lvPeers.Items.Count) + ' connection(s)';

  if CallHan <> 0 then
    StatusBar.Panels[2].Text := ' Connecting to ' + fConnect.edHost.Text + ':' + fConnect.edPort.Text;
end;



procedure TfMain.UpdateAbortButton;
begin
  btnAbort.Enabled := False;
  if lvTransfers.Selected = nil then Exit;
  if lvTransfers.Selected.Data = nil then Exit;  //non-active
  btnAbort.Enabled := True;
end;



procedure TfMain.AddPeer(aPeer: TPeer);
begin
  Peers.Add(aPeer);
  aPeer.Address := SockLib.GetRemoteAddress(aPeer.Socket);
  aPeer.Port := SockLib.GetRemotePort(aPeer.Socket);

  with lvPeers.Items.Add do
  begin
    Data := Pointer(aPeer);
    Caption := aPeer.Address;
    Subitems.Add(IntToStr(aPeer.Port));
    Subitems.Add(TimeToStr(now));
  end;

  aPeer.ListItem := lvPeers.Items[lvPeers.Items.Count-1];
  UpdateStatus;
end;


procedure TfMain.RemovePeer(aPeer: TPeer);
begin
  aPeer.Socket := 0;
  lvPeers.Items.Delete(aPeer.ListItem.Index);
  Peers.Remove(aPeer);
  UpdateStatus;
end;



procedure TfMain.FinishUpload(aPeer: TPeer);
begin
  if aPeer.UpFileHandle > 0 then
  begin
    FileClose(aPeer.UpFileHandle);
    aPeer.UpFileHandle := 0;
  end;
  aPeer.UpFileItem.Data := nil;
  aPeer.UpFileItem := nil;
  UpdateAbortButton;
end;


procedure TfMain.FinishDownload(aPeer: TPeer);
begin
  if aPeer.DnFileHandle > 0 then
  begin
    FileClose(aPeer.DnFileHandle);
    aPeer.DnFileHandle := 0;
  end;
  aPeer.DnFileItem.Data := nil;
  aPeer.DnFileItem := nil;
end;



//************  User Events


procedure TfMain.btnExitClick(Sender: TObject);
begin
  Application.Terminate;
end;


procedure TfMain.btnAboutClick(Sender: TObject);
begin
  fAbout.ShowModal;
end;




procedure TfMain.FormShow(Sender: TObject);
begin
  SetProperties;
end;



procedure TfMain.btnSettingsClick(Sender: TObject);
begin
  if fSettings.ShowModal <> mrOk then Exit;
  SetProperties;
end;








//************** Socket event handlers


//successful call
procedure TfMain.SessionCreated(Sender: TObject; aHandle: Integer);
var
  Pr: TPeer;
begin
  CallHan := 0;
  Pr := TPeer.Create;
  Pr.Socket := aHandle;
  SockLib.SetMoniker(aHandle, Longword(Pr));
  AddPeer(Pr);
end;



//unsuccessful call
procedure TfMain.SessionRejected(Sender: TObject; aHandle: Integer);
begin
  CallHan := 0;
  UpdateStatus;
  ShowMessage('Cannot connect to ' + fConnect.edHost.Text + ':' + fConnect.edPort.Text);
end;


//incoming connection
procedure TfMain.SessionInvoked(Sender: TObject; aHandle: Integer);
var
  Pr: TPeer;
begin
  Pr := TPeer.Create;
  Pr.Socket := aHandle;
  SockLib.SetMoniker(aHandle, Longword(Pr));
  AddPeer(Pr);
end;


procedure TfMain.SessionClosed(Sender: TObject; aHandle: Integer);
var
  Pr: TPeer;
begin
  Pr := TPeer(SockLib.GetMoniker(aHandle));
  RemovePeer(Pr);
end;


procedure TfMain.PacketReceived(Sender: TObject; aHandle: Integer);
begin
  ThePeer := TPeer(SockLib.GetMoniker(aHandle));
  case SockLib.GetRcvdCmnd(aHandle) of
    pcAlert: RecvAlert;
    pcChat: RecvChat;
    pcHAVE_FILE_HEAD: RecvHaveFileHead;
    pcGIVE_FILE_BODY: RecvGiveFileBody;
    pcHAVE_FILE_BODY: RecvHaveFileBody;
    pcSTOP_FILE_BODY: RecvStopFileBody;
  end;
end;




procedure TfMain.RecvAlert;
var
  S: String;
begin
  S := 'From ' + SockLib.GetRemoteAddress(ThePeer.Socket) + ' at ' + TimeToStr(Now) + #13#13;
  S := S + SockLib.TextFromBuffer(ThePeer.Socket);
  MessageBox(0, PChar(S), 'Alert!', MB_OK or MB_ICONEXCLAMATION or MB_SYSTEMMODAL);
end;



procedure TfMain.RecvChat;
var
  S: String;
begin
  S := '['+TimeToStr(Now) + '] ';
  S := S + SockLib.GetRemoteAddress(ThePeer.Socket) + ':';
  S := S + SockLib.TextFromBuffer(ThePeer.Socket);
  mmChat.Lines.Add(S);
end;



procedure TfMain.RecvHaveFileHead;
begin
  if ThePeer.DnFileHandle > 0 then
  begin
    FileClose(ThePeer.DnFileHandle);
    ThePeer.DnFileItem.SubItems[2] := 'Canceled';
  end;

  ThePeer.DnFileCount := 0;
  ThePeer.DnFileName := SockLib.TextFromBuffer(ThePeer.Socket);
  ThePeer.DnFileSize := SockLib.IntegerFromBuffer(ThePeer.Socket);

  ThePeer.DnFileHandle := FileCreate(ExtractFilePath(Application.ExeName) + ThePeer.DnFileName);
  if ThePeer.DnFileHandle <= 0 then
  begin
    ShowMessage('Cannot create ' + ThePeer.DnFileName);
    Exit;
  end;

  ThePeer.DnFileItem := lvTransfers.Items.Add;
  with ThePeer.DnFileItem do
  begin
    Data := ThePeer;
    Caption := ThePeer.DnFileName;
    SubItems.Add('download');
    SubItems.Add(ThePeer.ListItem.Caption);
    SubItems.Add('handshaking');
  end;

  SockLib.SendPacket(ThePeer.Socket, pcGIVE_FILE_BODY);
end;



procedure TfMain.RecvGiveFileBody;
var
  Pt: OleVariant;
  Len: Integer;
  P: PByte;

begin
  if ThePeer.UpFileItem = nil then Exit;  //aborted

  if ThePeer.UpFileCount >= ThePeer.UpFileSize then
  begin
    //the file is done
    ThePeer.UpFileItem.SubItems[2] := 'Done.';
    FinishUpload(ThePeer);
  end
  else
  begin
    while ThePeer.UpFileCount < ThePeer.UpFileSize  do
    begin
      Len := ThePeer.UpFileSize - ThePeer.UpFileCount;
      if Len > SockLib.GetFreeRoom(ThePeer.Socket) then
        Len := SockLib.GetFreeRoom(ThePeer.Socket);
      if Len <= 0 then Break;

      Pt := VarArrayCreate([0, Len-1], varByte);
      P := VarArrayLock(Pt);
      if P = nil then Exit;
      if FileRead(ThePeer.UpFileHandle, P^, Len) <> Len then
      begin
        ThePeer.UpFileItem.SubItems[2] := 'Error reading';
        FinishUpload(ThePeer);
        Exit;
      end;
      VarArrayUnlock(Pt);
      SockLib.BytesToBuffer(ThePeer.Socket, Pt);
      Inc(ThePeer.UpFileCount, Len);
    end;
    SockLib.SendPacket(ThePeer.Socket, pcHAVE_FILE_BODY);
  end;
end;



procedure TfMain.RecvHaveFileBody;
var
  Pt: OleVariant;
  Len: Integer;
  P: PByte;
begin
  if ThePeer.DnFileHandle < 0 then Exit;
  Pt := SockLib.BytesFromBuffer(ThePeer.Socket);
  if not VarIsArray(Pt) then Exit;
  Len := VarArrayHighBound(Pt, 1) - VarArrayLowBound(Pt, 1) + 1;
  P := VarArrayLock(Pt);
  if P = nil then Exit;
  if FileWrite(ThePeer.DnFileHandle, P^, Len) <> Len then
  begin
    ThePeer.DnFileItem.SubItems[2] := 'Error writing';
    FinishDownload(ThePeer);
    Exit;
  end;
  VarArrayUnlock(Pt);
  Inc(ThePeer.DnFileCount, Len);
  if ThePeer.DnFileCount >= ThePeer.DnFileSize then
  begin
    //the file is done
    ThePeer.DnFileItem.SubItems[2] := 'Done.';
    FinishDownload(ThePeer);
  end;
end;



procedure TfMain.RecvStopFileBody;
begin
  if ThePeer.DnFileItem = nil then Exit;
  ThePeer.DnFileItem.SubItems[2] := 'Aborted';
  FinishDownload(ThePeer);
end;



procedure TfMain.PacketSent(Sender: TObject; aHandle: Integer);
begin
  if SockLib.LastError > 0 then Exit;
  ThePeer := TPeer(SockLib.GetMoniker(aHandle));
  if SockLib.GetSentCmnd(aHandle) = pcHAVE_FILE_BODY then
    RecvGiveFileBody;
end;



procedure TfMain.btnConnectClick(Sender: TObject);
begin
  if fConnect.ShowModal <> mrOk then Exit;
  CallHan := SockLib.CreateSession(fConnect.edHost.Text, StrToIntDef(fConnect.edPort.Text, 0));

  if CallHan = 0  then
    MessageDlg('Cannot create socket: ' + IntToStr(SockLib.LastError), mtError, [mbOk], 0)
  else
  begin
    btnConnect.Enabled := False;
    btnCancel.Enabled := True;
    UpdateStatus;
  end;
end;


procedure TfMain.btnMyIPClick(Sender: TObject);
begin
  ShowMessage(SockLib.LocalIP);
end;


procedure TfMain.lvPeersChange(Sender: TObject; Item: TListItem; Change: TItemChange);
begin
  if Change <> ctState then Exit;
  btnRemove.Enabled := lvPeers.SelCount = 1;
  UpdateStatus;
end;

procedure TfMain.btnCancelClick(Sender: TObject);
begin
  SockLib.TerminateSession(CallHan);
  CallHan := 0;
  UpdateStatus;
end;


procedure TfMain.btnRemoveClick(Sender: TObject);
var
  Pr: TPeer;
begin
  if lvPeers.Selected = nil then Exit;
  Pr := TPeer(lvPeers.Selected.Data);
  SockLib.TerminateSession(Pr.Socket);
  RemovePeer(Pr);
end;


procedure TfMain.btnChatClick(Sender: TObject);
var
  Pr: TPeer;
  S: String;
begin
  if not SelectPeer then Exit;
  if lvPeers.Selected = nil then Exit;
  Pr := TPeer(lvPeers.Selected.Data);

  SockLib.TextToBuffer(Pr.Socket, edChat.Text);
  SockLib.SendPacket(Pr.Socket, pcChat);

  S := '['+TimeToStr(Now) + '] Me:';
  S := S + edChat.Text;
  mmChat.Lines.Add(S);
  edChat.Text := '';
end;



procedure TfMain.btnAlertClick(Sender: TObject);
var
  Pr: TPeer;
begin
  if not SelectPeer then Exit;
  if lvPeers.Selected = nil then Exit;
  Pr := TPeer(lvPeers.Selected.Data);

  SockLib.TextToBuffer(Pr.Socket, edAlert.Text);
  SockLib.SendPacket(Pr.Socket, pcAlert);
  edAlert.Text := '';
end;


procedure TfMain.btnBrowseClick(Sender: TObject);
begin
  if not dlgOpen.Execute then Exit;
  edFile.Text := dlgOpen.FileName;
end;


procedure TfMain.btnSendClick(Sender: TObject);
begin
  if not SelectPeer then Exit;

  if not FileExists(edFile.Text) then
  begin
    ShowMessage('File does not exist!');
    Exit;
  end;

  ThePeer := TPeer(lvPeers.Selected.Data);
  if ThePeer.UpFileHandle > 0 then
  begin
    FileClose(ThePeer.UpFileHandle);
    ThePeer.UpFileItem.SubItems[2] := 'Canceled';
  end;

  ThePeer.UpFileHandle := FileOpen(edFile.Text, fmOpenRead or fmShareExclusive);
  if ThePeer.UpFileHandle <= 0 then
  begin
    ShowMessage('Cannot open ' + edFile.Text);
    Exit;
  end;

  ThePeer.UpFileItem := lvTransfers.Items.Add;
  with ThePeer.UpFileItem do
  begin
    Data := ThePeer;
    Caption := edFile.Text;
    SubItems.Add('upload');
    SubItems.Add(ThePeer.ListItem.Caption);
    SubItems.Add('handshaking');
  end;

  ThePeer.UpFileCount := 0;
  ThePeer.UpFileSize := GetFileSize(ThePeer.UpFileHandle, nil);
  SockLib.TextToBuffer(ThePeer.Socket, ExtractFileName(edFile.Text));
  SockLib.IntegerToBuffer(ThePeer.Socket, ThePeer.UpFileSize);
  SockLib.SendPacket(ThePeer.Socket, pcHAVE_FILE_HEAD);
end;



procedure TfMain.btnAbortClick(Sender: TObject);
var
  Pr: TPeer;
begin
  if lvTransfers.Selected = nil then Exit;
  Pr := TPeer(lvTransfers.Selected.Data);
  if Pr.UpFileItem = nil then Exit;
  Pr.UpFileItem.SubItems[2] := 'Aborted';
  FinishUpload(Pr);
  SockLib.SendPacket(Pr.Socket, pcSTOP_FILE_BODY);
end;



procedure TfMain.Timer1Timer(Sender: TObject);
var
  i: Integer;
  Pr: TPeer;
begin
  for i := 0 to lvPeers.Items.Count-1 do
  begin
    Pr := TPeer(lvPeers.Items[i].Data);
    if Pr.UpFileItem <> nil then
      Pr.UpFileItem.SubItems[2] := IntToStr(Pr.UpFileCount) + '/' + IntToStr(Pr.UpFileSize);
    if Pr.DnFileItem <> nil then
      Pr.DnFileItem.SubItems[2] := IntToStr(Pr.DnFileCount) + '/' + IntToStr(Pr.DnFileSize);

//debug    Pr.ListItem.SubItems[1] := IntToStr(SockLib.GetRcvCnt(Pr.Socket)) + '/' + IntToStr(SockLib.GetSndCnt(Pr.Socket));
  end;
end;



procedure TfMain.lvTransfersChange(Sender: TObject; Item: TListItem; Change: TItemChange);
begin
  if Change <> ctState then Exit;
  UpdateAbortButton;
end;


end.
(c) BigSpeed Computing Inc. - Secure private networking