Skip to content

Latest commit

 

History

History
1177 lines (1005 loc) · 30 KB

Claude-IRC-Bot-Indy.md

File metadata and controls

1177 lines (1005 loc) · 30 KB

Prompt

Can you please give me a simple example of an IRC logging bot using Free Pascal with the IRC client from Indy?

Answer

program IRCLogBot;

uses
  SysUtils, Classes, IdIRC, IdGlobal, IdComponent;

type
  TIRCLogger = class
  private
    FIRC: TIdIRC;
    FLogFile: TextFile;
    procedure OnConnect(Sender: TObject);
    procedure OnReceive(Sender: TObject; AMsg: string);
    procedure OnPrivMessage(ASender: TIdContext; const ANicknameFrom, AHost, 
      ATarget, AMessage: string);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Connect;
  end;

constructor TIRCLogger.Create;
begin
  FIRC := TIdIRC.Create(nil);
  
  // Set up IRC connection parameters
  FIRC.Nick := 'LogBot';
  FIRC.Username := 'LogBot';
  FIRC.RealName := 'Simple IRC Logging Bot';
  FIRC.Host := 'irc.example.com';  // Replace with actual IRC server
  FIRC.Port := 6667;
  
  // Set up event handlers
  FIRC.OnConnected := OnConnect;
  FIRC.OnReceive := OnReceive;
  FIRC.OnPrivMessage := OnPrivMessage;
  
  // Initialize log file
  AssignFile(FLogFile, 'irc_log.txt');
  if FileExists('irc_log.txt') then
    Append(FLogFile)
  else
    Rewrite(FLogFile);
end;

destructor TIRCLogger.Destroy;
begin
  CloseFile(FLogFile);
  FIRC.Free;
  inherited;
end;

procedure TIRCLogger.Connect;
begin
  try
    FIRC.Connect;
  except
    on E: Exception do
      Writeln('Connection error: ', E.Message);
  end;
end;

procedure TIRCLogger.OnConnect(Sender: TObject);
begin
  FIRC.Join('#yourchannel');  // Replace with actual channel name
  Writeln('Connected to IRC server');
  Writeln(FLogFile, '[' + DateTimeToStr(Now) + '] Connected to IRC server');
  Flush(FLogFile);
end;

procedure TIRCLogger.OnReceive(Sender: TObject; AMsg: string);
begin
  // Log all received messages
  Writeln(FLogFile, '[' + DateTimeToStr(Now) + '] ' + AMsg);
  Flush(FLogFile);
end;

procedure TIRCLogger.OnPrivMessage(ASender: TIdContext; const ANicknameFrom, 
  AHost, ATarget, AMessage: string);
begin
  // Log private messages
  Writeln(FLogFile, Format('[%s] <%s> %s', [
    DateTimeToStr(Now),
    ANicknameFrom,
    AMessage
  ]));
  Flush(FLogFile);
end;

var
  Logger: TIRCLogger;
begin
  Logger := TIRCLogger.Create;
  try
    Logger.Connect;
    Writeln('Press Enter to quit...');
    Readln;
  finally
    Logger.Free;
  end;
end.

I've created a simple IRC logging bot that:

  1. Connects to an IRC server
  2. Joins a specified channel
  3. Logs all received messages to a text file
  4. Handles private messages separately
  5. Includes timestamps for all logged messages

To use this bot, you'll need to:

  1. Have the Indy components installed in your Free Pascal environment
  2. Replace 'irc.example.com' with your actual IRC server address
  3. Replace '#yourchannel' with the channel you want to monitor
  4. Optionally modify the bot's nickname and other parameters

The bot creates or appends to an 'irc_log.txt' file in the same directory as the executable. Would you like me to explain any part of the code in more detail or add any additional features?

Prompt

Could you please alter the example in order for the bot to be able to connect via TLS.

Answer

program IRCLogBot;

uses
  SysUtils, Classes, IdIRC, IdGlobal, IdComponent, IdSSLOpenSSL;

type
  TIRCLogger = class
  private
    FIRC: TIdIRC;
    FLogFile: TextFile;
    FSSL: TIdSSLIOHandlerSocketOpenSSL;
    procedure OnConnect(Sender: TObject);
    procedure OnReceive(Sender: TObject; AMsg: string);
    procedure OnPrivMessage(ASender: TIdContext; const ANicknameFrom, AHost, 
      ATarget, AMessage: string);
    procedure OnSSLStatus(ASender: TObject; const AStatus: TIdStatus; 
      const AStatusText: string);
    procedure OnSSLVerifyPeer(ASender: TObject; AIOHandler: TIdIOHandler;
      const ACertificate: TIdX509; var AValid: Boolean);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Connect;
  end;

