5 use FS::Daemon ':all'; #daemonize1 drop_root daemonize2 myexit logfile sig*
6 use FS::UID qw( adminsuidsetup );
7 use FS::Record qw( qsearch qsearchs );
12 my $user = shift or die &usage;
14 #daemonize1('freeside-sprepaidd', $user); #keep unique pid files w/multi installs
15 daemonize1('freeside-cdrrewrited');
19 adminsuidsetup($user);
21 logfile( "%%%FREESIDE_LOG%%%/cdrrewrited-log.". $FS::UID::datasrc );
27 die "not running; cdr-asterisk_forward_rewrite, cdr-charged_party_rewrite ".
28 " and cdr-taqua-accountcode_rewrite conf options are all off\n"
33 my %sessionnum_unmatch = ();
34 my $sessionnum_retry = 4 * 60 * 60; # 4 hours
35 my $sessionnum_giveup = 4 * 24 * 60 * 60; # 4 days
37 my %cdr_type = map { lc($_->cdrtypename) => $_->cdrtypenum }
38 qsearch('cdr_type',{});
42 #hmm... don't want to do an expensive search with an ever-growing bunch
43 # of unprocessed CDRs during the month... better to mark them all as
44 # rewritten "skipped", i.e. why we're a daemon in the first place
45 # instead of just doing this search like normal CDRs
48 my @recent = grep { ($sessionnum_unmatch{$_} + $sessionnum_retry) > time }
49 keys %sessionnum_unmatch;
50 my $extra_sql = scalar(@recent)
51 ? ' AND acctid NOT IN ('. join(',', @recent). ') '
61 'extra_sql' => 'FOR UPDATE',
63 'extra_sql' => 'WHERE freesidestatus IS NULL '.
64 ' AND freesiderewritestatus IS NULL '.
66 ' LIMIT 1024', #arbitrary, but don't eat too much memory
70 next if $skip{$cdr->acctid};
75 if ( $conf->exists('cdr-asterisk_forward_rewrite')
76 && $cdr->dstchannel =~ /^Local\/(\d+)/i && $1 ne $cdr->dst
82 warn "dst ". $cdr->dst. " does not match dstchannel $dst ".
83 "(". $cdr->dstchannel. "); rewriting CDR as a forwarded call";
85 $cdr->charged_party($cdr->dst);
89 push @status, 'asterisk_forward';
93 # XXX weird special case stuff--can we modularize this somehow?
95 if ( $conf->exists('cdr-asterisk_australia_rewrite') and
96 $cdr->disposition eq 'ANSWERED' ) {
99 if ( $dst =~ /^0?(12|13|1800|1900|0055)/ ) {
100 # toll free or smart numbers, any length
102 $cdr->charged_party($dst);
104 elsif ( $dst =~ /^(11|0011)/ ) {
105 # will be followed by country code
106 $type = 'international';
107 $dst =~ s/^$1/0011/; #standardize
110 elsif ( length($dst) == 10 and$dst =~ /^04/ ) {
113 elsif ( length($dst) == 10 and $dst =~ /^02|03|07|08/ ) {
116 elsif ( length($dst) == 8 ) {
117 # local call, no area code
123 if ( $type and exists($cdr_type{$type}) ) {
124 $cdr->cdrtypenum($cdr_type{$type});
125 push @status, 'asterisk_australia';
128 $warning{"no CDR type defined for $type calls"}++;
132 if ( $conf->exists('cdr-charged_party_rewrite') && ! $cdr->charged_party ) {
134 $cdr->set_charged_party;
135 push @status, 'charged_party';
139 if ( $cdr->cdrtypenum == 1
142 $conf->exists('cdr-taqua-accountcode_rewrite') or
143 $conf->exists('cdr-taqua-callerid_rewrite') )
147 #find the matching CDR
148 my %search = ( 'sessionnum' => $cdr->sessionnum );
149 if ( $cdr->lastapp eq 'acctcode' ) {
150 $search{'src'} = $cdr->subscriber;
151 } elsif ( $cdr->lastapp eq 'CallerId' ) {
152 $search{'dst'} = $cdr->subscriber;
154 my $primary = qsearchs('cdr', \%search);
156 unless ( $primary ) {
158 my $cantfind = "can't find primary CDR with session ". $cdr->sessionnum.
159 ", src ". $cdr->subscriber;
160 if ( $cdr->calldate_unix + $sessionnum_giveup < time ) {
161 warn "ERROR: $cantfind; giving up\n";
162 push @status, 'taqua-sessionnum-NOTFOUND';
163 $cdr->status('done'); #so it doesn't try to rate
164 delete $sessionnum_unmatch{$cdr->acctid}; #so it doesn't suck mem
166 warn "WARNING: $cantfind; will keep trying\n";
167 $sessionnum_unmatch{$cdr->acctid} = time;
173 if ( $cdr->lastapp eq 'acctcode' ) {
174 # lastdata contains the dialed account code
175 $primary->accountcode( $cdr->lastdata );
176 push @status, 'taqua-accountcode';
177 } elsif ( $cdr->lastapp eq 'CallerId' ) {
178 # lastdata contains "allowed" or "restricted"
179 # or case variants thereof
180 if ( lc($cdr->lastdata) eq 'restricted' ) {
181 $primary->clid( 'PRIVATE' );
183 push @status, 'taqua-callerid';
185 warn "unknown Taqua service name: ".$cdr->lastapp."\n";
187 #$primary->freesiderewritestatus( 'taqua-accountcode-primary' );
188 my $error = $primary->replace if $primary->modified;
190 warn "WARNING: error rewriting primary CDR (will retry): $error\n";
193 $skip{$primary->acctid} = 1;
195 $cdr->status('done'); #so it doesn't try to rate
201 if ( $conf->exists('cdr-userfield_dnis_rewrite') and
202 $cdr->userfield =~ /DNIS=(\d+)/ ) {
204 push @status, 'userfield_dnis';
207 if ( $conf->exists('cdr-intl_to_domestic_rewrite') and
208 $cdr->dst =~ /^(011)(\d{0,7})$/ ) {
210 push @status, 'intl_to_domestic';
213 $cdr->freesiderewritestatus(
214 scalar(@status) ? join('/', @status) : 'skipped'
217 my $error = $cdr->replace;
220 warn "WARNING: error rewriting CDR (will retry in 30 seconds):".
222 sleep 30; #i dunno, wait and see if the database comes back?
225 last if sigterm() || sigint();
229 foreach (sort keys %warning) {
230 warn "WARNING: $_ (x $warning{$_})\n";
234 myexit() if sigterm() || sigint();
235 #sleep 1 unless $found;
236 sleep 5 unless $found;
243 $conf->exists('cdr-asterisk_forward_rewrite')
244 || $conf->exists('cdr-asterisk_australia_rewrite')
245 || $conf->exists('cdr-charged_party_rewrite')
246 || $conf->exists('cdr-taqua-accountcode_rewrite')
247 || $conf->exists('cdr-taqua-callerid_rewrite')
248 || $conf->exists('cdr-intl_to_domestic_rewrite')
249 || $conf->exists('cdr-userfield_dnis_rewrite')
255 die "Usage:\n\n freeside-cdrrewrited user\n";
260 freeside-cdrrewrited - Real-time daemon for CDR rewriting
268 Runs continuously, searches for CDRs and does forwarded-call rewriting if any
269 of the following config options are enabled:
273 =item cdr-asterisk_australia_rewrite
275 Classifies Australian numbers as domestic, mobile, tollfree, international, or
276 "other", and tries to assign a cdrtypenum based on that.
278 =item cdr-asterisk_forward_rewrite
280 Identifies Asterisk forwarded calls using the 'dstchannel' field. If the
281 dstchannel is "Local/" followed by a number, but the number doesn't match the
282 dst field, the dst field will be rewritten to match.
284 =item cdr-charged_party_rewrite
286 Calls set_charged_party on all calls.
288 =item cdr-taqua-accountcode_rewrite
290 =item cdr-taqua-callerid_rewrite
292 These actually have the same effect. Taqua uses cdrtypenum = 1 to tag accessory
293 records. They will have "sessionnum" = that of the primary record, and
294 "lastapp" indicating their function:
296 - "acctcode": "lastdata" contains the dialed account code. Insert this into the
297 accountcode field of the primary record.
299 - "CallerId": "lastdata" contains "allowed" or "restricted". If "restricted"
300 then the clid field of the primary record is set to "PRIVATE".
302 =item cdr-intl_to_domestic_rewrite
304 Finds records where the destination number has the "011" international prefix,
305 but with seven or fewer digits in the rest of the number, and strips the "011"
306 prefix so that they will be treated as domestic calls. This is very uncommon.