====== Новый модуль авторизации mikbill.pl для Radius ======
Работает только с версии 3.09.02, на версиях ниже могут быть проблемы так как со стороны ядра биллинга нет контроля соединения
* Добавлена проверка соединения с ядром биллинга и его автоматическое восстановление
* Добавлено логирование о потери связи
\\ Данный код заменить в файле /etc/raddb/mikbill.pl или /etc/freeradius/3.0/mikbill.pl
\\ После чего перезапустить радиус.
===== Changelog =====
* 2024-11-26: Переписан модуль (добавлены проверки ответа от ядра, улучшен контроль соединения).
===== Файл mikbill.pl =====
Обратите внимание на $host="127.0.0.1" и $port="22007" если они у вас отличаются!
use strict;
use warnings;
use vars qw(%RAD_REQUEST %RAD_REPLY %RAD_CHECK);
use IO::Socket;
use Socket qw(SOL_SOCKET SO_KEEPALIVE SO_ERROR);
use locale;
use POSIX;
use serialize;
use Data::Dumper;
setlocale(LC_ALL, 'C');
use constant RLM_MODULE_REJECT=> 0;# /* immediately reject the request */
use constant RLM_MODULE_FAIL=> 1;# /* module failed, don't reply */
use constant RLM_MODULE_OK=> 2;# /* the module is OK, continue */
use constant RLM_MODULE_HANDLED=> 3;# /* the module handled the request, so stop. */
use constant RLM_MODULE_INVALID=> 4;# /* the module considers the request invalid. */
use constant RLM_MODULE_USERLOCK=> 5;# /* reject the request (user is locked out) */
use constant RLM_MODULE_NOTFOUND=> 6;# /* user not found */
use constant RLM_MODULE_NOOP=> 7;# /* module succeeded without doing anything */
use constant RLM_MODULE_UPDATED=> 8;# /* OK (pairs modified) */
use constant RLM_MODULE_NUMCODES=> 9;# /* How many return codes there are */
use constant RADIUS_L_DBG=> 0;# /* debug log */
use constant RADIUS_L_AUTH=> 1;# /* auth log */
use constant RADIUS_L_PROXY=> 2;# /* peroxy log */
use constant RADIUS_L_INFO=> 3;# /* info log */
use constant RADIUS_L_ERROR=> 4;# /* error log */
# mikbill daemon IP address and port
my $host="127.0.0.1";
my $port="22007";
my $tcp_keepidle = 60; # 60 секунд
my $tcp_keepintvl = 10; # 10 секунд
my $tcp_keepcnt = 5; # 5 попыток
# def
my $sock;
my $answer;
# Helper function for logging
sub log_message {
my ($level, $message) = @_;
&radiusd::radlog($level, $message);
}
sub socket_init {
my $sock = new IO::Socket::INET (
PeerAddr => $host,
PeerPort => $port,
Proto => 'tcp',
) or do {
log_message(RADIUS_L_ERROR, "No connection with mikbill daemon ($host:$port)");
return undef;
};
# Configure socket options
setsockopt($sock, SOL_SOCKET, SO_KEEPALIVE, 1) or do {
log_message(RADIUS_L_ERROR, "Failed to enable SO_KEEPALIVE: $!");
return undef;
};
setsockopt($sock, Socket::IPPROTO_TCP(), Socket::TCP_KEEPIDLE(), $tcp_keepidle) or do {
log_message(RADIUS_L_ERROR, "Failed to set TCP_KEEPIDLE: $!");
return undef;
};
setsockopt($sock, Socket::IPPROTO_TCP(), Socket::TCP_KEEPINTVL(), $tcp_keepintvl) or do {
log_message(RADIUS_L_ERROR, "Failed to set TCP_KEEPINTVL: $!");
return undef;
};
setsockopt($sock, Socket::IPPROTO_TCP(), Socket::TCP_KEEPCNT(), $tcp_keepcnt) or do {
log_message(RADIUS_L_ERROR, "Failed to set TCP_KEEPCNT: $!");
return undef;
};
log_message(RADIUS_L_INFO, "Connected to mikbill successfully.");
return $sock;
}
# Check if the socket is still connected
sub StillConnected {
return unless defined $sock;
return unless $sock->connected();
my $error = getsockopt($sock, SOL_SOCKET, SO_ERROR);
if (!defined $error) {
log_message(RADIUS_L_ERROR, "getsockopt failed: $!");
return;
}
if (!$error) {
log_message(RADIUS_L_ERROR, "Socket error detected: $error");
return;
}
return 1;
}
# Ensure socket is active, reinitialize if needed
sub check_socket {
unless (&StillConnected()) {
log_message(RADIUS_L_INFO, "Reinitializing socket...");
return socket_init();
}
return $sock;
}
# Close the socket cleanly
sub close_socket {
if (defined $sock) {
$sock->close();
undef $sock;
}
}
# Deserialize safely
sub safe_unserialize {
my ($data) = @_;
my $result = eval { unserialize($data) };
if ($@) {
log_message(RADIUS_L_ERROR, "Deserialization failed: $@");
log_message(RADIUS_L_DBG, "'$data'");
return undef;
}
return $result;
}
# Main authorize function
sub authorize {
return RLM_MODULE_REJECT unless &check_socket();
my $result = RLM_MODULE_OK;
eval {
print $sock "auth\n";
$answer = <$sock>;
print $sock serialize(\%RAD_REQUEST);
$answer = <$sock>;
print $sock serialize(\%RAD_REPLY);
$answer = <$sock>;
print $sock serialize(\%RAD_CHECK);
$answer = <$sock>;
if ($answer eq "reject\n") {
$result = RLM_MODULE_REJECT;
return;
}
$answer = <$sock>;
if ($answer eq "reject\n") {
$result = RLM_MODULE_REJECT;
return;
}
if ($answer eq " ") {
$answer = <$sock>;
}
if ($answer eq "accept\n") {
$answer = <$sock>;
}
my $unserialized_reply = safe_unserialize($answer);
if (defined $unserialized_reply && ref($unserialized_reply) eq 'HASH') {
%RAD_REPLY = %{$unserialized_reply};
} else {
log_message(RADIUS_L_DBG, "failed unserialize RAD_REPLY: " . Dumper($unserialized_reply));
$result = RLM_MODULE_REJECT;
return;
}
$answer = <$sock>;
my $unserialized_check = safe_unserialize($answer);
if (defined $unserialized_check && ref($unserialized_check) eq 'HASH') {
%RAD_CHECK = %{$unserialized_check};
} else {
log_message(RADIUS_L_DBG, "failed unserialize RAD_CHECK: " . Dumper($unserialized_check));
$result = RLM_MODULE_REJECT;
return;
}
};
if ($@) {
log_message(RADIUS_L_ERROR, "Authorize error: $@");
$result = RLM_MODULE_REJECT;
}
return $result;
}
# Authenticate function
sub authenticate {
if ($RAD_REPLY{'Packet-Type'} eq "Access-Reject") {
return RLM_MODULE_REJECT;
} else {
return RLM_MODULE_OK;
}
}
# Accounting function
sub accounting {
return RLM_MODULE_OK unless &check_socket();
eval {
print $sock "acct\n";
$answer = <$sock>;
print $sock serialize(\%RAD_REQUEST);
};
if ($@) {
log_message(RADIUS_L_ERROR, "Accounting error: $@");
}
return RLM_MODULE_OK;
}
# Log RADIUS request attributes
sub log_request_attributes {
for (keys %RAD_REQUEST) {
log_message(RADIUS_L_INFO, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}");
}
}
sub CLONE {
$sock=&socket_init;
}
# Lifecycle management functions
sub preacct { return RLM_MODULE_OK; }
sub checksimul { return RLM_MODULE_OK; }
sub pre_proxy { return RLM_MODULE_OK; }
sub post_proxy { return RLM_MODULE_OK; }
sub post_auth { return authorize(); }
sub xlat { log_message(RADIUS_L_INFO, "xlat not implemented"); }
sub detach { log_message(RADIUS_L_INFO, "rlm_perl::Detaching. Done."); }
sub test_call { log_message(RADIUS_L_INFO, "Test call invoked."); }
1;