constructor TIRCLogger.Create;
begin
  // Create SSL handler
  FSSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  FSSL.SSLOptions.Method := sslvTLSv1_2;  // Use TLS 1.2
  FSSL.SSLOptions.Mode := sslmClient;
  FSSL.OnStatus := OnSSLStatus;
  FSSL.OnVerifyPeer := OnSSLVerifyPeer;

  FIRC := TIdIRC.Create(nil);
  
  // Set up IRC connection parameters
  FIRC.IOHandler := FSSL;  // Assign SSL handler
  FIRC.UseTLS := True;     // Enable TLS
  FIRC.Nick := 'LogBot';
  FIRC.Username := 'LogBot';
  FIRC.RealName := 'Simple IRC Logging Bot';
  FIRC.Host := 'irc.example.com';  // Replace with actual IRC server
  FIRC.Port := 6697;               // Standard IRC over TLS port
  
  // Set up event handlers
  FIRC.OnConnected := OnConnect;
  FIRC.OnReceive := OnReceive;
  FIRC.OnPrivMessage := OnPrivMessage;
  
  // Initialize log file
  AssignFile(FLogFile, 'irc_log.txt');
  if FileExists('irc_log.txt') then
    Append(FLogFile)
  else
    Rewrite(FLogFile);
end;

destructor TIRCLogger.Destroy;
begin
  CloseFile(FLogFile);
  FIRC.Free;
  FSSL.Free;
  inherited;
end;

procedure TIRCLogger.Connect;
begin
  try
    FIRC.Connect;
  except
    on E: Exception do
    begin
      Writeln('Connection error: ', E.Message);
      Writeln(FLogFile, '[' + DateTimeToStr(Now) + '] Connection error: ' + E.Message);
      Flush(FLogFile);
    end;
  end;
end;

procedure TIRCLogger.OnConnect(Sender: TObject);
begin
  FIRC.Join('#yourchannel');  // Replace with actual channel name
  Writeln('Connected to IRC server');
  Writeln(FLogFile, '[' + DateTimeToStr(Now) + '] Connected to IRC server');
  Flush(FLogFile);
end;

procedure TIRCLogger.OnReceive(Sender: TObject; AMsg: string);
begin
  // Log all received messages
  Writeln(FLogFile, '[' + DateTimeToStr(Now) + '] ' + AMsg);
  Flush(FLogFile);
end;

procedure TIRCLogger.OnPrivMessage(ASender: TIdContext; const ANicknameFrom, 
  AHost, ATarget, AMessage: string);
begin
  // Log private messages
  Writeln(FLogFile, Format('[%s] <%s> %s', [
    DateTimeToStr(Now),
    ANicknameFrom,
    AMessage
  ]));
  Flush(FLogFile);
end;

procedure TIRCLogger.OnSSLStatus(ASender: TObject; const AStatus: TIdStatus;
  const AStatusText: string);
begin
  // Log SSL/TLS status changes
  Writeln(Format('SSL Status: %s - %s', [GetEnumName(TypeInfo(TIdStatus), 
    Ord(AStatus)), AStatusText]));
  Writeln(FLogFile, Format('[%s] SSL Status: %s - %s', [
    DateTimeToStr(Now),
    GetEnumName(TypeInfo(TIdStatus), Ord(AStatus)),
    AStatusText
  ]));
  Flush(FLogFile);
end;

procedure TIRCLogger.OnSSLVerifyPeer(ASender: TObject; AIOHandler: TIdIOHandler;
  const ACertificate: TIdX509; var AValid: Boolean);
begin
  // Log certificate verification
  Writeln('Verifying SSL certificate from: ' + ACertificate.Subject);
  Writeln(FLogFile, Format('[%s] Verifying SSL certificate from: %s', [
    DateTimeToStr(Now),
    ACertificate.Subject
  ]));
  Flush(FLogFile);
  // You might want to implement proper certificate validation here
  AValid := True;
end;

var
  Logger: TIRCLogger;
begin
  Logger := TIRCLogger.Create;
  try
    Logger.Connect;
    Writeln('Press Enter to quit...');
    Readln;
  finally
    Logger.Free;
  end;
end.

I've updated the IRC bot to support TLS connections. Here are the key changes:

  1. Added the IdSSLOpenSSL unit to support SSL/TLS
  2. Created a TIdSSLIOHandlerSocketOpenSSL component for handling TLS connections
  3. Changed the default port to 6697 (standard IRC over TLS port)
  4. Added SSL status and certificate verification event handlers
  5. Added logging for SSL/TLS-related events

