異動の時期に活躍するスクリプト

異動の時期になりました.この時期の管理者はメール転送なんかをやらなきゃならない.でも,タイミングが悪いとスプールにメールが溜まっていたり,そもそもサーバに残す設定にしている人がいる.そんなこんなで考えたのがこのスクリプト.全部どかんと載せると長いから,少しずつ.

#!perl -w

{
  package Mine;
  use base qw(Net::SMTP::Multipart);
  use Carp;

Net::POP3を使ってサーバにアクセスし,Net::SMTP::Multipartを使って取得したメールを添付形式で投げている.

  sub Text
  {
    my $self = shift;
    my $text = shift;
    $text .= "\n";
    $self->datasend(sprintf"\n--%s\n",$Net::SMTP::Multipart::b);
    $self->datasend("Content-Type: text/plain; charset=iso-2022-jp\n");
    $self->datasend("Content-Transfer-Encoding: 7bit\n");
    $self->datasend("Content-Description: Mail message body\n\n");
    $self->datasend($text);
    $self->datasend("\n\n");
  }

Net::SMTP::MultipartはContent-Typeの設定がなくてテキストが表示できなかったので,Net::SMTP::Multipartを継承してMineというクラスを作り,Textメソッドをオーバーライト.

  sub FileAttach {
    my $self = shift;
    my $type = shift;

    foreach my $file (@_) {
      unless (-f $file) {
        carp 'Net::SMTP::Multipart:FileAttach: unable to find file $file';
        next;
      }
      my($bytesread,$buffer,$data,$total);
      open(FH,"$file") || carp "Net::SMTP::Multipart:FileAttach: failed to open $file\n";
      binmode(FH);
      while ( ($bytesread=sysread(FH,$buffer, 1024))==1024 ){
        $total += $bytesread;
        $data .= $buffer;
      }
      if ($bytesread) {
        $data .= $buffer;
        $total += $bytesread ;
      }
      close FH;

FileAttachメソッドもオーバーライトする.といっても,ここら辺には変更は特になし.Content-Typeの情報を取得するために,typeという引数を増やしている.

      if ($data){
        $self->datasend("--$Net::SMTP::Multipart::b\n");
        $self->datasend("Content-Type: $type ; name=\"$file\"\n");
        $self->datasend("Content-Disposition: attachment; filename=\"$file\"\n\n");
        $self->datasend($data);
        $self->datasend("--$Net::SMTP::Multipart::b\n");
      }
    }
  }
}

元のNet::SMTP::MultipartはMIMEencodeしているけど,その処理をはずした.そして,Content-Typeを設定している.

## mail catch and release script
#
use Net::POP3;
use File::Temp qw /mkstemps/;
use strict;

my $pops  = "xxx.xxx.xxx.xxx";
my $smtps = "xxx.xxx.xxx.xxx";
my $from  = "from@hoge.jp";
my $user  = shift or die "specify username\n";
my $pass  = $user;
my $to    = shift or die "specify to mail address\n";

スクリプト本体の開始.$pops, $smtpsには使っているpop3サーバ,smtpサーバの値を設定.$fromには誰かのメールなのかを指定する.そういえば,このスクリプトは,pop before smtpには対応してない.今回は使わなくてすんだので,その処理はここではパス.ここでは,ユーザ名とパスワードに同じ値を設定している例を書いている.このスクリプトの使い方は,"hoge.pl username to_mail_address" としている.

my $pop = Net::POP3->new($pops);
unless (my $num = $pop->login($user, $pass)){
  unless (defined $num){
    print "USER or PASS mismatched\n";
    exit;
  }else{
    print "No mails for $user\n";
    exit;
  }
}
my $msgsref = $pop->list;
my $count;

ここが,POP3にアクセスしてメッセージを取得するまでの流れ.

for my $msgref (sort {$a <=> $b} keys %$msgsref){
  my ($fh, $filename) = mkstemps("robotXXXXX", ".eml");
  $pop->get($msgref, $fh);
  close $fh;

メッセージを,.emlという拡張子を持つテンポラリファイルに格納する.

  my $smtp = Mine->new($smtps);
  my $subj = sprintf "Forwarding mail No.%d.", ++$count;
  $smtp->Header(
    To   => $to,
    Subj => $subj,
    From => $from);
  $smtp->Text("Hi, ");
  $smtp->FileAttach("message/rfc822", $filename);
  $smtp->End();
  unlink $filename;
}
$pop->quit;
printf "%d mails forwarded for %s.\n", $count, $user;

ホントは"unlink $filename"というのは必要ないはずだが,なぜかwin32ではうまく動かない.そのため,明示的にファイルを削除した.これでメールがぱかすかと飛んでいく.