New build path variable
[freeradius.git] / scripts / radsqlrelay
1 #!/usr/bin/perl
2 ##
3 ##  radsqlrelay.pl      This program tails a SQL logfile and forwards
4 ##                      the queries to a database server. Used to
5 ##                      replicate accounting records to one (central)
6 ##                      database, even if the database has extended
7 ##                      downtime.
8 ##
9 ##  Version:    $Id$
10 ##
11 ##  Author:     Nicolas Baradakis <nicolas.baradakis@cegetel.net>
12 ##
13 ##  Copyright (C) 2005 Cegetel
14 ##
15 ##  This program is free software; you can redistribute it and/or
16 ##  modify it under the terms of the GNU General Public License
17 ##  as published by the Free Software Foundation; either version 2
18 ##  of the License, or (at your option) any later version.
19 ##
20 ##  This program is distributed in the hope that it will be useful,
21 ##  but WITHOUT ANY WARRANTY; without even the implied warranty of
22 ##  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 ##  GNU General Public License for more details.
24 ##
25 ##  You should have received a copy of the GNU General Public License
26 ##  along with this program; if not, write to the Free Software
27 ##  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
28 ##
29
30 use DBI;
31 use Fcntl;
32 use Getopt::Std;
33 use POSIX qw(:unistd_h :errno_h);
34
35 use warnings;
36 use strict;
37
38 my $need_exit = 0;
39
40 sub got_signal()
41 {
42     $need_exit = 1;
43 }
44
45 # /!\ OS-dependent structure
46 # Linux struct flock
47 #   short l_type;
48 #   short l_whence;
49 #   off_t l_start;
50 #   off_t l_len;
51 #   pid_t l_pid;
52 # c2ph says: typedef='s2 l2 i', sizeof=16
53 my $FLOCK_STRUCT = 's2l2i';
54
55 sub setlock($;$$)
56 {
57     my ($fh, $start, $len) = @_;
58     $start = 0 unless defined $start;
59     $len = 0 unless defined $len;
60
61                                     #type     whence    start   till  pid
62     my $packed = pack($FLOCK_STRUCT, F_WRLCK, SEEK_SET, $start, $len, 0);
63     if (fcntl($fh, F_SETLKW, $packed)) { return 1 }
64     else { return 0 }
65 }
66
67 sub usage()
68 {
69     print STDERR <<HERE;
70 usage: radsqlrelay [options] file_path
71 options:
72         -?              Print this help message.
73         -1              One-shot mode: push the file to database and exit.
74         -b database     Name of the database to use.
75         -d sql_driver   Driver to use: mysql, pg, oracle.
76         -f file         Read password from file, instead of command line.
77         -h host         Connect to host.
78         -P port         Port number to use for connection.
79         -p passord      Password to use when connecting to server.
80         -u user         User for login.
81         -x              Turn on debugging.
82 HERE
83 }
84
85 sub connect_wait($)
86 {
87     my $dbinfo = shift;
88     my $dbh;
89     while (!$dbh) {
90         $dbh = DBI->connect($dbinfo->{base}, $dbinfo->{user}, $dbinfo->{pass},
91                             { RaiseError => 0, PrintError => 0,
92                               AutoCommit => 1 });
93         sleep (1) if !$dbh;
94         exit if $need_exit;
95     }
96     $dbinfo->{handle} = $dbh;
97 }
98
99 sub process_file($$)
100 {
101     my ($dbinfo, $path) = @_;
102
103     unless (-e $path.'.work') {
104         until (rename($path, $path.'.work')) {
105             if ($! == ENOENT) {
106                 sleep(1);
107                 return if $need_exit;
108             } else {
109                 print STDERR "error: Couldn't move $path to $path.work: $!\n";
110                 exit 1;
111             }
112         }
113     }
114
115     open(FILE, "+< $path.work") or die "error: Couldn't open $path.work: $!\n";
116     setlock(\*FILE) or die "error: Couldn't lock $path.work: $!\n";
117
118     while (<FILE>) {
119         chomp(my $query = $_);
120         until ($dbinfo->{handle}->do($query)) {
121             print $dbinfo->{handle}->errstr."\n";
122             if ($dbinfo->{handle}->ping) {
123                 sleep (1);
124             } else {
125                 print "error: Lost connection to database\n";
126                 $dbinfo->{handle}->disconnect;
127                 connect_wait($dbinfo);
128             }
129         }
130     }
131
132     unlink($path.'.work');
133     close(FILE); # and unlock
134 }
135
136 # sub main()
137
138 my %args = (
139             b => 'radius',
140             d => 'mysql',
141             h => 'localhost',
142             p => 'radius',
143             u => 'radius',
144 );
145 my $ret = getopts("b:d:f:h:P:p:u:x1?", \%args);
146 if (!$ret or @ARGV != 1) {
147     usage();
148     exit 1;
149 }
150 if ($args{'?'}) {
151     usage();
152     exit 0;
153 }
154
155 my $data_source;
156 if (lc($args{d}) eq 'mysql') {
157     $data_source = "DBI:mysql:database=$args{b};host=$args{h}";
158 } elsif (lc($args{d}) eq 'pg') {
159     $data_source = "DBI:Pg:dbname=$args{b};host=$args{h}";
160 } elsif (lc($args{d}) eq 'oracle') {
161     $data_source = "DBI:Oracle:$args{b}";
162 } else {
163     print STDERR "error: SQL driver not supported yet: $args{d}\n";
164     exit 1;
165 }
166 $data_source .= ";port=$args{P}" if $args{'P'};
167
168 my $pw;
169 if($args{f}) {
170     open(FILE, "< $args{f}") or die "error: Couldn't open $args{f}: $!\n";
171     $pw = <FILE>;
172     chomp($pw);
173     close(FILE);
174 } else {
175     # args{p} is always defined.
176     $pw = $args{p};
177 }
178
179 $SIG{INT} = \&got_signal;
180 $SIG{TERM} = \&got_signal;
181
182 my %dbinfo = (
183               base => $data_source,
184               user => $args{u},
185               pass => $pw,
186 );
187 connect_wait(\%dbinfo);
188
189 my $path = shift @ARGV;
190
191 until ($need_exit) {
192     process_file(\%dbinfo, $path);
193     last if ($args{1} || $need_exit);
194     sleep(10);
195 }
196
197 $dbinfo{handle}->disconnect;