To use this secured version, you'll need to:

  1. Have OpenSSL libraries installed on your system
  2. Make sure you're connecting to an IRC server that supports TLS
  3. The server certificate verification is currently set to always accept - you might want to implement proper certificate validation in OnSSLVerifyPeer

Would you like me to explain any of the TLS-specific components or add any additional security features?

Prompt

Can you please alter the current example so that the main body hooks to system signals?

Answer

program IRCLogBot;

uses
  SysUtils, Classes, IdIRC, IdGlobal, IdComponent, IdSSLOpenSSL
  {$IFDEF UNIX}
  , BaseUnix
  {$ENDIF}
  ;

type
  TIRCLogger = class
  private
    FIRC: TIdIRC;
    FLogFile: TextFile;
    FSSL: TIdSSLIOHandlerSocketOpenSSL;
    FTerminated: Boolean;
    procedure OnConnect(Sender: TObject);
    procedure OnReceive(Sender: TObject; AMsg: string);
    procedure OnPrivMessage(ASender: TIdContext; const ANicknameFrom, AHost, 
      ATarget, AMessage: string);
    procedure OnSSLStatus(ASender: TObject; const AStatus: TIdStatus; 
      const AStatusText: string);
    procedure OnSSLVerifyPeer(ASender: TObject; AIOHandler: TIdIOHandler;
      const ACertificate: TIdX509; var AValid: Boolean);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Connect;
    procedure Terminate;
    property Terminated: Boolean read FTerminated;
  end;

var
  Logger: TIRCLogger = nil;

{$IFDEF UNIX}
procedure SignalHandler(sig: cint); cdecl;
begin
  case sig of
    SIGTERM, SIGINT:
      begin
        WriteLn('Received termination signal');
        if Assigned(Logger) then
          Logger.Terminate;
      end;
    SIGHUP:
      begin
        WriteLn('Received SIGHUP - could implement config reload here');
        // Could implement configuration reload here
      end;
  end;
end;

procedure SetupSignalHandlers;
var
  act: SigActionRec;
begin
  FillChar(act, SizeOf(act), 0);
  act.sa_handler := @SignalHandler;
  act.sa_flags := 0;
  
  // Set up signal handlers
  fpSigAction(SIGTERM, @act, nil);
  fpSigAction(SIGINT, @act, nil);
  fpSigAction(SIGHUP, @act, nil);
end;
{$ENDIF}

{$IFDEF WINDOWS}
function ConsoleCtrlHandler(CtrlType: DWORD): BOOL; stdcall;
begin
  case CtrlType of
    CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT:
      begin
        WriteLn('Received termination signal');
        if Assigned(Logger) then
          Logger.Terminate;
        Result := True;
        Exit;
      end;
  end;
  Result := False;
end;

procedure SetupSignalHandlers;
begin
  SetConsoleCtrlHandler(@ConsoleCtrlHandler, True);
end;
{$ENDIF}

constructor TIRCLogger.Create;
begin
  FTerminated := False;
  
  // Create SSL handler
  FSSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  FSSL.SSLOptions.Method := sslvTLSv1_2;  // Use TLS 1.2
  FSSL.SSLOptions.Mode := sslmClient;
  FSSL.OnStatus := OnSSLStatus;
  FSSL.OnVerifyPeer := OnSSLVerifyPeer;

  FIRC := TIdIRC.Create(nil);
  
  // Set up IRC connection parameters
  FIRC.IOHandler := FSSL;  // Assign SSL handler
  FIRC.UseTLS := True;     // Enable TLS
  FIRC.Nick := 'LogBot';
  FIRC.Username := 'LogBot';
  FIRC.RealName := 'Simple IRC Logging Bot';
  FIRC.Host := 'irc.example.com';  // Replace with actual IRC server
  FIRC.Port := 6697;               // Standard IRC over TLS port
  
  // Set up event handlers
  FIRC.OnConnected := OnConnect;
  FIRC.OnReceive := OnReceive;
  FIRC.OnPrivMessage := OnPrivMessage;
  
  // Initialize log file
  AssignFile(FLogFile, 'irc_log.txt');
  if FileExists('irc_log.txt') then
    Append(FLogFile)
  else
    Rewrite(FLogFile);
end;

destructor TIRCLogger.Destroy;
begin
  if FIRC.Connected then
    FIRC.Disconnect;
  CloseFile(FLogFile);
  FIRC.Free;
  FSSL.Free;
  inherited;
