Can you please give me a simple example of an IRC
logging bot using Free Pascal with the IRC
client from Indy
?
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:
- Connects to an IRC server
- Joins a specified channel
- Logs all received messages to a text file
- Handles private messages separately
- Includes timestamps for all logged messages
To use this bot, you'll need to:
- Have the Indy components installed in your Free Pascal environment
- Replace
'irc.example.com'
with your actual IRC server address - Replace
'#yourchannel'
with the channel you want to monitor - 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?
Could you please alter the example in order for the bot to be able to connect via TLS
.
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:
- Added the
IdSSLOpenSSL
unit to support SSL/TLS - Created a
TIdSSLIOHandlerSocketOpenSSL
component for handling TLS connections - Changed the default port to 6697 (standard IRC over TLS port)
- Added SSL status and certificate verification event handlers
- Added logging for SSL/TLS-related events
To use this secured version, you'll need to:
- Have OpenSSL libraries installed on your system
- Make sure you're connecting to an IRC server that supports TLS
- 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?
Can you please alter the current example so that the main body hooks to system signals?
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:
-
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
-
Added a
Terminated
property andTerminate
method to handle graceful shutdown:- Sends a QUIT message to the IRC server
- Disconnects properly
- Closes the log file
-
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
-
Made the Logger variable global to allow access from signal handlers
-
Added conditional compilation directives (
{$IFDEF UNIX}
and{$IFDEF WINDOWS}
) to handle platform-specific code
To use this version:
- The bot will now respond properly to Ctrl+C
- On Unix systems, you can send SIGHUP to potentially reload configuration (currently just logs the signal)
- 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?
Can you please alter the code so we don't use the antiquated file methods, but instead use a file stream?
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:
- Replaced
TextFile
withTFileStream
andTStreamWriter
for better control and UTF-8 support - Added proper file sharing modes for better concurrent access handling
- Created a centralized
WriteLog
method for consistent logging - Added proper cleanup in the destructor
- Added automatic flushing for immediate writes through
AutoFlush := True
- Improved error handling for file operations
- Added proper UTF-8 encoding support
- Standardized timestamp format
- 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?
Can you please alter the code so we can validate the SSL
certificate?
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:
-
Added certificate store path handling:
- Unix: defaults to
/etc/ssl/certs
- Windows: uses a local
certs
directory
- Unix: defaults to
-
Added comprehensive certificate validation:
- Verifies the certificate chain
- Checks certificate dates
- Uses OpenSSL's verification infrastructure
- Proper error handling and logging
-
Enhanced SSL configuration:
- Enforces TLS 1.2
- Requires peer certificate
- Sets verification depth
- Configurable certificate store path
-
Added detailed certificate validation logging
To use this secured version:
- On Unix systems, ensure the system's certificate store is properly maintained
- On Windows, create a
certs
directory and populate it with required certificates - 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:
- Export the server's CA certificate
- Place it in the certificate store directory
- 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?