/ctcp/dcc/get.plm の resume 対応

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><!-- $_ if 0; # -*- perl -*-

package ctcp_dcc_get;

$RECVSIZE = 65536;
$DIRECTORY = '.';
$CLIENTINFO = 'DCC';
$TIMEOUT = 3600;
$LOGFILE = "dccfile.log";

%rezhost = ();
%rezhosttofile = ();
%rezsize = ();

$_ = 'ctcp_dcc_get';

sub main_loop {
  local($userno) = @_;
  local($socket, $recv, $length, $size, $tmp, $timeout);
  $recv = &'property($userno, 'recvsize') || $RECVSIZE;
  $timeout = &'property($userno, 'timeout') || $TIMEOUT;
  foreach $sno (&'array($dccserverlist)) {
    next unless $'userno[$server[$sno]] == $userno;
    if (vec($'rout, $sno, 1)) {
      if ($size[$sno] - $done[$sno] < $recv) {
        $size = $size[$sno] - $done[$sno];
      } else {
        $size = $recv;
      }
      $tmp = '';
      $socket = $'socket[$sno];
      if ($length = sysread($socket, $tmp, $size)) {
        if (open(FILE, ">>$file[$sno]")) {
          select((select(FILE), $| = 1)[0]);
          print FILE $tmp;
          close(FILE);
        }
        $done[$sno] += $length;
        print $socket pack('N', $done[$sno]);
        if ($done[$sno] >= $size[$sno]) {
          &dcc_close($sno);
        }
      } else {
        &dcc_close($sno);
      }
      vec($'rout, $sno, 1) = 0;
    } elsif (time() - $'access[$sno] > $timeout) {
      &'close($sno);
      $dccserverlist = &'remove($dccserverlist, $sno);
    }
  }
}

sub module_disable {
  local($userno) = @_;
  foreach $sno (&'array($dccserverlist)) {
    next unless $'userno[$server[$sno]] == $userno;
    &'close($sno);
    $dccserverlist = &'remove($dccserverlist, $sno);
  }
}

sub cpss_dcc {
  local($serverno, $prefix, $cmd, @params) = @_;
  local($userno, $regex);
  $userno = $'userno[$serverno];
  &client_notice("*** DCC request from $prefix:  " . join(' ', $cmd, @params ) );
  if (&check_mask($userno, $prefix, $params[0])) {
    ($cmd, $params[1]) = &dcc_open($serverno, $cmd, $params[1]);
    &client_notice("*** DCC get request checked");
    return ($prefix, $cmd, @params);
  }
  return ($prefix, $cmd, @params);
}

sub dcc_open {
  local($serverno, $cmd, $msg) = @_;
  local($userno, $nick, $dir, $name, $mode, @params, $sno);

  $userno = $'userno[$serverno];
  $nick = &'prefix($prefix);
  @params = split(/\s+/, $msg);

# params[0] // DCCコマンド
# params[1] // ファイル名
#       .DCC RESUME _file _port _size.
#       .DCC ACCEPT _file _port _size.

  $dir = &'expand(&'property($userno, 'directory') || $DIRECTORY);

  if ("\L$params[0]\E" eq 'new') {
    $name = "$dir/" . &newfile($dir, $params[1]);
    &dcc_newfile($serverno, $name);
    &dcc_start($serverno, $name, $params[2], $params[3], $params[4], 0);
    return ();
  }
  elsif ("\L$params[0]\E" eq 'send') {
    $rcvfilename = &safe_filename( $params[1] );

    # ファイルがあり、ユーザがチャンネルで無いなら、RESUMEリクエスト (*) sub newfile での判定が無駄に...?_?
    if (-e "$dir/$rcvfilename" &&
         !(&'exist($'channellist[$serverno], $nick))) {
      $size = -s "$dir/$rcvfilename";
      
      $rezhost{$prefix . $params[1]} = $params[2];
      $rezsize{$prefix . $params[1]} = $params[4];
      $rezhosttofile{$prefix} = $params[1]; # ACCEPTは 'file.ext' として送り返される時が多い

      &client_notice("*** DCC get SEND request :" .  join(' ', 'DCC RESUME', $params[1], $params[3], $size));
      &'s_print($serverno, '', 'PRIVMSG', $prefix, "\cA" . join(' ', 'DCC RESUME', $params[1], $params[3], $size) . "\cA");
      return ();
    }
    else {
      $name = "$dir/" . &newfile($dir, $params[1]);
      &dcc_newfile($serverno, $name);
      
      &dcc_start($serverno, $name, $params[2], $params[3], $params[4], 0);
      return ();
    }
  }
  elsif ("\L$params[0]\E" eq 'accept') {
    $params[1] = $rezhosttofile{$prefix} if( "\L$params[1]\E" eq 'file.ext' );
    &client_notice("*** DCC accept request $params[1] from $prefix");
    $rcvfilename = &safe_filename( $params[1] );

    if (-e "$dir/$rcvfilename") {
      $size = -s "$dir/$rcvfilename";
      if ( $size == $params[3] ) {
        local( $rzmsz ) = $rezsize{$prefix . $params[1]};
        local( $rzmhst ) = $rezhost{$prefix . $params[1]};
    
        #一度Accept が通ったら破棄
        delete $rezhost{$prefix . $params[1]};
        delete $rezsize{$prefix . $params[1]};
        delete $rezhosttofile{$prefix};

        &dcc_start($serverno, "$dir/$rcvfilename", $rzmhst, $params[2], $rzmsz, $size);
        return ();
      }
      else {
        &client_notice("*** DCC accept request :size is different $size != $params[3]");
      }
    }
  }

  return ($cmd, $msg);
}

