package FS::part_export::huawei_hlr; use vars qw(@ISA %info $DEBUG $CACHE); use Tie::IxHash; use FS::Record qw(qsearch qsearchs dbh); use FS::part_export; use FS::svc_phone; use FS::inventory_class; use FS::inventory_item; use IO::Socket::INET; use Data::Dumper; use strict; $DEBUG = 0; @ISA = qw(FS::part_export); tie my %options, 'Tie::IxHash', 'opname' => { label=>'Operator login (required)' }, 'pwd' => { label=>'Operator password (required)' }, 'tplid' => { label=>'Template number' }, 'hlrsn' => { label=>'HLR serial number' }, 'k4sno' => { label=>'K4 serial number' }, 'cardtype' => { label => 'Card type (required)', type => 'select', options=> ['SIM', 'USIM'] }, 'alg' => { label => 'Authentication algorithm (required)', type => 'select', options=> ['COMP128_1', 'COMP128_2', 'COMP128_3', 'MILENAGE' ], }, 'opcvalue' => { label=>'OPC value (for MILENAGE only)' }, 'opsno' => { label=>'OP serial number (for MILENAGE only)' }, 'timeout' => { label=>'Timeout (seconds)', default => 120 }, 'debug' => { label=>'Enable debugging', type=>'checkbox' }, ; %info = ( 'svc' => 'svc_phone', 'desc' => 'Provision mobile phone service to Huawei HLR9820', 'options' => \%options, 'notes' => <<'END' Connects to a Huawei Subscriber Management Unit via TCP and configures mobile phone services according to a template. The sim_imsi field must be set on the service, and the template must exist. END ); sub actions { 'Import SIMs' => 'misc/part_export/huawei_hlr-import_sim.html' } sub _export_insert { my( $self, $svc_phone ) = (shift, shift); # svc_phone::check should ensure phonenum and sim_imsi are numeric my @command = ( IMSI => '"'.$svc_phone->sim_imsi.'"', ISDN => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"', TPLID => $self->option('tplid'), ); unshift @command, 'HLRSN', $self->option('hlrsn') if $self->option('hlrsn'); unshift @command, 'ADD TPLSUB'; my $err_or_queue = $self->queue_command($svc_phone->svcnum, @command); ref($err_or_queue) ? '' : $err_or_queue; } sub _export_replace { my( $self, $new, $old ) = @_; my $depend_jobnum; if ( $new->sim_imsi ne $old->sim_imsi ) { my @command = ( 'MOD IMSI', ISDN => '"'.$old->countrycode.$old->phonenum.'"', IMSI => '"'.$old->sim_imsi.'"', NEWIMSI => '"'.$new->sim_imsi.'"', ); my $err_or_queue = $self->queue_command($new->svcnum, @command); return $err_or_queue unless ref $err_or_queue; $depend_jobnum = $err_or_queue->jobnum; } if ( $new->countrycode ne $old->countrycode or $new->phonenum ne $old->phonenum ) { my @command = ( 'MOD ISDN', ISDN => '"'.$old->countrycode.$old->phonenum.'"', NEWISDN => '"'.$new->countrycode.$new->phonenum.'"', ); my $err_or_queue = $self->queue_command($new->svcnum, @command); return $err_or_queue unless ref $err_or_queue; if ( $depend_jobnum ) { my $error = $err_or_queue->depend_insert($depend_jobnum); return $error if $error; } } # no other svc_phone changes need to be exported ''; } sub _export_suspend { my( $self, $svc_phone ) = (shift, shift); $self->_export_lock($svc_phone, 'TRUE'); } sub _export_unsuspend { my( $self, $svc_phone ) = (shift, shift); $self->_export_lock($svc_phone, 'FALSE'); } sub _export_lock { my ($self, $svc_phone, $lockstate) = @_; # XXX I'm not sure this actually suspends. Need to test it. my @command = ( 'MOD LCK', IMSI => '"'.$svc_phone->sim_imsi.'"', ISDN => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"', IC => $lockstate, OC => $lockstate, GPRSLOCK=> $lockstate, ); my $err_or_queue = $self->queue_command($svc_phone->svcnum, @command); ref($err_or_queue) ? '' : $err_or_queue; } sub _export_delete { my( $self, $svc_phone ) = (shift, shift); my @command = ( 'RMV SUB', #IMSI => '"'.$svc_phone->sim_imsi.'"', ISDN => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"', ); my $err_or_queue = $self->queue_command($svc_phone->svcnum, @command); ref($err_or_queue) ? '' : $err_or_queue; } sub queue_command { my ($self, $svcnum, @command) = @_; my $queue = FS::queue->new({ svcnum => $svcnum, job => 'FS::part_export::huawei_hlr::run_command', }); $queue->insert($self->exportnum, @command) || $queue; } sub run_command { my ($exportnum, @command) = @_; my $self = FS::part_export->by_key($exportnum); my $socket = $self->login; my $result = $self->command($socket, @command); $self->logout($socket); $socket->close; die $result->{error} if $result->{error}; ''; } sub login { my $self = shift; local $DEBUG = $self->option('debug') || 0; # Send a command to the SMU. # The caller is responsible for quoting string parameters. my %socket_param = ( PeerAddr => $self->machine, PeerPort => 7777, Proto => 'tcp', Timeout => ($self->option('timeout') || 30), ); warn "Connecting to ".$self->machine."...\n" if $DEBUG; warn Dumper(\%socket_param) if $DEBUG; my $socket = IO::Socket::INET->new(%socket_param) or die "Failed to connect: $!\n"; warn 'Logging in as "'.$self->option('opname').".\"\n" if $DEBUG; my @login_param = ( OPNAME => '"'.$self->option('opname').'"', PWD => '"'.$self->option('pwd').'"', ); if ($self->option('HLRSN')) { unshift @login_param, 'HLRSN', $self->option('HLRSN'); } my $login_result = $self->command($socket, 'LGI', @login_param); die $login_result->{error} if $login_result->{error}; return $socket; } sub logout { warn "Logging out.\n" if $DEBUG; my $self = shift; my ($socket) = @_; $self->command($socket, 'LGO'); $socket->close; } sub command { my $self = shift; my ($socket, $command, @param) = @_; my $string = $command . ':'; while (@param) { $string .= shift(@param) . '=' . shift(@param); $string .= ',' if @param; } $string .= "\n;"; my @result; eval { # timeout local $SIG{ALRM} = sub { die "timeout\n" }; alarm ($self->option('timeout') || 120); warn "Sending to server:\n$string\n\n" if $DEBUG; $socket->print($string); warn "Received:\n"; my $line; local $/ = "\r\n"; do { $line = $socket->getline(); warn $line if $DEBUG; chomp $line; push @result, $line if length($line); } until ( $line =~ /^---\s*END$/ or $socket->eof ); alarm 0; }; my %return; if ( $@ eq "timeout\n" ) { return { error => 'request timed out' }; } elsif ( $@ ) { return { error => $@ }; } else { #+++ HLR9820