GmailのSMTPを利用してPerlからメール送信(添付ファイル付)

さて、前稿でモジュールの準備ができたのでPerlからGmail経由で添付ファイルを送る。

#!/usr/bin/perl

use lib '/home/xxxxxxxx/local/lib/perl5';
use Jcode;
use Net::SMTP;
use Net::SMTP::SSL;
use MIME::Entity;

my $smtp_server = 'smtp.gmail.com';
my $smtp_port = '465';
my $smtp_acc = 'gmail@example.com';
my $smtp_pwd = 'password';

my $mail_to = 'to@example.com';
my $mail_bcc = 'bcc@example.com';
my $mail_from = 'from@example.com';
my $mail_subject = '件名ですよ';
my $mail_body = <<"_MAILBODY_";
本文ですよ
_MAILBODY_

$mail_subject = jcode($mail_subject)->jis;
$mail_subject = jcode($mail_subject)->mime_encode;
$mail_to = jcode($mail_to)->jis;
$mail_to = jcode($mail_to)->mime_encode;
$mail_bcc = jcode($mail_bcc)->jis;
$mail_bcc = jcode($mail_bcc)->mime_encode;
$mail_from = jcode($mail_from)->jis;
$mail_from = jcode($mail_from)->mime_encode;
$mail_body = jcode($mail_body)->jis;

my $err;
my $oSmtp;
my $oMime;

$oSmtp = Net::SMTP::SSL->new($smtp_server,Port => $smtp_port, Debug => 1);

if($oSmtp->auth($smtp_acc,$smtp_pwd)){
  $oSmtp->mail($mail_from);
  $oSmtp->to($mail_to);
  $oSmtp->bcc($mail_bcc);
  $oSmtp->data();
  $oMime = MIME::Entity->build(
    From     => $mail_from,
    To       => $mail_to,
    Bcc      => $mail_bcc,
    Subject  => $mail_subject,
    Data     => $mail_body);
  $oMime->attach(
    Path     => '/home/xxxxxxxx/hogefuga.csv',
    Type     => "application/octet-stream",
    Encoding => "Base64"
  );
  $oSmtp->datasend($oMime->stringify);
  $oSmtp->dataend();
  $oSmtp->quit;
}else{
  $err = 'SMTP Server Authentication Error!!';
}

今回はCSVなのでContent-Typeはapplication/octet-streamに。
GmailのアドレスはGoogla AppsのでもOK。


問題なく稼動したのでcronにスケジューリング。
今夜から毎晩2:00にメールが届く予定。

参考にしたサイト

sakuraのレンタルサーバにCPANをインストール

cronで添付ファイルを飛ばすスクリプトGmail経由にする。
Net::SMTP::SSLなどを使いたいのでCPANをユーザー領域にインストールする。


localディレクトリに入れることにする。

mkdir -p ~/local

CPANの初期設定ファイルを作る。

echo no | cpan

~/.cpan/CPAN/MyConfig.pmの内容を修正。

