Added note on OID for machine authentication
[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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 [-d sql_driver] [-b database] [-h host] [-u user] [-p password] [-1] 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.
76         -h host         Connect to host.
77         -p passord      Password to use when connecting to server.
78         -u user         User for login.
79         -x              Turn on debugging.
80 HERE
81 }
82
83 sub connect_wait($)
84 {
85     my $dbinfo = shift;
86     my $dbh;
87     while (!$dbh) {
88         $dbh = DBI->connect($dbinfo->{base}, $dbinfo->{user}, $dbinfo->{pass},
89                             { RaiseError => 0, PrintError => 0,
90                               AutoCommit => 1 });
91         sleep (1) if !$dbh;
92         exit if $need_exit;
93     }
94     $dbinfo->{handle} = $dbh;
95 }
96
97 sub process_file($$)
98 {
99     my ($dbinfo, $path) = @_;
100
101     until (rename($path, $path.'.work')) {
102         if ($! == ENOENT) {
103             sleep(1);
104             return if $need_exit;
105         } else {
106             print STDERR "error: Couldn't move $path to $path.work: $!\n";
107             exit 1;
108         }
109     }
110
111     open(FILE, "+< $path.work") or die "error: Couldn't open $path.work: $!\n";
112     setlock(\*FILE) or die "error: Couldn't lock $path.work: $!\n";
113
114     while (<FILE>) {
115         chomp(my $query = $_);
116         until ($dbinfo->{handle}->do($query)) {
117             print $dbinfo->{handle}->errstr."\n";
118             if ($dbinfo->{handle}->ping) {
119                 sleep (1);
120             } else {
121                 print "error: Lost connection to database\n";
122                 $dbinfo->{handle}->disconnect;
123                 connect_wait($dbinfo);
124             }
125         }
126     }
127
128     unlink($path.'.work');
129     close(FILE); # and unlock #
130 }
131
132 # sub main()
133
134 my %args = (
135             d => 'mysql',
136             b => 'radius',
137             h => 'localhost',
138             u => 'radius',
139             p => 'radius',
140 );
141 my $ret = getopts("d:b:fh:u:p:x1?", \%args);
142 if (!$ret or @ARGV != 1) {
143     usage();
144     exit 1;
145 }
146 if ($args{'?'}) {
147     usage();
148     exit 0;
149 }
150
151 my $data_source;
152 if ($args{d} eq 'mysql') {
153     $data_source = "DBI:mysql:database=$args{b};host=$args{h}";
154 } elsif ($args{d} eq 'pg') {
155     $data_source = "DBI:Pg:dbname=$args{b};host=$args{h}";
156 } else {
157     print STDERR "error: SQL driver not supported yet: $args{d}\n";
158     exit 1;
159 }
160
161 $SIG{INT} = \&got_signal;
162 $SIG{TERM} = \&got_signal;
163
164 my %dbinfo = (
165               base => $data_source,
166               user => $args{u},
167               pass => $args{p},
168 );
169 connect_wait(\%dbinfo);
170
171 my $path = shift @ARGV;
172
173 if (-e $path.'.work') {
174     process_file(\%dbinfo, $path.'.work');
175 }
176
177 until ($need_exit) {
178     process_file(\%dbinfo, $path);
179     last if ($args{1} || $need_exit);
180     sleep(10);
181 }
182
183 $dbinfo{handle}->disconnect;