default to a session cookie instead of setting an explicit timeout, weird timezone...
[freeside.git] / FS / FS / Misc / FixIPFormat.pm
1 package FS::Misc::FixIPFormat;
2 use strict;
3 use warnings;
4 use FS::Record qw(dbh qsearchs);
5 use FS::upgrade_journal;
6
7 =head1 NAME
8
9 FS::Misc::FixIPFormat - Functions to repair bad IP address input
10
11 =head1 DESCRIPTION
12
13 Provides functions for freeside_upgrade to check IP address storage for
14 user-entered leading 0's in IP addresses.  When read from database, NetAddr::IP
15 would treat the number as octal isntead of decimal.  If a user entered
16 10.0.0.052, this may get invisibly translated to 10.0.0.42 when exported.
17 Base8:52 = Base0:42
18
19 Tied to freeside_upgrade with journal name TABLE__fixipformat
20
21 see: RT# 80555
22
23 =head1 SYNOPSIS
24
25 Usage:
26
27     # require, not use - this module is only run once
28     require FS::Misc::FixIPFormat;
29
30     my $error = FS::Misc::FixIPFormat::fix_bad_addresses_in_table(
31       'svc_broadband', 'svcnum', 'ip_addr'
32     );
33     die "oh no!" if $error;
34
35 =head2 fix_bad_addresses_in_table TABLE, ID_COLUMN, IP_COLUMN
36
37 $error = fix_bad_addresses_in_table( 'svc_broadband', 'svcnum', 'ip_addr' );
38
39 =cut
40
41 sub fix_bad_addresses_in_table {
42   my ( $table ) = @_;
43   return if FS::upgrade_journal->is_done("${table}__fixipformat");
44   for my $id ( find_bad_addresses_in_table( @_ )) {
45     if ( my $error = fix_ip_for_record( $id, @_ )) {
46       die "fix_bad_addresses_in_table(): $error";
47     }
48   }
49   FS::upgrade_journal->set_done("${table}__fixipformat");
50   0;
51 }
52
53 =head2 find_bad_addresses_in_table TABLE, ID_COLUMN, IP_COLUMN
54
55 @id = find_bad_addresses_in_table( 'svc_broadband', 'svcnum', 'ip_addr' );
56
57 =cut
58
59 sub find_bad_addresses_in_table {
60   my ( $table, $id_col, $ip_col ) = @_;
61   my @fix_ids;
62
63   # using DBI directly for performance
64   my $sql_statement = "
65     SELECT $id_col, $ip_col
66     FROM $table
67     WHERE $ip_col IS NOT NULL
68   ";
69   my $sth = dbh->prepare( $sql_statement ) || die "SQL ERROR ".dbh->errstr;
70   $sth->execute || die "SQL ERROR ".dbh->errstr;
71   while ( my $row = $sth->fetchrow_hashref ) {
72     push @fix_ids, $row->{ $id_col }
73       if $row->{ $ip_col } =~ /[\.^]0\d/;
74   }
75   @fix_ids;
76 }
77
78 =head2 fix_ip_for_record ID, TABLE, ID_COLUMN, IP_COLUMN
79
80 Attempt to strip the leading 0 from a stored IP address record.  If
81 the corrected IP address would be a duplicate of another record in the
82 same table, thow an exception.
83
84 $error = fix_ip_for_record( 1001, 'svc_broadband', 'svcnum', 'ip_addr', );
85
86 =cut
87
88 sub fix_ip_for_record {
89   my ( $id, $table, $id_col, $ip_col ) = @_;
90
91   my $row = qsearchs($table, {$id_col => $id})
92     || die "Error finding $table record for id $id";
93
94   my $ip = $row->getfield( $ip_col );
95   my $fixed_ip = join( '.',
96     map{ int($_) }
97     split( /\./, $ip )
98   );
99
100   return undef unless $ip ne $fixed_ip;
101
102   if ( my $dupe_row = qsearchs( $table, {$ip_col => $fixed_ip} )) {
103     if ( $dupe_row->getfield( $id_col ) != $row->getfield( $id_col )) {
104       # Another record in the table has this IP address
105       # Eg one ip is provisioned as 10.0.0.51 and another is
106       # provisioned as 10.0.0.051.  Cannot auto-correct by simply
107       # trimming leading 0.  Die, let support decide how to fix.
108
109       die "Invalid IP address could not be auto-corrected - ".
110           "($table - $id_col = $id, $ip_col = $ip) ".
111            "colission with another reocrd - ".
112            "($table - $id_col = ".$dupe_row->getfield( $id_col )." ".
113            "$ip_col = ",$dupe_row->getfield( $ip_col )." ) - ".
114          "The entry must be corrected to continue";
115     }
116   }
117
118   warn "Autocorrecting IP address problem for ".
119        "($table - $id_col = $id, $ip_col = $ip) $fixed_ip\n";
120   $row->setfield( $ip_col, $fixed_ip );
121   $row->replace;
122 }
123
124 1;