1 package FS::part_export::huawei_hlr;
3 use vars qw(@ISA %info $DEBUG $CACHE);
5 use FS::Record qw(qsearch qsearchs dbh);
8 use FS::inventory_class;
9 use FS::inventory_item;
12 use MIME::Base64 qw(decode_base64);
13 use Storable qw(thaw);
18 @ISA = qw(FS::part_export);
20 tie my %options, 'Tie::IxHash',
21 'opname' => { label=>'Operator login (required)' },
22 'pwd' => { label=>'Operator password (required)' },
23 'tplid' => { label=>'Template number' },
24 'hlrsn' => { label=>'HLR serial number' },
25 'k4sno' => { label=>'K4 serial number' },
26 'cardtype' => { label => 'Card type (required)',
28 options=> ['SIM', 'USIM']
30 'alg' => { label => 'Authentication algorithm (required)',
32 options=> ['COMP128_1',
37 'opcvalue' => { label=>'OPC value (for MILENAGE only)' },
38 'opsno' => { label=>'OP serial number (for MILENAGE only)' },
39 'timeout' => { label=>'Timeout (seconds)', default => 120 },
40 'debug' => { label=>'Enable debugging', type=>'checkbox' },
45 'desc' => 'Provision mobile phone service to Huawei HLR9820',
46 'options' => \%options,
48 Connects to a Huawei Subscriber Management Unit via TCP and configures mobile
49 phone services according to a template. The <i>sim_imsi</i> field must be
50 set on the service, and the template must exist.
55 'Import SIMs' => 'misc/part_export/huawei_hlr-import_sim.html'
59 my( $self, $svc_phone ) = (shift, shift);
60 # svc_phone::check should ensure phonenum and sim_imsi are numeric
62 IMSI => '"'.$svc_phone->sim_imsi.'"',
63 ISDN => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"',
64 TPLID => $self->option('tplid'),
66 unshift @command, 'HLRSN', $self->option('hlrsn')
67 if $self->option('hlrsn');
68 unshift @command, 'ADD TPLSUB';
69 my $err_or_queue = $self->queue_command($svc_phone->svcnum, @command);
70 ref($err_or_queue) ? '' : $err_or_queue;
74 my( $self, $new, $old ) = @_;
76 if ( $new->sim_imsi ne $old->sim_imsi ) {
79 ISDN => '"'.$old->countrycode.$old->phonenum.'"',
80 IMSI => '"'.$old->sim_imsi.'"',
81 NEWIMSI => '"'.$new->sim_imsi.'"',
83 my $err_or_queue = $self->queue_command($new->svcnum, @command);
84 return $err_or_queue unless ref $err_or_queue;
85 $depend_jobnum = $err_or_queue->jobnum;
87 if ( $new->countrycode ne $old->countrycode or
88 $new->phonenum ne $old->phonenum ) {
91 ISDN => '"'.$old->countrycode.$old->phonenum.'"',
92 NEWISDN => '"'.$new->countrycode.$new->phonenum.'"',
94 my $err_or_queue = $self->queue_command($new->svcnum, @command);
95 return $err_or_queue unless ref $err_or_queue;
96 if ( $depend_jobnum ) {
97 my $error = $err_or_queue->depend_insert($depend_jobnum);
98 return $error if $error;
101 # no other svc_phone changes need to be exported
105 sub _export_suspend {
106 my( $self, $svc_phone ) = (shift, shift);
107 $self->_export_lock($svc_phone, 'TRUE');
110 sub _export_unsuspend {
111 my( $self, $svc_phone ) = (shift, shift);
112 $self->_export_lock($svc_phone, 'FALSE');
116 my ($self, $svc_phone, $lockstate) = @_;
117 # XXX I'm not sure this actually suspends. Need to test it.
120 IMSI => '"'.$svc_phone->sim_imsi.'"',
121 ISDN => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"',
124 GPRSLOCK=> $lockstate,
126 my $err_or_queue = $self->queue_command($svc_phone->svcnum, @command);
127 ref($err_or_queue) ? '' : $err_or_queue;
131 my( $self, $svc_phone ) = (shift, shift);
134 #IMSI => '"'.$svc_phone->sim_imsi.'"',
135 ISDN => '"'.$svc_phone->countrycode.$svc_phone->phonenum.'"',
137 my $err_or_queue = $self->queue_command($svc_phone->svcnum, @command);
138 ref($err_or_queue) ? '' : $err_or_queue;
142 my ($self, $svcnum, @command) = @_;
143 my $queue = FS::queue->new({
145 job => 'FS::part_export::huawei_hlr::run_command',
147 $queue->insert($self->exportnum, @command) || $queue;
151 my ($exportnum, @command) = @_;
152 my $self = FS::part_export->by_key($exportnum);
153 my $socket = $self->login;
154 my $result = $self->command($socket, @command);
155 $self->logout($socket);
157 die $result->{error} if $result->{error};
163 local $DEBUG = $self->option('debug') || 0;
164 # Send a command to the SMU.
165 # The caller is responsible for quoting string parameters.
167 PeerAddr => $self->machine,
170 Timeout => ($self->option('timeout') || 30),
172 warn "Connecting to ".$self->machine."...\n" if $DEBUG;
173 warn Dumper(\%socket_param) if $DEBUG;
174 my $socket = IO::Socket::INET->new(%socket_param)
175 or die "Failed to connect: $!\n";
177 warn 'Logging in as "'.$self->option('opname').".\"\n" if $DEBUG;
179 OPNAME => '"'.$self->option('opname').'"',
180 PWD => '"'.$self->option('pwd').'"',
182 if ($self->option('HLRSN')) {
183 unshift @login_param, 'HLRSN', $self->option('HLRSN');
185 my $login_result = $self->command($socket, 'LGI', @login_param);
186 die $login_result->{error} if $login_result->{error};
191 warn "Logging out.\n" if $DEBUG;
194 $self->command($socket, 'LGO');
200 my ($socket, $command, @param) = @_;
201 my $string = $command . ':';
203 $string .= shift(@param) . '=' . shift(@param);
204 $string .= ',' if @param;
209 local $SIG{ALRM} = sub { die "timeout\n" };
210 alarm ($self->option('timeout') || 120);
211 warn "Sending to server:\n$string\n\n" if $DEBUG;
212 $socket->print($string);
217 $line = $socket->getline();
218 warn $line if $DEBUG;
220 push @result, $line if length($line);
221 } until ( $line =~ /^---\s*END$/ or $socket->eof );
225 if ( $@ eq "timeout\n" ) {
226 return { error => 'request timed out' };
228 return { error => $@ };
230 #+++ HLR9820 <date> <time>\n
231 my $header = shift(@result);
232 $header =~ /(\+\+\+.*)/
233 or return { error => 'malformed response: '.$header };
234 $return{header} = $1;
235 #SMU #<serial number>\n
236 $return{smu} = shift(@result);
237 #%%<command string>%%\n
238 $return{echo} = shift(@result); # should match the input
239 #<message code>: <message description>\n
240 my $message = shift(@result);
241 if ($message =~ /^SUCCESS/) {
242 $return{success} = $message;
244 $return{error} = $message;
246 $return{trailer} = pop(@result);
247 $return{details} = join("\n",@result,'');
252 sub process_import_sim {
254 my $param = thaw(decode_base64(shift));
255 $param->{'job'} = $job;
256 my $exportnum = delete $param->{'exportnum'};
257 my $export = __PACKAGE__->by_key($exportnum);
258 my $file = delete $param->{'uploaded_files'};
260 my $dir = $FS::UID::cache_dir .'/cache.'. $FS::UID::datasrc;
261 open( $param->{'filehandle'}, '<', "$dir/$file" )
262 or die "unable to open '$file'.\n";
263 my $error = $export->import_sim($param);
268 local $FS::UID::AutoCommit = 1; # yes, 1
271 my $job = $param->{'job'};
272 my $fh = $param->{'filehandle'};
273 my @lines = $fh->getlines;
275 my @command = 'ADD KI';
276 push @command, ('HLRSN', $self->option('hlrsn')) if $self->option('hlrsn');
278 my @args = ('OPERTYPE', 'ADD');
279 push @args, ('K4SNO', $self->option('k4sno')) if $self->option('k4sno');
280 push @args, ('CARDTYPE', $self->option('cardtype'),
281 'ALG', $self->option('alg'));
282 push @args, ('OPCVALUE', $self->option('opcvalue'),
283 'OPSNO', $self->option('opsno'))
284 if $self->option('alg') eq 'MILENAGE';
286 my $agentnum = $param->{'agentnum'};
287 my $classnum = $param->{'classnum'};
288 my $class = FS::inventory_class->by_key($classnum)
289 or die "bad inventory class $classnum\n";
290 my %existing = map { $_->item, 1 }
291 qsearch('inventory_item', { 'classnum' => $classnum });
293 my $socket = $self->login;
295 my $total = scalar(@lines);
296 foreach my $line (@lines) {
298 $job->update_statustext(int(100*$num/$total).',Provisioning IMSIs...')
302 my ($imsi, $iccid, $pin1, $puk1, $pin2, $puk2, $acc, $ki) =
304 # the only fields we really care about are the IMSI and KI.
305 if ($imsi !~ /^\d{15}$/ or $ki !~ /^[0-9A-Z]{32}$/) {
306 warn "misspelled line in SIM file: $line\n";
309 if ($existing{$imsi}) {
310 warn "IMSI $imsi already in inventory, skipped\n";
314 # push IMSI/KI to the HLR
315 my $return = $self->command($socket,
318 'KIVALUE', qq{"$ki"},
321 if ( $return->{success} ) {
323 my $item = FS::inventory_item->new({
324 'classnum' => $classnum,
325 'agentnum' => $agentnum,
328 my $error = $item->insert;
330 die "IMSI $imsi added to HLR, but not to inventory:\n$error\n";
333 die "IMSI $imsi could not be added to HLR:\n".$return->{error}."\n";
336 $self->logout($socket);