end;

procedure TIRCLogger.Connect;
begin
  try
    FIRC.Connect;
  except
    on E: Exception do
    begin
      Writeln('Connection error: ', E.Message);
      Writeln(FLogFile, '[' + DateTimeToStr(Now) + '] Connection error: ' + E.Message);
      Flush(FLogFile);
    end;
  end;
end;

procedure TIRCLogger.Terminate;
begin
  FTerminated := True;
  if FIRC.Connected then
  begin
    FIRC.SendMessage('', 'QUIT :Received termination signal');
    FIRC.Disconnect;
  end;
end;

procedure TIRCLogger.OnConnect(Sender: TObject);
begin
  FIRC.Join('#yourchannel');  // Replace with actual channel name
  Writeln('Connected to IRC server');
  Writeln(FLogFile, '[' + DateTimeToStr(Now) + '] Connected to IRC server');
  Flush(FLogFile);
end;

procedure TIRCLogger.OnReceive(Sender: TObject; AMsg: string);
begin
  // Log all received messages
  Writeln(FLogFile, '[' + DateTimeToStr(Now) + '] ' + AMsg);
  Flush(FLogFile);
end;

procedure TIRCLogger.OnPrivMessage(ASender: TIdContext; const ANicknameFrom, 
  AHost, ATarget, AMessage: string);
begin
  // Log private messages
  Writeln(FLogFile, Format('[%s] <%s> %s', [
    DateTimeToStr(Now),
    ANicknameFrom,
    AMessage
  ]));
  Flush(FLogFile);
end;

procedure TIRCLogger.OnSSLStatus(ASender: TObject; const AStatus: TIdStatus;
  const AStatusText: string);
begin
  // Log SSL/TLS status changes
  Writeln(Format('SSL Status: %s - %s', [GetEnumName(TypeInfo(TIdStatus), 
    Ord(AStatus)), AStatusText]));
  Writeln(FLogFile, Format('[%s] SSL Status: %s - %s', [
    DateTimeToStr(Now),
    GetEnumName(TypeInfo(TIdStatus), Ord(AStatus)),
    AStatusText
  ]));
  Flush(FLogFile);
end;

procedure TIRCLogger.OnSSLVerifyPeer(ASender: TObject; AIOHandler: TIdIOHandler;
  const ACertificate: TIdX509; var AValid: Boolean);
begin
  // Log certificate verification
  Writeln('Verifying SSL certificate from: ' + ACertificate.Subject);
  Writeln(FLogFile, Format('[%s] Verifying SSL certificate from: %s', [
    DateTimeToStr(Now),
    ACertificate.Subject
  ]));
  Flush(FLogFile);
  // You might want to implement proper certificate validation here
  AValid := True;
end;

begin
  SetupSignalHandlers;
  Logger := TIRCLogger.Create;
  try
    Logger.Connect;
    WriteLn('Bot is running. Press Ctrl+C to quit...');
    
    // Main loop
    while not Logger.Terminated do
    begin
      Sleep(100);  // Prevent busy waiting
      {$IFDEF UNIX}
      // Process pending signals
      fpPause;
      {$ENDIF}
    end;
  finally
    Logger.Free;
    Logger := nil;
  end;
end.

I've updated the IRC bot to handle system signals properly. Here are the key changes:

  1. Added signal handling for both Unix-like systems and Windows:

    • Unix: SIGTERM, SIGINT (Ctrl+C), and SIGHUP
    • Windows: CTRL_C_EVENT, CTRL_BREAK_EVENT, and CTRL_CLOSE_EVENT
  2. Added a Terminated property and Terminate method to handle graceful shutdown:

    • Sends a QUIT message to the IRC server
    • Disconnects properly
    • Closes the log file
  3. Replaced the simple Readln wait with a main loop that:

    • Checks for termination
    • Sleeps to prevent busy waiting
    • On Unix systems, uses fpPause to properly handle signals
  4. Made the Logger variable global to allow access from signal handlers

  5. Added conditional compilation directives ({$IFDEF UNIX} and {$IFDEF WINDOWS}) to handle platform-specific code

To use this version:

  1. The bot will now respond properly to Ctrl+C
  2. On Unix systems, you can send SIGHUP to potentially reload configuration (currently just logs the signal)
  3. The bot will clean up properly when terminated

Would you like me to add any additional signal handling features or explain any part of the implementation in more detail?

Prompt

Can you please alter the code so we don't use the antiquated file methods, but instead use a file stream?

Answer

program IRCLogBot;

