G-chan Square

じーちゃん/へたっぴの綴る、日々のつれづれとか。
twitterのまとめとゲームネタが中心。2015年からロードバイク/ミニベロ始めました。

[perl] Net::SMTPでメールを送ろう

こんばんわ、じーちゃんです。

さてさて、昨日はくだらないミスで時間を潰してしまいました……orz
今日は、OP25B配下のサーバからプロバイダのメールサーバー経由でメールを送るのに挑戦してみましたよ。
さてさて、そんなわけでLAN環境にてxen環境の生死を監視して、死んでいたらメールアラートを飛ばそうという計画です。
昨日は、「OP25Bのせいで、サーバーから直接携帯にメールが飛ばせないYO!」ってところで止まりました。
そんなわけで、監視側からプロバイダが提供しているメールサーバに向けて直接SMTPをしゃべってメールを送信しよう計画を立案。
そして、直接SMTPをしゃべろうとしたモノの相手側のメールサーバと通信が確立しないよ! どういうこと!? → iptablesの設定が間違っていた というオチまでたどり着いたところで終わっていますw


そんなわけで、iptablesの設定を修正して再度接続を試みる。
ちなみに、じーちゃんの使っているDTIではメールサーバが通常の25番でListenしておらず、587番ポートで接続するようになっています。(注意:DTI全体がこうなっているかどうかは不明です)

そんなわけで、さっそく

telnet XXXX.dti.ne.jp 587 とかやってみて、SMTPをしゃべってみた。
以下、白い字はサーバーからの応答。黄色い字はこちら側からの入力内容。
"XXXX"は伏せ字w
220 XXXX.dti.ne.jp ESMTP DTImail 3.11v; Tue, 12 May 2009 21:35:15 +0900 (JST)
EHLO [127.0.0.1]
XXXX.dti.ne.jp Hello XXXX.dion.ne.jp [XXX.XXX.XXX.XXX], pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE 5242880
250-AUTH CRAM-MD5 PLAIN LOGIN
250-DELIVERBY
250 HELP
AUTH PLAIN XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
235 2.0.0 OK Authenticated
RSET
250 2.0.0 Reset state
MAIL FROM:<XXXX@mf.point.ne.jp>
250 2.1.0 <XXXX@mf.point.ne.jp>... Sender ok
RCPT TO:<XXXX@docomo.ne.jp>
250 2.1.5 <XXXX@docomo.ne.jp>... Recipient ok
DATA
354 Enter mail, end with "." on a line by itself
Date: Tue, 12 May 2009 21:35:17 +0900
From: XXXX <XXXX@mf.point.ne.jp>
To: XXXX@docomo.ne.jp
Subject: test
test
.
250 2.0.0 n4CCZFNP011397 Message accepted for delivery
QUIT
221 2.0.0 XXXX.dti.ne.jp closing connection
ちゃんと、メール送れました。

SMTP AUTHでの認証方法は "CRAM-MD5 PLAIN LOGIN" の3種類がある様子なんだけど、今回は送信できるかどうかだけを知りたかったのでPLAINを指定。
UserIDとパスワードをBase64エンコードした文字列が送られるので、平文そのものではないとはいえ、誰でも簡単にデコードできてしまうので平文そのものと変わらない罠w



さて、そんなわけで直接SMTPしゃべってメールが送れるなら、スクリプトで組んでも送れるはずじゃろー ってことで、メールサーバと直接やりとりするスクリプトを作成する。
言語は……とりあえず perl かな? ま、なんでもいいんだけど、シェルスクリプトライクな処理を書くときは、なぜかperlにしているじーちゃん。
CLIのPHPでもRubyでもいいんだけど……あ、でもPHPは open_base_dir の設定とか面倒くさいところで動かなかったりするから、やっぱりperlかRubyの2拓かなあ? ま、今回はperlで。

で、"perl smtp"とかでググって出てきたページを元に、"Net::SMTP"をインストール。
そして、諸処いろいろなページに書いてあるサンプルを動かそうとして……ここで、手が止まる。

接続先サーバのポート番号の指定はどないすればええのん?

そう! SMTP=25番ポートと決めつけている処理がほとんどだから、"Net::SMTP"のどのサンプルを見てもポート番号の指定方法とか載っていない。
"perldoc Net::SMTP"とか見ても……
       new ( [ HOST ] [, OPTIONS ] )
           This is the constructor for a new Net::SMTP object. "HOST" is the name of
           the remote host to which an SMTP connection is required.
           "HOST" is optional. If "HOST" is not given then it may instead be passed as
           the "Host" option described below. If neither is given then the "SMTP_Hosts"
           specified in "Net::Config" will be used.
           "OPTIONS" are passed in a hash like fashion, using key and value pairs.
           Possible options are:
           Hello - SMTP requires that you identify yourself. This option specifies a
           string to pass as your mail domain. If not given localhost.localdomain will
           be used.
           Host - SMTP host to connect to. It may be a single scalar, as defined for
           the "PeerAddr" option in IO::Socket::INET, or a reference to an array with
           hosts to try in turn. The "host" method will return the value which was used
           to connect to the host.
           LocalAddr and LocalPort - These parameters are passed directly to IO::Socket
           to allow binding the socket to a local port.
           Timeout - Maximum time, in seconds, to wait for a response from the SMTP
           server (default: 120)
           ExactAddresses - If true the all ADDRESS arguments must be as defined by
           "addr-spec" in RFC2822. If not given, or false, then Net::SMTP will attempt
           to extract the address from the value passed.
           Debug - Enable debugging information
           Example:
               $smtp = Net::SMTP->new(’mailhost’,
                                      Hello => ’my.mail.domain’,
                                      Timeout => 30,
                                      Debug   => 1,
                                     );
               # the same
               $smtp = Net::SMTP->new(
                                      Host => ’mailhost’,
                                      Hello => ’my.mail.domain’,
                                      Timeout => 30,
                                      Debug   => 1,
                                     );
               # Connect to the default server from Net::config
               $smtp = Net::SMTP->new(
                                      Hello => ’my.mail.domain’,
                                      Timeout => 30,
                                     );
