#!/usr/bin/perl -w

# モジュール指定
use IO::Socket;
use IO::Select;

$port = shift || "5103";       # ポート番号を設定
                               # 引数(@ARGV)で指定がなければ 5103 を使用
$ttl = "3600";                 # Aレコードに指定する TTL の値
$key_file = "./hosts.key";     # 認証ファイル名とそのパス
$host_cmd = "host";            # hostコマンドのパス
$update_server = "127.0.0.1";  # updateを行うDNSサーバ(ここではlocalhost)


my $listening_socket = IO::Socket::INET->new(LocalPort => $port,
					     Listen    => SOMAXCONN,
					     Proto     => 'tcp',
					     Reuse     => 1,
					    );
if ( ! $listening_socket ){
  die "開始できませんでした。 $!\n";
}

my $read_sel = IO::Select->new;
$read_sel->add($listening_socket);
print "TCP:$port で DDNS 処理待ち受け開始\n";

while (1){# 無限ループでデーモン化
  my ($active_socks) = IO::Select->select($read_sel,undef,undef,undef);# 複数処理に対応
  foreach my $sock (@{$active_socks}){
    if ( $sock == $listening_socket ){
      my $new_sock = $listening_socket->accept;
      $read_sel->add($new_sock);
      print "Connecting\n";
    } else {
      my $buf = <$sock>;
      $buf =~ s/\r\n$//g;# 末尾の改行(CR LF)削除
      if ( $buf && &BindUpdate($buf,$sock)){
	$sock->flush();;# バッファデータ送出
      } else {
	$read_sel->remove($sock);
	$sock->close();
	print "Connection close\n";
      }
    }
  }
}
undef $read_sel;
undef @ARGV;
exit(0);

### nsupdate処理 ###
sub BindUpdate {
  my($mess,$sock) = @_;
  if($mess =~ /^quit$/i){ # コマンドがquitの場合、終了
    return 0;
  }
  my(@a) = split(/\s+/,$mess);# $a[0]:コマンド $a[1]:ホスト名 $a[2]:キー $a[3]:SALT
  my $client_ip = $sock->peerhost(); # クライアントのIP取得

  # nsupdate に流し込むファイル名(/tmp/ddns + クライアントIP + "-" + 100未満の乱数)
  my $rnd = int(rand(100));
  my $add_file = "/tmp/ddns$client_ip-$rnd";

  if($a[0] =~ m/^ddns$/i){# コマンドがDDNSの場合dnsupdate
    if(&check_key($a[1],$a[2],$a[3])){# keyの正当性を確認
      if(&check_rec($a[1],$client_ip)){
	# 同じレコードが登録されていない場合
	open(ADDFILE ,"> $add_file") or return 0;
	print ADDFILE "server $update_server\n";
	print ADDFILE "update delete $a[1] IN A\n";
	print ADDFILE "update add $a[1] $ttl IN A $client_ip\n";
	print ADDFILE "\n";
	close(ADDFILE);
	`nsupdate $add_file`;
	unlink($add_file);
	print $sock "正常に終了\r\n";# 正常にUpdate
	print "正常に update されました\n";
	return 1;
      }else{
	# 同じレコードが登録されている場合
	print $sock "登録されているレコードに変更はありません\r\n";
	print "既登録につきレコード変更なし\n";
      }
    }else{
      print $sock "ホスト名またはキーが一致しません\r\n";# パスワードエラー
      print "Host not found or Key Error\n";
    }
  }else{
    print $sock "不正なコマンドです\r\n";
    print "不正コマンド\n";
  }
  return 0;
}
### キーの正当性を確認 ###
sub check_key{
  my($host,$crypt,$salt) = @_; # 引数:ホスト名:クリプト化されたキー:SALT
  open(KEYDATA,$key_file);     # ローカルのキーファイルの読み込み
  while($line=<KEYDATA>){
    chomp $line;
    my(@b) = split(/\s+/,$line);
    if($b[0] =~ /^$host$/i){
      my $client_crypt = crypt($b[1],$salt);
      if($crypt =~ m/$client_crypt/){
	return 1;              # ホスト名 キーともに一致した場合
      }
    }
  }
  return 0;# host.key ファイル総スキャン後該当のホスト名無し
}
### レコードが登録されているか確認 ###
sub check_rec(){
  my($host,$client_ip) = @_;
  my(@cmd_res) = `$host_cmd $host $update_server`;
  my $res = pop @cmd_res;
  my(@c) = split(/\s+/,$res);# "host.example.jp has address 10.0.0.5"のような結果を期待
  if($c[3] =~ /^$client_ip$/i){
    return 0;# 登録されている
  }else{
    return 1;# 登録されていない
  }
}