uses
  SysUtils, Classes, IdIRC, IdGlobal, IdComponent, IdSSLOpenSSL
  {$IFDEF UNIX}
  , BaseUnix
  {$ENDIF}
  ;

type
  TIRCLogger = class
  private
    FIRC: TIdIRC;
    FLogStream: TFileStream;
    FLogWriter: TStreamWriter;
    FSSL: TIdSSLIOHandlerSocketOpenSSL;
    FTerminated: Boolean;
    procedure OnConnect(Sender: TObject);
    procedure OnReceive(Sender: TObject; AMsg: string);
    procedure OnPrivMessage(ASender: TIdContext; const ANicknameFrom, AHost, 
      ATarget, AMessage: string);
    procedure OnSSLStatus(ASender: TObject; const AStatus: TIdStatus; 
      const AStatusText: string);
    procedure OnSSLVerifyPeer(ASender: TObject; AIOHandler: TIdIOHandler;
      const ACertificate: TIdX509; var AValid: Boolean);
    procedure WriteLog(const AMessage: string);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Connect;
    procedure Terminate;
    property Terminated: Boolean read FTerminated;
  end;

var
  Logger: TIRCLogger = nil;

{$IFDEF UNIX}
procedure SignalHandler(sig: cint); cdecl;
begin
  case sig of
    SIGTERM, SIGINT:
      begin
        WriteLn('Received termination signal');
        if Assigned(Logger) then
          Logger.Terminate;
      end;
    SIGHUP:
      begin
        WriteLn('Received SIGHUP - could implement config reload here');
        // Could implement configuration reload here
      end;
  end;
end;

procedure SetupSignalHandlers;
var
  act: SigActionRec;
begin
  FillChar(act, SizeOf(act), 0);
  act.sa_handler := @SignalHandler;
  act.sa_flags := 0;
  
  // Set up signal handlers
  fpSigAction(SIGTERM, @act, nil);
  fpSigAction(SIGINT, @act, nil);
  fpSigAction(SIGHUP, @act, nil);
end;
{$ENDIF}

{$IFDEF WINDOWS}
function ConsoleCtrlHandler(CtrlType: DWORD): BOOL; stdcall;
begin
  case CtrlType of
    CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT:
      begin
        WriteLn('Received termination signal');
        if Assigned(Logger) then
          Logger.Terminate;
        Result := True;
        Exit;
      end;
  end;
  Result := False;
end;

procedure SetupSignalHandlers;
begin
  SetConsoleCtrlHandler(@ConsoleCtrlHandler, True);
end;
{$ENDIF}

constructor TIRCLogger.Create;
var
  FileMode: Word;
begin
  FTerminated := False;
  
  // Create SSL handler
  FSSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  FSSL.SSLOptions.Method := sslvTLSv1_2;  // Use TLS 1.2
  FSSL.SSLOptions.Mode := sslmClient;
  FSSL.OnStatus := OnSSLStatus;
  FSSL.OnVerifyPeer := OnSSLVerifyPeer;

  FIRC := TIdIRC.Create(nil);
  
  // Set up IRC connection parameters
  FIRC.IOHandler := FSSL;  // Assign SSL handler
  FIRC.UseTLS := True;     // Enable TLS
  FIRC.Nick := 'LogBot';
  FIRC.Username := 'LogBot';
  FIRC.RealName := 'Simple IRC Logging Bot';
  FIRC.Host := 'irc.example.com';  // Replace with actual IRC server
  FIRC.Port := 6697;               // Standard IRC over TLS port
  
  // Set up event handlers
  FIRC.OnConnected := OnConnect;
  FIRC.OnReceive := OnReceive;
  FIRC.OnPrivMessage := OnPrivMessage;
  
  // Initialize log file stream
  if FileExists('irc_log.txt') then
    FileMode := fmOpenWrite or fmShareDenyWrite
  else
    FileMode := fmCreate or fmShareDenyWrite;
    
  try
    FLogStream := TFileStream.Create('irc_log.txt', FileMode);
    if FileMode = fmOpenWrite then
      FLogStream.Seek(0, soEnd);  // Move to end of file if appending
      
    FLogWriter := TStreamWriter.Create(FLogStream, TEncoding.UTF8);
    FLogWriter.AutoFlush := True;  // Ensure immediate writes
  except
    on E: Exception do
    begin
      WriteLn('Error creating log file: ', E.Message);
      raise;
    end;
  end;
end;