'make_install_arg' => qq[SITEPREFIX=$ENV{HOME}/local],
'makepl_arg' => qq[INSTALLDIRS=site INSTALL_BASE=$ENV{HOME}/localLIB=$ENV{HOME}/local/lib/perl5 PREFIX=$ENV{HOME}/local],
'mbuildpl_arg' => qq[./Build --install_base $ENV{HOME}/local],
'urllist' => [q[ftp://ftp.kddilabs.jp/CPAN/]],

参考にしたサイトではurllistが

'urllist' => [q[ftp://ftp.cpan.jp/], q[ftp://ftp.kddilabs.jp/CPAN/]],

となってますが、ftp.cpan.jpが応答しなかったので、こちらは削除。


環境変数を設定してからCPANを起動

$ echo 'setenv PATH $HOME/local/bin:$PATH' >> ~/.cshrc
$ source ~/.cshrc
$ cpan

設定を数個聞かれるがデフォルト値でいいのでエンターでパス。


欲しいモジュールをインストール。

cpan>install Test::More
cpan>install IO::Socket::SSL
cpan>install Net::SMTP
cpan>install Net::SMTP::SSL
cpan>install Authen::SASL
cpan>install MIME::Entity


cronで起動させるスクリプトを書くのでuse libを使わなくてはいけないが、シェルから起動してテストもするので、一応環境変数を設定。

$ echo 'setenv PERL5LIB $HOME/local/lib/perl5:$HOME/local/lib/perl5/site_perl' >> ~/.cshrc
$ source ~/.cshrc

実際のスクリプトでは先頭に

use lib '/home/xxxxxx/local/lib/perl5';

として利用。

参考にしたサイト

Attacking PHP

盛り上がってるみたいなので便乗。
もともとは、

こちらの記事で、それにいろんな人が意見を書いてるようです。

日本語で分かりやすくまとめてる、Rubyの中の人、Matzさんの記事から引用させてもらうと

  • いろんなものがfalseだったりするせいで、新たな比較演算子「===」が必要
  • hashやlistがobjectが区別できない
  • オブジェクト指向機能が壊れている
  • lambda(無名関数)がない。create_functionはlambdaじゃない
  • short_open_tagsやらsafe_modeやらregister_globalsがonになってるかもしれない
  • 一貫性のない名前(str_replace,strlen,parse_strとか)
  • 引数の順番がわかりにくい(in_arrayとかstrposとか)
  • strpos('abcd','a')は0を返すが、0は偽である
  • PHPで書かれたたくさんのアプリがあるがどれもHTMLとロジックが分離されてない(本当?)
  • PHPは初心者に学びやすい」と言われる。確かにそうかもしれないが、 おかげでどれだけのSQLインジェクションやらXSS脆弱性やら、 ひどいコードが放置されていることか
  • グローバル。使うんじゃないってば。

PHPをメインに利用することが多いのですが、概ね反論できません。
下から3つ目の、デザインとビジネスロジックの分離は、フレームワークなどを利用して適切に書いてるソースも多いと思えるので、一応否定しておきます。
(ごちゃ混ぜのソースも多々ありますし、そもそもMatzさんも?つけてますね)


もっとも追記にあった

にも同意します。


言語なんてものは選択肢に過ぎないと思っていますので、どんな言語でも利用する限り、セキュアでかつスパゲッティじゃない(=メンテナンスしやすく、他人も読みやすい)コードを書けるようになりたいものです。
当然不必要に実行時間やメモリなどのリソースを食うアルゴリズムも論外です。

DoCoMoからシフトJISのメール

最近DoCoMoSMTPサーバーの仕様が変わったのか、DoCoMoからのメールがシフトJISで届くようになった。
しかもSubjectまで、

Subject: =?shift_jis?B?hogefuga=?=

こんな感じで。


PHSにきたメールをSOFTBANKスクリプトで自動転送していたのですが、雑なつくりだったために、文字化けしてしまうようになりました。
仕方なく修正。


それにしてもメールアドレスの件といい、この会社は相変わらず自分の道を行ってるようです。
もう少しRFCなどに準拠して欲しいものです。

複数セッションでのhistory共有

Ctrl+PやCtrl+Rで利用できるBashのコマンド履歴、実体は~/.bash_historyだが標準の動作ではセッション閉じた時に上書きなので、複数のセッションを同時利用してるとちと不便だ。
ってことでプロンプト表示のたびにリロード、追記を行うことで共有する。
~/.bashrcに、

function share_history {
    history -a
    history -c
    history -r
}
PROMPT_COMMAND='share_history'
shopt -u histappend
export HISTSIZE=9999

を追記。

history -a

追記(Appendかな)。

history -c

ロード済みのhisotryキャッシュを消去(Clearかな)。

history -r

リロード(Reload)。


これらをプロンプト表示時にコールするように設定し、

shopt -u histappend

Bash標準の上書き動作を解除。

参考サイト

認証サイトのcrawler

無事にCrypt::SSLeayがインストールできたので、当初の目的のスクリプトを作成。

use strict;
use LWP::UserAgent;
use HTTP::Cookies;

my $ua = LWP::UserAgent->new;
my $url = 'https://www.example.com/login';
my $req = HTTP::Request->new('POST' => $url);
$req->content('user=hoge&pass=fuga');
$req->header('Content-Type' => 'application/x-www-form-urlencoded');
my $res = $ua->request($req);

my $cookie = HTTP::Cookies->new;
$cookie->extract_cookies($res);
$url = 'https://www.example.com/info';
$req = HTTP::Request->new('GET' => $url);
$cookie->add_cookie_header($req);
$res = $ua->request($req);

ログインページにユーザー情報をPOSTして、返ってきたCookieをセットして情報を取得したいページをGETします。
ネットバンクとかから残高を取得するスクリプトなんかに利用できるかと。

HTTP::Responseのis_hogehoge

PerlのHTTP::Responseモジュールだがis_successというメソッドがある。
前回のHTTPSクローラーでも正常な通信の判定に利用していたが、これに落とし穴が。
サーバーが302のMoved Permanentlyを返した時にFALSEとなるのである。

CPANにあるドキュメントを読んでみると、
http://search.cpan.org/~gaas/libwww-perl-5.808/lib/HTTP/Response.pm

$r->is_info
$r->is_success
$r->is_redirect
$r->is_error

These methods indicate if the response was informational, successful, a redirection, or an error. See HTTP::Status for the meaning of these.

とあるのでHTTP::Statusのドキュメントを読む。

is_info( $code )

Return TRUE if $code is an Informational status code. This class of status code indicates a provisional response which can't have any content.

is_success( $code )

Return TRUE if $code is a Successful status code.

is_redirect( $code )

Return TRUE if $code is a Redirection status code. This class of status code indicates that further action needs to be taken by the user agent in order to fulfill the request.

is_error( $code )

Return TRUE if $code is an Error status code. The function return TRUE for both client error or a server error status codes.

is_client_error( $code )

Return TRUE if $code is an Client Error status code. This class of status code is intended for cases in which the client seems to have erred.

This function is not exported by default.

is_server_error( $code )

Return TRUE if $code is an Server Error status code. This class of status codes is intended for cases in which the server is aware that it has erred or is incapable of performing the request.

だそうだ。


念のためにソースも確認すると、

sub is_info         ($) { $_[0] >= 100 && $_[0] < 200; }
sub is_success      ($) { $_[0] >= 200 && $_[0] < 300; }
sub is_redirect     ($) { $_[0] >= 300 && $_[0] < 400; }
sub is_error        ($) { $_[0] >= 400 && $_[0] < 600; }
sub is_client_error ($) { $_[0] >= 400 && $_[0] < 500; }
sub is_server_error ($) { $_[0] >= 500 && $_[0] < 600; }

これは分かりやすい。


HTTP::Responseのコードは、

sub is_info     { HTTP::Status::is_info     (shift->{'_rc'}); }
sub is_success  { HTTP::Status::is_success  (shift->{'_rc'}); }
sub is_redirect { HTTP::Status::is_redirect (shift->{'_rc'}); }
sub is_error    { HTTP::Status::is_error    (shift->{'_rc'}); }

ということ。


まとめるとHTTPのレスポンスコードによって、

  • 100番台 ⇒ is_infoがTRUE
  • 200番台 ⇒ is_successがTRUE
  • 300番台 ⇒ is_redirectがTRUE
  • 400番台 ⇒ is_client_errorがTRUE,is_errorがTRUE
  • 500番台 ⇒ is_server_errorがTRUE,is_errorがTRUE

となる。
ただしis_client_errorとis_server_errorはHTTP::Responseでは未実装。


ログイン認証つきのクローラーを書く時、ログインした時にリダイレクトするケースが多いのでis_successを安易に使うとはまります。