……。

載ってNEEEEEEE!!!!!

え? これはあれですか? 「SMTPが25番ポート以外しゃべるなんて、ボク知らな~い」ってことですか? そーなんですか!?

で、いろいろググってみるも、有力な情報を得られないまま時を過ごすじーちゃん。

「じゃ、しょうがない。Net::SMTPの接続処理部分をフレキシブルにポート変更できるように自分で書き換えるか」ってことで、Net::SMTPのソースを読んでみたら……
  foreach $h (@{ref($hosts) ? $hosts : [$hosts]}) {
    $obj = $type->SUPER::new(
      PeerAddr => ($host = $h),
      PeerPort => $arg{Port} || 'smtp(25)',  # この行に注目
      LocalAddr => $arg{LocalAddr},
      LocalPort => $arg{LocalPort},
      Proto     => 'tcp',
      Timeout   => defined $arg{Timeout}
      ? $arg{Timeout}
      : 120
      )
      and last;
  }
以上のソースは、Net::SMTPの一部分を抜粋。

ちゃんとポート番号の指定できるじゃん!w

そうなら最初からドキュメントにも書いておいてYO!


というわけで、最終的にNet::SMTPを使ってDTIのメールサーバ経由でメールを送るスクリプトは以下の通りと相成り候。
#!/usr/bin/perl
use Jcode;
use Net::SMTP;
my $mailhost = 'XXXX.mf.point.ne.jp';
my $mailport = 587;
my $mail_username = 'XXXX@mf.point.ne.jp';
my $mail_password = 'XXXX';
my $to_mail = 'XXXX@docomo.ne.jp';
my $from_mail = 'XXXX@mf.point.ne.jp';
my $smtp = Net::SMTP->new($mailhost,
                          Port => $mailport);
$smtp->auth($mail_username, $mail_password);
$smtp->mail($from_mail);
$smtp->to($to_mail);
$smtp->data();
$smtp->datasend("From: $from_mail\n");
$smtp->datasend("To: $to_mail\n");
$smtp->datasend("Subject: xen01 is dead\n");
$smtp->datasend("\n");
$smtp->datasend("xen01 is dead\n");
$smtp->quit;

コメント(5)

inujini 返信

open_basedirってシンボリックリンクで回避できたけど、今でもそのままの仕様なんですかね。
まあ、apache経由しなければいいのか。

うちはテキトーに組んだphpの小物が多いので、もう全部phpでいいや的な感じですね。

匿名 返信

死活監視といえば、うちの社内サーバも電源以外はやってますが、困った事にDiCEがちゃんと動かなくて30日に1回Dyndnsのアカウントに手動でログインしてますね。つーかDiCE英語版とか無いのかよっ!とか思います。
リモートからだとUTF-8対応のSSHクライアント使ってるんで、DiCEの設定画面の文字が読めない。勘でやったけどダメだったよママン。EUC-JPうぜえええええええ
かといって物理的にモニターとかキーボード繋ぐのもめんどくさい。

inujini 返信

名前入れ忘れた。

じーちゃん 返信

>inujiniくん
PHPのopen_basedirの設定はapache経由してもしなくても有効だね。
なので、CLIのツールを作るのにPHPを使ってしまうと、いらんところで苦しくなるハメになりがちw

シンボリックリンクの解決は、確かPHP5系で厳しくなったはず。
http://www.phppro.jp/phpmanual/php/ini.sect.safe-mode.html
によると、「全てのシンボリックリンクは解決されるので、 シンボリックリンクを使ってこの制限を回避することは不可能です」とある。今試してみたけどやっぱりダメだったね。

じーちゃんのこのサーバもDDNSで稼働しているんだけど、DiCEは使用していないね。
まぁ、"dip.jp"のドメインを貸してくれているところが、オリジナルの処理を用意してくれていて、こっちは定期的にHTTP_GETするだけだから、楽でよい面セキュリティ的には今ひとつw

あと、SSHクライアントは……Win上で動くものはじーちゃん3つしか知らないんだよねぇ。
"Teraterm" <老舗。超有名。でも機能的にはイマイチ。
"Putty" <これも有名だけど……じーちゃんはあまり使ってない
"Poderosa" <機能的には十分なんだけど、.net frameworkで動いていてたまに異常終了するクセモノw

Poderosaなら、UTF-8対応されていて、かつ、サーバがEUC-JPをしゃべるならEUC-JPモードに切り替えることが可能。

inujini 返信

むしろTeraterm UTF-8対応版とWinSCPに対応していないサーバやサービスが悪と断言しよう。sambaのディレクトリ下もファイル名とかSJISじゃなくてUTF-8(EUC-JPにはできる)になればいいのに。
そしてもう全世界がUTF-8になればいいのにとか思ってたらgoogleがUTF-8に絵文字入れて再定義しようぜとか言い出して余計なことするなアホゥがとか思う今日この頃です。

そして、open_basedirの設定はapache経由しなくても有効なのですか。
おれ最初の設定で何やったっけなぁ(;´Д`)
/var/wwwと/home/inujiniの両方で動く。でも設定が思い出せない。

コメントする