destructor TIRCLogger.Destroy;
begin
  if FIRC.Connected then
    FIRC.Disconnect;
    
  // Clean up file handling objects
  if Assigned(FLogWriter) then
  begin
    FLogWriter.Flush;
    FLogWriter.Free;
  end;
  
  if Assigned(FLogStream) then
    FLogStream.Free;
    
  FIRC.Free;
  FSSL.Free;
  inherited;
end;

procedure TIRCLogger.WriteLog(const AMessage: string);
var
  TimeStamp: string;
begin
  if not Assigned(FLogWriter) then
    Exit;
    
  TimeStamp := FormatDateTime('yyyy-mm-dd hh:nn:ss', Now);
  FLogWriter.WriteLine(Format('[%s] %s', [TimeStamp, AMessage]));
end;

procedure TIRCLogger.Connect;
begin
  try
    FIRC.Connect;
  except
    on E: Exception do
    begin
      WriteLn('Connection error: ', E.Message);
      WriteLog('Connection error: ' + E.Message);
    end;
  end;
end;

procedure TIRCLogger.Terminate;
begin
  FTerminated := True;
  if FIRC.Connected then
  begin
    WriteLog('Bot shutting down...');
    FIRC.SendMessage('', 'QUIT :Received termination signal');
    FIRC.Disconnect;
  end;
end;

procedure TIRCLogger.OnConnect(Sender: TObject);
begin
  FIRC.Join('#yourchannel');  // Replace with actual channel name
  WriteLn('Connected to IRC server');
  WriteLog('Connected to IRC server');
end;

procedure TIRCLogger.OnReceive(Sender: TObject; AMsg: string);
begin
  WriteLog(AMsg);
end;

procedure TIRCLogger.OnPrivMessage(ASender: TIdContext; const ANicknameFrom, 
  AHost, ATarget, AMessage: string);
begin
  WriteLog(Format('<%s> %s', [ANicknameFrom, AMessage]));
end;

procedure TIRCLogger.OnSSLStatus(ASender: TObject; const AStatus: TIdStatus;
  const AStatusText: string);
begin
  WriteLog(Format('SSL Status: %s - %s', [
    GetEnumName(TypeInfo(TIdStatus), Ord(AStatus)),
    AStatusText
  ]));
end;

procedure TIRCLogger.OnSSLVerifyPeer(ASender: TObject; AIOHandler: TIdIOHandler;
  const ACertificate: TIdX509; var AValid: Boolean);
begin
  WriteLog(Format('Verifying SSL certificate from: %s', [ACertificate.Subject]));
  // You might want to implement proper certificate validation here
  AValid := True;
end;

begin
  SetupSignalHandlers;
  Logger := TIRCLogger.Create;
  try
    Logger.Connect;
    WriteLn('Bot is running. Press Ctrl+C to quit...');
    
    // Main loop
    while not Logger.Terminated do
    begin
      Sleep(100);  // Prevent busy waiting
      {$IFDEF UNIX}
      // Process pending signals
      fpPause;
      {$ENDIF}
    end;
  finally
    Logger.Free;
    Logger := nil;
  end;
end.

I've updated the IRC bot to use modern file streams instead of the older file handling methods. Here are the key changes:

  1. Replaced TextFile with TFileStream and TStreamWriter for better control and UTF-8 support
  2. Added proper file sharing modes for better concurrent access handling
  3. Created a centralized WriteLog method for consistent logging
  4. Added proper cleanup in the destructor
  5. Added automatic flushing for immediate writes through AutoFlush := True
  6. Improved error handling for file operations
  7. Added proper UTF-8 encoding support
  8. Standardized timestamp format
  9. Added proper file mode handling for create/append scenarios

The new implementation offers several advantages:

  • Better memory management
  • Proper encoding support
  • More robust file handling
  • Better error handling
  • More control over file access modes
  • Immediate writes with auto-flush

Would you like me to explain any part of the file handling implementation in more detail or make any additional improvements?

Prompt

Can you please alter the code so we can validate the SSL certificate?

Answer

program IRCLogBot;

uses
  SysUtils, Classes, IdIRC, IdGlobal, IdComponent, IdSSLOpenSSL,
  IdX509, IdSSLOpenSSLHeaders
  {$IFDEF UNIX}
  , BaseUnix
  {$ENDIF}
  ;

