| Server IP : 173.209.174.21 / Your IP : 216.73.216.89 Web Server : Apache/2.4.58 (Ubuntu) System : Linux wcfs-server 6.8.0-124-generic #124-Ubuntu SMP PREEMPT_DYNAMIC Tue May 26 13:00:45 UTC 2026 x86_64 User : nodor ( 1000) PHP Version : 8.3.6 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /bin/ |
Upload File : |
#!/usr/bin/perl -w
##
## radsqlrelay.pl This program tails a SQL logfile and forwards
## the queries to a database server. Used to
## replicate accounting records to one (central)
## database, even if the database has extended
## downtime.
##
## Version: $Id: 74531ba9bf7588eb42bb409fc69ecd0abdf919f5 $
##
## Author: Nicolas Baradakis <nicolas.baradakis@cegetel.net>
##
## Copyright (C) 2005 Cegetel
## Copyright (C) 2019 Network RADIUS
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version 2
## of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
##
use strict;
use DBI;
use Fcntl;
use Getopt::Std;
use POSIX qw(:unistd_h :errno_h);
use Time::HiRes qw(clock_gettime usleep CLOCK_MONOTONIC);
# We send watchdogs at half the indicated interval if the
# Linux::Systemd::Daemon module is available and the WATCHDOG_USEC environment
# variable is set by the systemd service manager.
my $watchdog_usec;
my $next_watchdog;
eval {
require Linux::Systemd::Daemon;
Linux::Systemd::Daemon->import();
$watchdog_usec = $ENV{'WATCHDOG_USEC'} || undef;
};
# To synthetically test the watchdog functionality then uncomment these next
# lines:
#
# $watchdog_usec = 30 *1000*1000;
# sub sd_notify {}
my $maxcollect = 100; # tunable, works for MySQL!
my $lastinsert;
my @values;
my $need_exit = 0;
my $debug = 0;
sub got_signal()
{
$need_exit = 1;
sd_notify(stopping => 1, status => 'Signalled. Shutting down.') if $watchdog_usec;
}
sub debug
{
print shift if $debug;
}
# /!\ OS-dependent structure
# Linux struct flock
# short l_type;
# short l_whence;
# off_t l_start;
# off_t l_len;
# pid_t l_pid;
# c2ph says: typedef='s2 l2 i', sizeof=16
my $FLOCK_STRUCT = 's2l2i';
sub setlock($;$$)
{
my ($fh, $start, $len) = @_;
$start = 0 unless defined $start;
$len = 0 unless defined $len;
#type whence start till pid
my $packed = pack($FLOCK_STRUCT, F_WRLCK, SEEK_SET, $start, $len, 0);
if (fcntl($fh, F_SETLKW, $packed)) { return 1 }
else { return 0 }
}
sub usage()
{
print STDERR <<HERE;
usage: radsqlrelay [options] file_path
options:
-? Print this help message.
-1 One-shot mode: push the file to database and exit.
-b database Name of the database to use.
-d sql_driver Driver to use: mysql, pg, oracle.
-f file Read password from file, instead of command line.
-h host Connect to host.
-P port Port number to use for connection.
-p password Password to use when connecting to server.
-u user User for login.
-x Turn on debugging.
HERE
}
# Sleep for given amount of time, but don't oversleep the watchdog interval.
# Send a watchdog notification if it is due.
# interval can be given as 0 to just check whether a watchdog is due and send
# it as necessary, in which case we do not yield.
sub sleep_for ($)
{
my $interval=shift;
$interval*=1000*1000;
if ($watchdog_usec) {
my $now=clock_gettime(CLOCK_MONOTONIC)*1000*1000;
if ($now >= $next_watchdog) {
$next_watchdog=$now+($watchdog_usec / 2);
debug "Sending watchdog\n";
sd_notify(watchdog => 1);
debug "Next watchdog due in ".(($next_watchdog-$now)/1000/1000)." secs.\n";
}
# Don't oversleep!
$interval=$next_watchdog-$now if $next_watchdog-$now < $interval;
}
return unless $interval; # Don't yield if we are not asked to sleep
debug "Sleeping for ".($interval/1000/1000)." secs.\n";
usleep ($interval);
}
sub connect_wait($)
{
my $dbinfo = shift;
my $dbh;
debug "Connecting to " . $dbinfo->{base};
while (!$dbh) {
debug ".";
$dbh = DBI->connect($dbinfo->{base}, $dbinfo->{user}, $dbinfo->{pass},
{ RaiseError => 0, PrintError => 0,
AutoCommit => 1 });
sleep_for (1) if !$dbh;
exit if $need_exit;
}
debug "\n";
$dbinfo->{handle} = $dbh;
}
sub process_file($$)
{
my ($dbinfo, $path) = @_;
sub do_inserts($) {
my $dbinfo = shift;
debug "I";
if (scalar(@values) > 0) {
my $query = $lastinsert . " ";
$query .= join(" ), ( ",@values);
$query .= " );";
do_query($dbinfo,$query);
}
@values = ();
}
sub do_query($$) {
my ($dbinfo,$query) = @_;
debug ">";
until ($dbinfo->{handle}->do($query)) {
# If an error occured and we're disconnected then try to recomnnect
# and redo the query, otherwise give up so we don't become stuck.
print $dbinfo->{handle}->errstr."\n";
if ($dbinfo->{handle}->ping) {
sleep_for (1);
last;
} else {
print "error: Lost connection to database\n";
$dbinfo->{handle}->disconnect;
connect_wait($dbinfo);
}
}
sleep_for(0) if $watchdog_usec; # Send a watchdog if it is due
}
unless (-e $path.'.work') {
debug "Waiting for $path\n";
until (rename($path, $path.'.work')) {
if ($! == ENOENT) {
sleep_for(1);
return if $need_exit;
} else {
print STDERR "error: Couldn't move $path to $path.work: $!\n";
exit 1;
}
}
debug "Renamed $path to $path.work\n";
}
debug "\nOpening $path.work\n";
open(FILE, "+< $path.work") or die "error: Couldn't open $path.work: $!\n";
debug "Getting file lock\n";
setlock(\*FILE) or die "error: Couldn't lock $path.work: $!\n";
$lastinsert = "";
@values = ();
debug "Reading: ";
my $lines = 0;
while (<FILE>) {
chomp (my $line = $_);
$lines++;
if (!($line =~ /^\s*insert\s+into\s+`?\w+`?\s+(?:\(.*?\)\s+)?
values\s*\(.*\)\s*;\s*$/ix)) {
# This is no INSERT, so start new collection
do_inserts($dbinfo);
debug ".";
$lastinsert = "";
# must output this line
do_query($dbinfo, "$line");
} else {
# This is an INSERT, so collect it
debug "+";
my $insert = $line;
my $values = $line;
$insert =~ s/^\s*(insert\s+into\s+`?\w+`?\s+(?:\(.*?\)\s+)?
values\s*\().*\)\s*;\s*$/$1/ix;
$values =~ s/^\s*insert\s+into\s+`?\w+`?\s+(?:\(.*?\)\s+)?
values\s*\((.*)\)\s*;\s*$/$1/ix;
if (($lastinsert ne "") && ($insert ne $lastinsert)) {
# This is different from the last one
do_inserts($dbinfo);
}
push(@values, $values);
$lastinsert = $insert; # start new collection
}
# limit to $maxcollect collected lines
if (scalar(@values) >= $maxcollect) {
debug "hit maxcollect limit, doing inserts";
do_inserts($dbinfo);
}
}
# Cleanup
debug "\nNo more lines to read, doing any final inserts: ";
do_inserts($dbinfo);
debug "\n";
debug "Processed $lines lines\n";
debug "Removing and closing $path.work\n\n";
unlink($path.'.work');
close(FILE); # and unlock
}
# sub main()
my %args = (
b => 'radius',
d => 'mysql',
h => 'localhost',
p => 'radius',
u => 'radius',
);
my $ret = getopts("b:d:f:h:P:p:u:x1?", \%args);
if (!$ret or @ARGV != 1) {
usage();
exit 1;
}
if ($args{'?'}) {
usage();
exit 0;
}
$debug = 1 if $args{'x'};
my $data_source;
if (lc($args{d}) eq 'mysql') {
$data_source = "DBI:mysql:database=$args{b};host=$args{h}";
} elsif (lc($args{d}) eq 'pg') {
$data_source = "DBI:Pg:dbname=$args{b};host=$args{h}";
} elsif (lc($args{d}) eq 'oracle') {
$data_source = "DBI:Oracle:$args{b}";
# Oracle does not conform to the SQL standard for multirow INSERTs
$maxcollect = 1;
} else {
print STDERR "error: SQL driver not supported yet: $args{d}\n";
exit 1;
}
$data_source .= ";port=$args{P}" if $args{'P'};
my $pw;
if($args{f}) {
open(FILE, "< $args{f}") or die "error: Couldn't open $args{f}: $!\n";
$pw = <FILE>;
chomp($pw);
close(FILE);
} else {
# args{p} is always defined.
$pw = $args{p};
}
$SIG{INT} = \&got_signal;
$SIG{TERM} = \&got_signal;
if ($watchdog_usec) {
debug "Watchdog set to $watchdog_usec\n";
my $now=clock_gettime(CLOCK_MONOTONIC)*1000*1000;
$next_watchdog=$now+($watchdog_usec / 2);
sd_notify(ready => 1, status => 'Started');
}
my %dbinfo = (
base => $data_source,
user => $args{u},
pass => $pw,
);
connect_wait(\%dbinfo);
my $path = shift @ARGV;
until ($need_exit) {
process_file(\%dbinfo, $path);
last if ($args{1} || $need_exit);
debug "Sleeping\n";
sleep_for(10);
}
debug "Disconnecting from database\n";
$dbinfo{handle}->disconnect;