sub dcc_newfile {
  local($serverno, $name) = @_;
  $userno = $'userno[$serverno];
  if (open(FILE, ">$name")) {
    close(FILE);
    $mode = &'property($userno, 'mode');
    if (defined($mode)) {
      chmod(oct($mode), $name);
    }
  }
}

sub dcc_start {
  local($serverno, $name, $server, $port, $size, $done ) = @_;
  local($userno, $nick, $sno, $cno, $mode, $host);

  $userno = $'userno[$serverno];
  $nick = &'prefix($prefix);

  if ($sno = &'connect($server, $port)) {
    $file[$sno] = $name;
    $size[$sno] = $size;
    $done[$sno] = $done;
    $nick[$sno] = $nick;
    $server[$sno] = $serverno;
    $dccserverlist = &'add($dccserverlist, $sno);

    local($host) = (&'peername($sno))[2];

    &client_notice("*** DCC getting file $name ($size bytes) from $nick\@$host");
    &logging("$header DCC get $name ($size bytes) from $nick\n");
    return ();
  }
}

sub dcc_close {
  local($sno) = @_;
  &client_notice("*** DCC got file $file[$sno] ($done[$sno]/$size[$sno] bytes) from $nick[$sno]");
  &logging("$header DCC got $file[$sno] ($size[$sno] bytes) from $nick[$sno]\n");
  &'close($sno);
  $dccserverlist = &'remove($dccserverlist, $sno);
}

sub newfile {
  local($dir, $file) = @_;
  local($name, $ext, $num);

  $file = &safe_filename($file);
  $name = $file;
  if (-e "$dir/$name") {
    $ext = '';
    if (rindex($file, '.') > 0) {
      $ext = substr($file, rindex($file, '.'));
      $file = substr($file, 0, rindex($file, '.'));
    }
    $num = 1;
    $name = "$file-$num$ext";
    while (-e "$dir/$name") {
      $num++;
      $name = "$file-$num$ext";
    }
  }
  return $name;
}

sub check_mask {
  local($userno, $prefix, $chan) = @_;
  local($user, $chlist, $list);
  foreach $mask (&'property($userno, 'mask')) {
    ($user, $chlist) = split(/\s+/, $mask);
    $list = '';
    foreach $vchan (split(/\,/, $chlist || '')) {
      $list = &'add($list, &'real($vchan));
    }
    if (&'channel($chan)) {
      next unless (!$list || &'exist($list, $chan));
    } else {
      next unless (!$list || &'exist($list, '*'));
    }
    if ($user =~ /^\-/) {
      $regex = &'regex(substr($user, 1));
      return 0 if $prefix =~ /$regex/i;
    }
    if ($user =~ /^\+/) {
      $regex = &'regex(substr($user, 1));
    } else {
      $regex = &'regex($user);
    }
    return 1 if $prefix =~ /$regex/i;
  }
  return 0;
}

sub safe_filename {
  local($name) = @_;
  $name = &'jis_sjis($line);
  $name =~ s/^.*[\\\/]([^\\\/]+)$/$1/;
  return $name;
}

sub client_notice {
  local($serverno, $prefix, $msg) = @_;
  local($cno);

  foreach $cno (&'array($'clientlist)) {
    next unless $'avail[$cno];
    next unless $'server[$cno] == $serverno;
  
    &'c_print($cno, '', 'NOTICE', $'nick[$cno], $msg);
  }
}