type
  TIRCLogger = class
  private
    FIRC: TIdIRC;
    FLogStream: TFileStream;
    FLogWriter: TStreamWriter;
    FSSL: TIdSSLIOHandlerSocketOpenSSL;
    FTerminated: Boolean;
    FCertStore: string;
    procedure OnConnect(Sender: TObject);
    procedure OnReceive(Sender: TObject; AMsg: string);
    procedure OnPrivMessage(ASender: TIdContext; const ANicknameFrom, AHost, 
      ATarget, AMessage: string);
    procedure OnSSLStatus(ASender: TObject; const AStatus: TIdStatus; 
      const AStatusText: string);
    procedure OnSSLVerifyPeer(ASender: TObject; AIOHandler: TIdIOHandler;
      const ACertificate: TIdX509; var AValid: Boolean);
    procedure WriteLog(const AMessage: string);
    function ValidateCertificate(const ACertificate: TIdX509): Boolean;
    procedure InitializeSSL;
  public
    constructor Create(const ACertStore: string);
    destructor Destroy; override;
    procedure Connect;
    procedure Terminate;
    property Terminated: Boolean read FTerminated;
    property CertStore: string read FCertStore write FCertStore;
  end;

{ Signal handling code remains the same... }

{$IFDEF UNIX}
procedure SignalHandler(sig: cint); cdecl;
begin
  case sig of
    SIGTERM, SIGINT:
      begin
        WriteLn('Received termination signal');
        if Assigned(Logger) then
          Logger.Terminate;
      end;
    SIGHUP:
      begin
        WriteLn('Received SIGHUP - could implement config reload here');
      end;
  end;
end;

procedure SetupSignalHandlers;
var
  act: SigActionRec;
begin
  FillChar(act, SizeOf(act), 0);
  act.sa_handler := @SignalHandler;
  act.sa_flags := 0;
  
  fpSigAction(SIGTERM, @act, nil);
  fpSigAction(SIGINT, @act, nil);
  fpSigAction(SIGHUP, @act, nil);
end;
{$ENDIF}

{$IFDEF WINDOWS}
function ConsoleCtrlHandler(CtrlType: DWORD): BOOL; stdcall;
begin
  case CtrlType of
    CTRL_C_EVENT, CTRL_BREAK_EVENT, CTRL_CLOSE_EVENT:
      begin
        WriteLn('Received termination signal');
        if Assigned(Logger) then
          Logger.Terminate;
        Result := True;
        Exit;
      end;
  end;
  Result := False;
end;

procedure SetupSignalHandlers;
begin
  SetConsoleCtrlHandler(@ConsoleCtrlHandler, True);
end;
{$ENDIF}

procedure TIRCLogger.InitializeSSL;
begin
  FSSL := TIdSSLIOHandlerSocketOpenSSL.Create(nil);
  
  // Configure SSL/TLS settings
  FSSL.SSLOptions.Method := sslvTLSv1_2;  // Use TLS 1.2
  FSSL.SSLOptions.Mode := sslmClient;
  FSSL.SSLOptions.VerifyMode := [sslvrfPeer, sslvrfFailIfNoPeerCert];
  FSSL.SSLOptions.VerifyDepth := 9;
  
  // Set up certificate store
  if DirectoryExists(FCertStore) then
  begin
    FSSL.SSLOptions.CertFile := '';  // Clear default cert file
    FSSL.SSLOptions.RootCertFile := '';  // Clear default root cert
    FSSL.SSLOptions.CAFile := '';  // Clear default CA file
    FSSL.SSLOptions.CertStore := FCertStore;  // Set certificate store path
  end
  else
    raise Exception.CreateFmt('Certificate store directory not found: %s', [FCertStore]);

  FSSL.OnStatus := OnSSLStatus;
  FSSL.OnVerifyPeer := OnSSLVerifyPeer;
end;

function TIRCLogger.ValidateCertificate(const ACertificate: TIdX509): Boolean;
var
  VerifyResult: Integer;
  Context: PSSL_CTX;
  SSL: PSSL;
  Store: PX509_STORE;
  CertChain: PSTACK_OF_X509;
  ErrorString: string;
begin
  Result := False;
  if not Assigned(ACertificate) then
    Exit;

  try
    // Get SSL context and connection
    Context := FSSL.SSLContext;
    SSL := FSSL.SSLSocket;
    if not Assigned(Context) or not Assigned(SSL) then
      Exit;

    // Get certificate store
    Store := SSL_CTX_get_cert_store(Context);
    if not Assigned(Store) then
      Exit;

    // Get peer certificate chain
    CertChain := SSL_get_peer_cert_chain(SSL);
    if not Assigned(CertChain) then
      Exit;

    // Verify the certificate chain
    VerifyResult := SSL_get_verify_result(SSL);
    Result := (VerifyResult = X509_V_OK);

    if not Result then
    begin
      ErrorString := Format('Certificate validation failed: %s',
        [X509_verify_cert_error_string(VerifyResult)]);
      WriteLog(ErrorString);
      WriteLn(ErrorString);
    end
    else
    begin
      // Additional custom checks can be added here
      // For example, checking certificate dates, key usage, etc.
      if ACertificate.NotAfter < Now then
      begin
        Result := False;
        WriteLog('Certificate has expired');
      end;
      
      if ACertificate.NotBefore > Now then
      begin
        Result := False;
        WriteLog('Certificate is not yet valid');
      end;
    end;

  except
    on E: Exception do
    begin
      Result := False;
      WriteLog('Certificate validation error: ' + E.Message);
    end;
  end;
