New build path variable
[freeradius.git] / src / billing / clarent2db.pl
1 #!/usr/bin/perl
2 #
3 # syslog2db - Extract Clarent VoIP CDRs from billing_record files and
4 # insert them into a Postgresql database.
5 #
6 # Author:       Peter Nixon <codemonkey@peternixon.net>
7 # Date:         2003-05-07
8 # Summary:      Clarent, VoIP, CDR, database, postgresql
9 # Copyright:    2002, Peter Nixon <codemonkey@peternixon.net>
10 # Copy Policy:  Free to copy and distribute provided all headers are left
11 #               intact and no charge is made for this program.  I would
12 #               appreciate copies of any modifications to the script.
13 # URL:          http://www.peternixon.net/code/
14 #
15 # $Id$
16
17
18 # Modules we use to make things easier
19 use POSIX;
20 require DBI;
21 require Getopt::Long;
22 use Carp;
23 #use Symbol;
24 #use Time::Local;
25 #use strict;    # Errrm. That looks like effort :-)
26
27
28 # Program and File locations
29 # gzcat - 'cat for .gz / gzip files'
30 # If you don't have gzcat and do have gzip then use: ln gzip gzcat
31 my $GZCAT = "/usr/bin/zcat";
32 # zcat - 'cat for .Z / compressed files'
33 my $ZCAT = "/usr/bin/zcat";
34 # bzcat - 'cat for .bz2 files'
35 my $BZCAT = "/usr/bin/bzcat";
36
37 #### You should not have to modify anything below here
38
39 $| = 1;         #Unbuffered output
40 my $progname = "clarent2db.pl";
41 my $progname_long = "Clarent Billing Record to DB Importer";
42 my $version = 0.2;
43
44 # Set up some basic variables
45 my $double_match_no = 0; my $verbose = 0; my $recordno = 0; my $fileno = 0; my $lineno = 0;
46 my $starttime = time();
47
48
49 # Database Information
50 my $database    = "clarent";
51 my $defaulthostname    = "localhost";
52 my $port        = "3306";
53 my $user        = "postgres";
54 my $password    = "";
55
56 # Defaults
57 my $defaulttimezone = "UTC";
58 my $defaultyear = 2003;
59 my $dbh;
60
61 my %working_record = ();
62
63 my %months_map = (
64     'Jan' => '01', 'Feb' => '02', 'Mar' => '03',
65     'Apr' => '04', 'May' => '05', 'Jun' => '06',
66     'Jul' => '07', 'Aug' => '08', 'Sep' => '09',
67     'Oct' => '10', 'Nov' => '11', 'Dec' => '12',
68     'jan' => '01', 'feb' => '02', 'mar' => '03',
69     'apr' => '04', 'may' => '05', 'jun' => '06',
70     'jul' => '07', 'aug' => '08', 'sep' => '09',
71     'oct' => '10', 'nov' => '11', 'dec' => '12',
72 );
73
74 sub db_connect {
75         my $hostname = shift;
76         if ($verbose > 1) { print "DEBUG: Connecting to Database Server: $hostname\n" }
77         if ($hostname eq 'localhost') {
78         if ($verbose > 1) { print "DEBUG: localhost connection so using UNIX socket instead of network socket.\n" }
79                 $dbh = DBI->connect("DBI:Pg:dbname=$database", "$user", "$password")
80                         or die "Couldn't connect to database: " . DBI->errstr;
81         }
82         else {
83                 $dbh = DBI->connect("DBI:Pg:dbname=$database;host=$hostname", "$user", "$password")
84                         or die "Couldn't connect to database: " . DBI->errstr;
85         }
86 }
87
88 sub db_disconnect {
89         ### Now, disconnect from the database
90         if ($verbose > 1) { print "DEBUG: Disconnecting from Database Server\n" }
91         $dbh->disconnect
92             or warn "Disconnection failed: $DBI::errstr\n";
93 }
94
95 sub db_read {
96         $passno++;
97         if ($verbose > 0) { print "Record: $passno) Conf ID: $working_record{h323confid}   Start Time: $working_record{start_time} IP: $working_record{ip_addr_egress} Call Length: $working_record{duration}\n"; }
98         my $sth = $dbh->prepare("SELECT ID FROM billing_record
99                 WHERE start_time = ?
100                 AND ip_addr_ingress = ?
101                 AND h323confid = ?")
102                 or die "Couldn't prepare statement: " . $dbh->errstr;
103
104           my @data;
105           $sth->execute($working_record{start_time}, $working_record{ip_addr_ingress}, $working_record{h323confid})             # Execute the query
106             or die "Couldn't execute statement: " . $sth->errstr;
107            my $returned_rows = $sth->rows;
108
109           if ($sth->rows == 0) {
110                 &db_insert;
111           } elsif ($sth->rows == 1) {
112                 if ($verbose > 0) { print "Exists in DB.\n"; }
113           } else {
114                 $double_match_no++;
115                 # FIXME: Log this somewhere!
116                 print "********* More than One Match! We have a problem!\n";
117           }
118
119         $sth->finish;
120
121 }
122
123 sub db_insert {
124         $sth2 = $dbh->prepare("INSERT into billing_record (local_SetupTime, start_time, duration, service_code, phone_number,
125                 ip_addr_ingress, ip_addr_egress, bill_type, disconnect_reason, extended_reason_code, dialed_number, codec, h323ConfID, port_number)
126                 values(?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
127
128          $sth2->execute($working_record{local_setuptime}, $working_record{start_time}, $working_record{duration},
129                 $working_record{service_code}, $working_record{phone_number}, $working_record{ip_addr_ingress}, $working_record{ip_addr_egress},
130                 $working_record{bill_type}, $working_record{disconnect_reason}, $working_record{extended_reason_code}, $working_record{dialed_number},
131                 $working_record{codec}, $working_record{h323confid}, $working_record{port_number});
132         #my $returned_rows = $sth2->rows;
133         if ($verbose > 0) { print "$sth2->rows rows added to DB\n"; }
134         $sth2->finish();
135
136 }
137
138 sub file_read {
139         my $filename = shift;
140         if ($verbose > 1) { print "DEBUG: Reading detail file: $filename\n" }
141         if ( $filename =~ /.gz$/ ) {
142                 open (FILE, "$GZCAT $filename |") || warn "read_detailfile(\"$filename\"): $!\n";
143         } elsif ( $filename =~ /.Z$/ ) {
144                 open (FILE, "$ZCAT $filename |") || warn "read_detailfile(\"$filename\"): $!\n";
145         } elsif ( $filename =~ /.bz2$/ ) {
146                 open (FILE, "$BZCAT $filename |") || warn "read_detailfile(\"$filename\"): $!\n";
147         } else {
148                 open (FILE, "<$filename") || warn "read_detailfile(\"$filename\"): $!\n";
149         }
150         $valid_input = (eof(FILE) ? 0 : 1);
151         if ($verbose > 1) { print "DEBUG: Starting to read records from $filename\n"; }
152         while($valid_input) {
153                 $valid_input = 0 if (eof(FILE));
154                 if ($verbose > 2) { print "DEBUG: Reading Record\n"; }
155                 $_ = <FILE>;
156                 $lineno++;
157                 if ($verbose > 1) { print "DEBUG Raw Record: $_"; }
158                 #&record_mangle($_);
159                 &record_match($_);
160         }
161 }
162
163 sub record_match($) {
164         chomp($_);
165
166         # Spilt the Call record up into fields
167         my @callrecord = split(/,/, $_);
168
169         if (scalar(@callrecord) == 70) {        # Check that we have the right number of fields for a Clarent record
170                 if ($verbose > 1) { print "DEBUG: Clean Record: @callrecord\n"; }
171                 $recordno++; %working_record = ();
172                 $working_record{local_setuptime} = clarent2normaltime($callrecord[0]);
173                 $working_record{start_time} = $callrecord[3];   # This is in Unix timetamp format, relative to the originating gateway.
174                                                                 # It is therefore useless unless ALL gateways are set with the same timezone,
175                                                                 # so I don't bother to convert it to datetime format.
176                 $working_record{duration} = $callrecord[4];
177                 $working_record{service_code} = $callrecord[5];
178                 $working_record{phone_number} = $callrecord[6];
179                 $working_record{ip_addr_ingress} = $callrecord[7];
180                 $working_record{ip_addr_egress} = $callrecord[8];
181                 $working_record{h323confid} = $callrecord[9];
182                 $working_record{bill_type} = $callrecord[12];
183                 $working_record{disconnect_reason} = $callrecord[15];
184                 $working_record{extended_reason_code} = $callrecord[16];
185                 $working_record{port_number} = $callrecord[21];
186                 $working_record{dialed_number} = $callrecord[60];
187                 $working_record{codec} = $callrecord[67];
188
189                 &db_read;
190
191         } else { if ($verbose > 1) { print "DEBUG: ERROR: Record is not in Clarent format: $str\n"; } }
192
193
194 }
195
196 sub clarent2normaltime($) {
197         if ( /^
198             (\S{3})\/(\d+)\/(\d{4})     # Month Day Year
199             \s
200             (\d+):(\d+):(\d+)           # Hour Min Sec
201             \s
202             (\d{4})                     # msec??
203             \s
204             \w*?:?                      # RESEND: (Discarded)
205             \s?
206             \S{1}                       # U (Discarded) FIXME: does anyone know what this value means??
207             /x ) {
208                 my $month = $months_map{$1}; defined $month or croak "ERROR: Unknown month \"$1\"\n";
209                 my $days = $2;
210                 my $years = $3;
211                 my $hours = $4;
212                 my $minutes = $5;
213                 my $seconds = $6;
214                 return "$years-$month-$days $hours:$minutes:$seconds";
215         } else {
216             if ($verbose > 0) { print "ERROR: Not in Clarent time format: $str\n"; }
217         };
218
219 }
220
221 sub print_usage_info {
222         print "\n";
223         my $leader = "$progname_long Ver: $version Usage Information";
224         my $underbar = $leader;
225         $underbar =~ s/./-/g;
226         print "$leader\n$underbar\n";
227         print "\n";
228         print "  Syntax:   $progname [ options ] file\n";
229         print "\n";
230         print "    -h --help                        Show this usage information\n";
231         print "    -v --verbose                     Turn on verbose\n";
232         print "    -x --debug                       Turn on debugging\n";
233         print "    -V --version                     Show version and copyright\n";
234         print "    -H --host                        Database host to connect to (Default: localhost)\n";
235         print "\n";
236 }
237
238 sub main {
239         # Parse the command line for options
240         if (!scalar(@ARGV)) {
241                 &print_usage_info();
242                 exit(SUCCESS);
243         };
244         my $quiet = 0;
245
246         # See the Getopt::Long man page for details on the syntax of this line
247         @valid_opts = ("h|help", "V|version", "f|file=s", "x|debug", "v|verbose+" => \$verbose, "q|quiet+" => \$quiet, "D|date=s", "H|host=s", "p|procedure");
248         Getopt::Long::Configure("no_getopt_compat", "bundling", "no_ignore_case");
249         Getopt::Long::GetOptions(@valid_opts);
250
251         # Post-parse the options stuff
252         select STDOUT; $| = 1;
253         if ($opt_V) {
254                 # Do not edit this variable.  It is updated automatically by CVS when you commit
255                 my $rcs_info = 'CVS Revision $Revision$ created on $Date$ by $Author$ ';
256
257                 $rcs_info =~ s/\$\s*Revision: (\S+) \$/$1/;
258                 $rcs_info =~ s/\$\s*Date: (\S+) (\S+) \$/$1 at $2/;
259                 $rcs_info =~ s/\$\s*Author: (\S+) \$ /$1/;
260
261                 print "\n";
262                 print "$progname Version $version by Peter Nixon <codemonkey\@peternixon.net>\n";
263                 print "Copyright (c) 2003 Peter Nixon\n";
264                 print "  ($rcs_info)\n";
265                 print "\n";
266                 return SUCCESS;
267         } elsif ($opt_h) {
268                 &print_usage_info();
269                 exit(SUCCESS);
270         }
271
272         if ($opt_x) {
273                 print "DEBUG: Debug mode is enabled.\n";
274                 $verbose = 2;
275         } elsif ($quiet) { $verbose -= $quiet; }
276
277         if (@ARGV) {
278                 if ($opt_H) { &db_connect($opt_H);
279                 } else { &db_connect($defaulthostname); }
280
281                 if (scalar(@ARGV) > 1) {
282                         foreach $file (@ARGV) {                 # Loop through the defined files
283                                 $fileno++;
284                                 &file_read($file);
285                         }
286                 } else {
287                         $file = @ARGV[0];
288                         &file_read($file);
289                 }
290                 if ($verbose >= 0) {
291                         my $runtime = (time() - $starttime);
292                         if ($runtime < 1) { $runtime = 0.5; }           # Prevent divide-by-zero errors
293                         my $speed = ($recordno / $runtime);
294                         if ($fileno > 1) {
295                                 print "\n$recordno records from $lineno lines in $fileno files were processed in ~$runtime seconds (~$speed records/sec)\n";
296                         } else {
297                                 print "\n$recordno records from $lineno lines in $file were processed in ~$runtime seconds (~$speed records/sec)\n";
298                         }
299                 }
300
301                 &db_disconnect;
302         } else {
303                 print "ERROR: Please specify one or more detail files to import.\n";
304                 exit(FAILURE);
305         }
306 }
307
308 exit &main();