--- /dev/null
+#!/usr/bin/perl
+
+####################################################################################
+# Asterisk Black/White List Entry CGI (AMI)
+#
+# $Id: black-white.pl,v 1.1 2026/08/22 12:45:22 dev Exp $
+#
+####################################################################################
+
+use warnings;
+use strict;
+use utf8;
+use Config::Simple '-strict';
+use CGI;
+use Telephony::Asterisk::AMI;
+use HTML::Template;
+use Encode;
+
+# Settings
+my $debug = 0;
+my $ami_def_user = 'user0001';
+my $ami_def_pass = 'password';
+my $cnf_def_path = '/var/www/asterisk/black_whitelist.ini';
+my $html_tmpl = '/var/www/asterisk/black_whitelist.tmpl';
+
+my $h_title = 'Asterisk Black/White List';
+my $h_lang = 'ja';
+my $h_enc = 'UTF-8';
+my $h_css = [
+ { 'href' => 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css',
+ 'integrity' => 'sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB',
+ 'crossorigin' => 'anonymous'
+ },
+ { 'href' => 'https://cdn.jsdelivr.net/npm/bootstrap-table@1.27.3/dist/bootstrap-table.min.css',
+ 'integrity' => '',
+ 'crossorigin' => 'anonymous'
+ }
+ ];
+my $h_js = [
+ { 'href' => 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js',
+ 'integrity' => 'sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI',
+ 'crossorigin' => 'anonymous'
+ },
+ { 'href' => 'https://code.jquery.com/jquery-3.7.1.min.js',
+ 'integrity' => 'sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=',
+ 'crossorigin' => 'anonymous'
+ }
+ ];
+
+# Init
+my ( $cfg, $ami_user, $ami_pass );
+my ( $response, $output, $res_stat, $stat_flg, $stat_msg );
+my ( $tab_1, $tab_2 );
+my ( $err_flg, $err_msg );
+my ( $cmd_f, $cmd_l );
+my ( $dmy, $telno );
+my ( $i, $line );
+my $hd_act = '';
+my $hd_cate = '';
+my $hd_telno= '';
+my $hd_desc = '';
+my $cate = '';
+my $desc = '';
+my $bid = 0;
+my $wid = 0;
+undef my @blacklist;
+undef my @whitelist;
+
+$err_flg = 0;
+
+if ( -f $cnf_def_path ){
+ $cfg = new Config::Simple($cnf_def_path);
+ $ami_user = $cfg->param("AMI.user");
+ $ami_pass = $cfg->param("AMI.pass");
+ $html_tmpl= $cfg->param("COM.template");
+ $debug = $cfg->param("COM.debug_flg");
+}
+else{
+ $ami_user = $ami_def_user;
+ $ami_pass = $ami_def_pass;
+}
+
+if ( $debug ) {
+ require Data::Dumper;
+ Data::Dumper->import( qw(Dumper) );
+ require CGI::Carp;
+ CGI::Carp->import( qw(fatalsToBrowser) );
+}
+#use if $debug, CGI::Carp => qw(fatalsToBrowser);
+
+my $ami_obj = Telephony::Asterisk::AMI->new(
+ Username => $ami_user,
+ Secret => $ami_pass
+);
+
+my $template = HTML::Template->new( filename => $html_tmpl, utf8 => 1 );
+my $cgi = new CGI;
+my $own_fn = $cgi->url;
+if ( defined $cgi->param('act') ) { $hd_act = $cgi->param('act'); }
+if ( defined $cgi->param('cate') ) { $hd_cate = $cgi->param('cate'); }
+if ( defined $cgi->param('telno') ){ $hd_telno = $cgi->param('telno'); }
+if ( defined $cgi->param('desc') ) { $hd_desc = $cgi->param('desc'); }
+
+$hd_telno =~ s/^\s+//;
+$hd_telno =~ s/\s+$//;
+$hd_telno =~ s/-//;
+
+### Main ###
+
+# POSTデータ(追加/削除ボタン押下時) astdb処理
+if ( $hd_act eq 'addnumber' or $hd_act eq 'delnumber' ){
+ $cmd_f = 0;
+ if ( $hd_act eq 'addnumber' ){
+ ($err_flg, $err_msg) = &telno_chk($hd_telno);
+
+ if ( $err_flg == 0 ){
+ if ( $hd_cate eq 'blacklist' ){
+ $cmd_f = 1;
+ $cmd_l = qq(database put blacklist $hd_telno "$hd_desc");
+ }
+ elsif ( $hd_cate eq 'whitelist' ){
+ $cmd_f = 1;
+ $cmd_l = qq(database put whitelist $hd_telno "$hd_desc");
+ }
+ else{
+ $err_flg = 1;
+ $err_msg = 'Illegal DataError';
+ }
+ }
+ }
+ elsif ( $hd_act eq 'delnumber' ){
+ ($err_flg, $err_msg) = &telno_chk($hd_telno);
+
+ if ( $err_flg == 0 ){
+ if ( $hd_cate eq 'blacklist' ){
+ $cmd_f = 1;
+ $cmd_l = qq(database del blacklist $hd_telno);
+ }
+ elsif ( $hd_cate eq 'whitelist' ){
+ $cmd_f = 1;
+ $cmd_l = qq(database del whitelist $hd_telno);
+ }
+ else{
+ $err_flg = 1;
+ $err_msg = 'Illegal DataError';
+ }
+ }
+ }
+ else{
+ $err_flg = 1;
+ $err_msg = 'Illegal DataError';
+ }
+
+ if ( $cmd_f == 1 ){
+ $ami_obj->connect or die $ami_obj->error;
+ my $response = $ami_obj->action({ Action => 'Command',
+ Command => "$cmd_l"
+ });
+ $ami_obj->disconnect or die $ami_obj->error;
+ $res_stat = $response->{'Response'};
+ if ( $res_stat eq 'Success' and $hd_act eq 'addnumber' ){
+ $stat_flg = 1;
+ $stat_msg = '登録しました';
+ }
+ elsif ( $res_stat eq 'Success' and $hd_act eq 'delnumber' ){
+ $stat_flg = 1;
+ $stat_msg = '削除しました';
+ }
+ else {
+ $err_flg = 1;
+ $err_msg = 'Illegal DataError';
+ }
+ }
+}
+
+# HTML::Template 変数入力の前処理
+# 追加/削除押下時のアクティブタブ 押下後も維持
+if ( $hd_cate eq 'blacklist' ){
+ $tab_1 = 'active';
+ $tab_2 = '';
+}
+elsif ( $hd_cate eq 'whitelist' ){
+ $tab_1 = '';
+ $tab_2 = 'active';
+}
+else{
+ $tab_1 = 'active';
+ $tab_2 = '';
+}
+
+# Asterisk astdbへブラックリスト/ホワイトリスト電話番号を取得
+$ami_obj->connect or die $ami_obj->error;
+$response = $ami_obj->action({ Action => 'Command',
+ Command => 'database show'
+ });
+$ami_obj->disconnect or die $ami_obj->error;
+
+for ( $i = 0; $i < @{$response->{'Output'}} ; $i++ ){
+ $line = $response->{'Output'}->[$i];
+ if ( $line =~ m!/blacklist/! or $line =~ m!/whitelist/! ){
+ ($dmy, $cate, $telno) = split(/\//, (split(/:/, $line))[0]);
+ $desc = (split(/:/, $line))[1];
+ $desc =~ s/\s+$//;
+ if ( $cate eq 'blacklist' and $telno =~ m/^([0-9]+)\s+$/ ){
+ $bid++;
+ $telno = $1;
+ push( @blacklist, {'bid' => $bid, 'telno' => $telno, 'desc' => $desc} );
+ }
+ elsif ( $cate eq 'whitelist' and $telno =~ m/^([0-9]+)\s+$/ ){
+ $wid++;
+ $telno = $1;
+ push( @whitelist, {'wid' => $wid, 'telno' => $telno, 'desc' => $desc} );
+ $wid++;
+ }
+ }
+}
+
+# HTML::Templateで使用する変数を定義
+$template->param( BLIST => \@blacklist );
+$template->param( WLIST => \@whitelist );
+$template->param( BNUM => $bid );
+$template->param( WNUM => $wid );
+$template->param( OWNPRG => $own_fn );
+$template->param( TAB_BL => $tab_1 );
+$template->param( TAB_WH => $tab_2 );
+$template->param( ERRFLG => $err_flg );
+$template->param( ERRMSG => $err_msg );
+$template->param( STAFLG => $stat_flg );
+$template->param( STAMSG => $stat_msg );
+$output = $template->output();
+
+# HTML 出力
+print $cgi->header( -type=>'text/html', -charset=> $h_enc );
+print $cgi->start_html(
+ -title=> $h_title,
+ -lang => $h_lang,
+ -encoding => $h_enc,
+ -meta => {'name'=>'robots',
+ 'content' => 'noindex',
+ },
+ -style => [
+ { 'src' => $h_css->[0]->{'href'},
+ 'integrity' => $h_css->[0]->{'integrity'},
+ 'crossorigin' => $h_css->[0]->{'crossorigin'}
+ },
+ { 'src' => $h_css->[1]->{'href'},
+ 'integrity' => $h_css->[1]->{'integrity'},
+ 'crossorigin' => $h_css->[1]->{'crossorigin'}
+ },
+ ],
+ );
+print qq(<script src="$h_js->[0]->{'href'}" integrity="$h_js->[0]->{'integrity'}" crossorigin="$h_js->[0]->{'crossorigin'}">);
+print qq(</script>\n);
+print qq(<script src="$h_js->[1]->{'href'}" integrity="$h_js->[1]->{'integrity'}" crossorigin="$h_js->[1]->{'crossorigin'}">);
+print qq(</script>\n);
+print Encode::encode( 'UTF-8', $output );
+
+### SubRoutine ###
+sub telno_chk {
+ my ($t1) = @_;
+ my ($t_flg, $t_msg);
+
+ if ( $t1 =~ m/^0[0-9]{9,9}$/ or $t1 =~ m/^0[6-9]0[1-9]{8,8}$/ ){
+ return (0,'');
+ }
+ elsif ( $t1 =~ m/^0[1-9][0-9]+$/ ){
+ $t_flg = 1;
+ $t_msg = '入力された桁数が規定と異なります。';
+ }
+ elsif ( $t1 =~ m/^[1-9][0-9]+$/ ){
+ $t_flg = 1;
+ $t_msg = '先頭文字は0から始まる必要があります。海外電話番号の入力は出来ません。';
+ }
+ elsif ( $t1 =~ m/\D/ ){
+ $t_flg = 1;
+ $t_msg = '数字以外の文字が入力されました。';
+ }
+ elsif ( $t1 eq '' ){
+ $t_flg = 1;
+ $t_msg = '電話番号が入力されていません。';
+ }
+ else {
+ $t_flg = 1;
+ $t_msg = 'Unknown Error';
+ }
+ return ($t_flg, "$t_msg");
+}