end;

constructor TIRCLogger.Create(const ACertStore: string);
var
  FileMode: Word;
begin
  FTerminated := False;
  FCertStore := ACertStore;
  
  // Initialize SSL
  InitializeSSL;

  FIRC := TIdIRC.Create(nil);
  
  // Set up IRC connection parameters
  FIRC.IOHandler := FSSL;
  FIRC.UseTLS := True;
  FIRC.Nick := 'LogBot';
  FIRC.Username := 'LogBot';
  FIRC.RealName := 'Simple IRC Logging Bot';
  FIRC.Host := 'irc.example.com';
  FIRC.Port := 6697;
  
  FIRC.OnConnected := OnConnect;
  FIRC.OnReceive := OnReceive;
  FIRC.OnPrivMessage := OnPrivMessage;
  
  // Initialize log file stream
  if FileExists('irc_log.txt') then
    FileMode := fmOpenWrite or fmShareDenyWrite
  else
    FileMode := fmCreate or fmShareDenyWrite;
    
  try
    FLogStream := TFileStream.Create('irc_log.txt', FileMode);
    if FileMode = fmOpenWrite then
      FLogStream.Seek(0, soEnd);
      
    FLogWriter := TStreamWriter.Create(FLogStream, TEncoding.UTF8);
    FLogWriter.AutoFlush := True;
  except
    on E: Exception do
    begin
      WriteLn('Error creating log file: ', E.Message);
      raise;
    end;
  end;
end;

procedure TIRCLogger.OnSSLVerifyPeer(ASender: TObject; AIOHandler: TIdIOHandler;
  const ACertificate: TIdX509; var AValid: Boolean);
begin
  WriteLog(Format('Verifying SSL certificate from: %s', [ACertificate.Subject]));
  AValid := ValidateCertificate(ACertificate);
  
  if AValid then
    WriteLog('Certificate validation successful')
  else
    WriteLog('Certificate validation failed');
end;

{ Rest of the implementation remains the same... }

var
  Logger: TIRCLogger = nil;
  CertStorePath: string;
begin
  {$IFDEF UNIX}
  CertStorePath := '/etc/ssl/certs';  // Default path for most Unix systems
  {$ENDIF}
  {$IFDEF WINDOWS}
  CertStorePath := ExtractFilePath(ParamStr(0)) + 'certs';  // Local certs directory
  {$ENDIF}

  SetupSignalHandlers;
  
  try
    Logger := TIRCLogger.Create(CertStorePath);
    Logger.Connect;
    WriteLn('Bot is running. Press Ctrl+C to quit...');
    
    while not Logger.Terminated do
    begin
      Sleep(100);
      {$IFDEF UNIX}
      fpPause;
      {$ENDIF}
    end;
  finally
    Logger.Free;
    Logger := nil;
  end;
end.

I've updated the IRC bot with proper SSL certificate validation. Here are the key changes:

  1. Added certificate store path handling:

    • Unix: defaults to /etc/ssl/certs
    • Windows: uses a local certs directory
  2. Added comprehensive certificate validation:

    • Verifies the certificate chain
    • Checks certificate dates
    • Uses OpenSSL's verification infrastructure
    • Proper error handling and logging
  3. Enhanced SSL configuration:

    • Enforces TLS 1.2
    • Requires peer certificate
    • Sets verification depth
    • Configurable certificate store path
  4. Added detailed certificate validation logging

To use this secured version:

  1. On Unix systems, ensure the system's certificate store is properly maintained
  2. On Windows, create a certs directory and populate it with required certificates
  3. The bot will now properly validate certificates and log the results

Note: You might need to set up the proper certificates in the store path. Depending on your IRC server, you may need to:

  1. Export the server's CA certificate
  2. Place it in the certificate store directory
  3. Update the certificate store path if needed

Would you like me to explain any part of the certificate validation process in more detail or add any additional security features?