sub logging {
  local( $msg ) =@_;
  local $logfile = &'property($userno, 'logfile') || $LOGFILE;

  if (open(DCCLOGFILE, ">>$logfile" )) {
      $header = &'date('%m%d %H:%M');
      print DCCLOGFILE $msg;
      close(DCCLOGFILE);
  }
}

__END__
--><HTML><HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=ISO-2022-JP">
<TITLE>ctcp/dcc.get.plm</TITLE></HEAD><BODY>

オンラインドキュメント


<HR><H3>名前</H3>

ctcp/dcc/get.plm - DCC SENDに反応して送られてきたファイルを受け取る


<HR><H3>説明</H3>

DCC SENDリクエストが送られてきたとき、
それに反応して送られてきたファイルを自動的に受信します。


<HR><H3>プロパティ</H3>

<DL>
<DT>  ctcp.dcc.get.directory ディレクトリ
</DT>
<DD>    受信したファイルを置くディレクトリを指定します
        ディレクトリが存在しないときや、
        ディレクトリに書き込みができないときはファイルを受信しません。
        デフォルトではカレントディレクトリです。
</DD>
<DT>  ctcp.dcc.get.mask* ユーザマスク [(チャンネル名)]
</DT>
<DD>    ファイルを送信してきた人がここで指定されたものにマッチしたら、
        送られてきたファイルを受信します。
</DD>
<DT>  ctcp.dcc.get.mode ファイルモード
</DT>
<DD>    ファイルを受信したとき、そのファイルのファイルのモードを
        ここで指定した値にします。
</DD>
<DT>  ctcp.dcc.get.logfile ファイル名
</DT>
<DD>    DCCファイル受信ログ
</DD>
</DL>


<HR><H3>設定例</H3>

<PRE>
+ ctcp/dcc/get.plm
ctcp.dcc.get.mask: *!*@*
</PRE>

DCC SENDでファイルが送信されてきたとき、
それが誰から送られてきたかにかかわらず、そのファイルを受信します。
受信したファイルはカレントディレクトリに作成されます。

<PRE>
+ ctcp/dcc/get.plm
ctcp.dcc.get.directory: dcc
ctcp.dcc.get.mask: *!*@*.jp
</PRE>

DCC SENDでファイルが送信されてきたとき、
送信してきた人が「*!*@*.jp」にマッチしたら、そのファイルを受信します。
受信したファイルは「dcc」というディレクトリの中に作成されます。
「dcc」という名前のディレクトリが存在しない場合や、
書き込みができない場合はファイルは受信しません。

</BODY></HTML>