fix aradial usage import (Net::SFTP::Foreign and cachedir), RT#29053
[freeside.git] / bin / aradial-sftp_and_import
1 #!/usr/bin/perl
2
3 #i'm kinda like freeside-cdr-sftp_and_import... some parts should be libraried
4
5 use strict;
6 use Getopt::Std;
7 use Text::CSV_XS;
8 use DBI;
9 use Net::SFTP::Foreign;
10 #use FS::UID qw( adminsuidsetup datasrc );
11
12 our %aradial2db = (
13   #'Date' => '',
14   'NASIP' => 'NASIPAddress',
15   'AcctSessionId' => 'AcctSessionId',
16   'Port' => 'NasPortId',
17   #'Status-Type' => 'Acct-Status-Type',
18   'UserID' => 'UserName',
19   'Authentic' => 'AccdtAuthentic',
20   'Service-Type' => 'ServiceType',
21   'FramedProtocol' => 'FramedProtocol',
22   #'FramedCompression' => '', #not handled, needed?  unlikely
23   'FramedAddress' => 'FramedIPAddress',
24   'Acct-Delay-Time' => 'AcctStartDelay', #?
25   'Session-Time' => 'AcctSessionTime',
26   #'Input-Gigawords' => '',
27   'Input-Octets' => 'AcctInputOctets',
28   #'Output-Gigawords' => '',
29   'Output-Octets' => 'AcctOutputOctets',
30   'NAS-Port-Type' => 'NASPortType',
31   'Acct-Terminate-Cause' => 'AcctTerminateCause',
32 );
33
34 ###
35 # parse command line
36 ###
37
38 use vars qw( $opt_m $opt_a $opt_b $opt_r $opt_d $opt_v $opt_P );
39 getopts('m:abr:dP:v:');
40
41 my %options = ();
42
43 my $user = shift or die &usage;
44 #adminsuidsetup $user;
45
46 # %%%FREESIDE_CACHE%%% & hardcoded datasrc
47 #my $cachedir = '%%%FREESIDE_CACHE%%%/cache.'. datasrc. '/cdrs';
48 my $cachedir = '/usr/local/etc/freeside/cache.DBI:Pg:dbname=freeside/cdrs';
49 mkdir $cachedir unless -d $cachedir;
50
51 my $servername = shift or die &usage;
52
53 my( $datasrc, $db_user, $db_pass ) = ( shift, shift, shift );
54 my $dbh = DBI->connect( $datasrc, $db_user, $db_pass)
55   or die "can't connect: $DBI::errstr\n";
56
57 my $csv = Text::CSV_XS->new;
58
59 ###
60 # get the file list
61 ###
62
63 warn "Retrieving directory listing\n" if $opt_v;
64
65 $opt_m = 'sftp' if !defined($opt_m);
66 $opt_m = lc($opt_m);
67
68 my $ls;
69
70 if($opt_m eq 'ftp') {
71   $options{'Port'}    = $opt_P if $opt_P;
72   $options{'Debug'}   = $opt_v if $opt_v;
73   $options{'Passive'} = $opt_a if $opt_a;
74
75   my $ls_ftp = ftp();
76
77   $ls = [ grep { /^.*$/i } $ls_ftp->ls ];
78 }
79 elsif($opt_m eq 'sftp') {
80   $options{'port'}    = $opt_P if $opt_P;
81   $options{'debug'}   = $opt_v if $opt_v;
82
83   my $ls_sftp = sftp();
84
85   $ls_sftp->setcwd($opt_r) or die "can't chdir to $opt_r\n"
86     if $opt_r;
87
88   $ls = $ls_sftp->ls('.', wanted => qr/^$.*\.$/i,
89                           names_only => 1 );
90 }
91 else {
92   die "Method '$opt_m' not supported; must be ftp or sftp\n";
93 }
94
95 ###
96 # import each file
97 ###
98
99 foreach my $filename ( @$ls ) {
100
101   warn "Downloading $filename\n" if $opt_v;
102
103   #get the file
104   if($opt_m eq 'ftp') {
105     my $ftp = ftp();
106     $ftp->get($filename, "$cachedir/$filename")
107       or die "Can't get $filename: ". $ftp->message . "\n";
108   }
109   else {
110     my $sftp = sftp();
111     $sftp->get($filename, "$cachedir/$filename")
112       or die "Can't get $filename: ". $sftp->error . "\n";
113   }
114
115   warn "Processing $filename\n" if $opt_v;
116  
117   my $file_timestamp = $filename.'-'.time2str('%Y-%m-%d', time);
118
119   open my $fh, "$cachedir/$filename" or die "$cachedir/$filename: $!";
120   my $header = $csv->getline($fh);
121
122   while ( my $row = $csv->getline($fh) ) {
123
124     my $i = 0;
125     my %hash = map { $_ => $row->[$i++] } @$header;
126
127     my %dbhash = map { $aradial2db{$_} => $hash{$_} }
128                    grep $aradial2db{$_},
129                      keys %hash;
130
131     my @keys = keys %dbhash;
132     my @values = map $dbhash{$_}, @keys;
133
134     if ( $hash{'Acct-Status-Type'} eq 'Start' ) {
135
136       $dbhash{'AcctStartTime'} = $hash{'Date'};
137
138       my $sql = 'INSERT INTO radacct ( ', join(',', @keys).
139                 ' ) VALUES ( '. map( ' ? ', @values ). ' )';
140       my $sth = $dbh->prepare($sql) or die $dbh->errstr;
141       $sth->execute(@values) or die $sth->errstr;
142
143     } elsif ( $hash{'Acct-Status-Type'} eq 'Stop' ) {
144
145       my $AcctSessionId = delete($dbhash{AcctSessionId});
146       $dbhash{'AcctStopTime'} = $hash{'Date'};
147
148       my $sql = 'UPDATE radacct '. join(' , ', map "SET $_ = ?", @keys ).
149                 ' WHERE AcctSessionId = ? ';
150       my $sth = $dbh->prepare($sql) or die $dbh->errstr;
151       $sth->execute(@values, $AcctSessionId) or die $sth->errstr;
152
153     } elsif ( $hash{'Acct-Status-Type'} eq 'Interim' ) {
154       #not handled, but stop should capture the usage.  unless session are
155       # normally super-long, extending across month boundaries, or we need
156       # real-time-ish data usage detail, it isn't a big deal
157     } else {
158       die 'Unknown Acct-Status-Type '. $hash{'Acct-Status-Type'}. "\n";
159     }
160
161   }
162   
163   if ( $opt_d ) {
164     if ( $opt_m eq 'ftp') {
165       my $ftp = ftp();
166       $ftp->rename($filename, "$opt_d/$file_timestamp")
167         or do {
168           unlink "$cachedir/$filename";
169           die "Can't move $filename to $opt_d: ".$ftp->message . "\n";
170         };
171     } else {
172       my $sftp = sftp();
173       $sftp->rename($filename, "$opt_d/$file_timestamp")
174         or do {
175           unlink "$cachedir/$filename";
176           die "can't move $filename to $opt_d: ". $sftp->error . "\n";
177         };
178     }
179   }
180
181   unlink "$cachedir/$filename";
182
183 }
184
185 ###
186 # subs
187 ###
188
189 sub usage {
190   "Usage:
191   aradial-sftp_and_import [ -m method ] [ -a ] [ -b ]
192     [ -r remotefolder ] [ -d donefolder ] [ -v level ] [ -P port ]
193     user [sftpuser@]servername dbi_datasrc dbi_username dbi_pass
194   ";
195 }
196
197 use vars qw( $sftp $ftp );
198
199 sub ftp {
200   return $ftp if $ftp && $ftp->pwd;
201   
202   my ($hostname, $userpass) = reverse split('@', $servername);
203   my ($ftp_user, $ftp_pass) = split(':', $userpass);
204
205   my $ftp = Net::FTP->new($hostname, %options) 
206     or die "FTP connection to '$hostname' failed.";
207   $ftp->login($ftp_user, $ftp_pass) or die "FTP login failed: ".$ftp->message;
208   $ftp->cwd($opt_r) or die "can't chdir to $opt_r\n" if $opt_r;
209   $ftp->binary or die "can't set BINARY mode: ". $ftp->message if $opt_b;
210   return $ftp;
211 }
212
213 sub sftp {
214
215   #reuse connections
216   return $sftp if $sftp && $sftp->cwd;
217
218   my %sftp = ( host => $servername );
219
220   $sftp = Net::SFTP::Foreign->new(%sftp);
221   $sftp->error and die "SFTP connection failed: ". $sftp->error;
222
223   $sftp;
224 }
225
226 =head1 NAME
227
228 freeside-aradial-sftp_and_import - Download Aradial "CDR" (really RADIUS detail) files from a remote server via SFTP
229
230 =head1 SYNOPSIS
231
232   aradial-sftp_and_import [ -m method ] [ -a ] [ -b ]
233     [ -r remotefolder ] [ -d donefolder ] [ -v level ] [ -P port ]
234     user [sftpuser@]servername dbi_datasrc dbi_username dbi_pass
235
236 =head1 DESCRIPTION
237
238 Command line tool to download CDR files from a remote server via SFTP 
239 or FTP and then import them into the database.
240
241 -m: transfer method (sftp or ftp), defaults to sftp
242
243 -a: use ftp passive mode
244
245 -b: use ftp binary mode
246
247 -r: if specified, changes into this remote folder before starting
248
249 -d: if specified, moves files to the specified folder when done
250
251 -P: if specified, sets the port to use
252
253 -v: set verbosity level; this script only has one level, but it will 
254     be passed as the 'debug' argument to the transport method
255
256 user: freeside username
257
258 [sftpuser@]servername: remote server
259 (or ftpuser:ftppass@servername)
260
261 =head1 BUGS
262
263 =head1 SEE ALSO
264
265 L<FS::cdr>
266
267 =cut
268
269 1;
270