メール配信の最近のブログ記事

子プロセスに処理を渡して、親プロセスで標準出力を切るとき、

my $pid;
if($pid = fork){
 #親の処理
 close (STDOUT);
 wait;
}
elsif(defined $pid){
 close (STDOUT);
 #子の処理
}
 ・
 ・
 ・


みたいな感じで今までOKだったのだが、ロリポップ系サーバなどではこれがうまく行かない。ブラウザの出力自体は切れるのだが、子プロセスの処理を行ってくれない。

WEBサーバによって変わると聞いたことがあるので、ググってみたところ、標準出力を切る部分を、

close (STDOUT);
close (STDERR);
close (STDIN);

とやったら上手く行った。

apache2.0系(うちの環境)とapache1.3系(ロリポップ系?)でどうやら違うらしい。

ちなみに、上記3つの順番が違っても1つ足りなくてもダメだった。

close (STDIN);
close (STDOUT);
close (STDERR);

↑これだとなぜか子プロセスが終了するまでブラウザが開放されない


close (STDOUT);
close (STDERR);
close (STDIN);

↑この順番じゃないとダメみたい。

STDOUTだけだと、新しいapacheのときダメだよって聞いていたので、それとは逆の現象だが、解決したからまあいいか。
私はDocomoの携帯しか持ってないので、事前にauとsoftbankを持ってる友人に頼んで、メールヘッダなどを調査したらこんな感じ↓

■ Docomo
----------------------------------------------------------------------------------
From ****@docomo.ne.jp Wed May 26 17:54:11 2010
Return-Path: <****@docomo.ne.jp>
X-Original-To: ***@igreks.jp
Delivered-To: ***@igreks.jp
Received: from localhost (localhost.localdomain [127.0.0.1])
by ***.igreks.jp (Postfix) with ESMTP id 5DE338B41B2
for <***@igreks.jp>; Wed, 26 May 2010 17:54:11 +0900 (JST)
X-Virus-Scanned: amavisd-new at igreks.jp
Received: from ***.igreks.jp ([127.0.0.1])
by localhost (igreks.jp [127.0.0.1]) (amavisd-new, port ****)
with ESMTP id kkpANBxiUasa for <****@igreks.jp>;
Wed, 26 May 2010 17:54:11 +0900 (JST)
Received: from docomo.ne.jp (mail108.docomo.ne.jp [203.138.203.8])
by ***.igreks.jp (Postfix) with ESMTP id 3E6618B40B0
for <***@igreks.jp>; Wed, 26 May 2010 17:54:11 +0900 (JST)
Date: Wed, 26 May 2010 17:54:09 +0900 (JST)
From: ****@docomo.ne.jp
To: ***@igreks.jp
Subject: =?iso-2022-jp?B?GyRCMSsbKEI=?=
Message-ID:
MIME-Version: 1.0
Content-Type: text/plain; charset="iso-2022-jp"
Content-Transfer-Encoding: 7bit

$BK\J8(B
----------------------------------------------------------------------------------


■ au
----------------------------------------------------------------------------------
From ****@ezweb.ne.jp Wed May 26 18:24:29 2010
Return-Path: <****@ezweb.ne.jp>
X-Original-To: ***@igreks.jp
Delivered-To: ***@igreks.jp
Received: from localhost (localhost.localdomain [127.0.0.1])
by ***.igreks.jp (Postfix) with ESMTP id F1F818B41B2
for <***@igreks.jp>; Wed, 26 May 2010 18:24:28 +0900 (JST)
X-Virus-Scanned: amavisd-new at igreks.jp
Received: from ***.igreks.jp ([127.0.0.1])
by localhost (igreks.jp [127.0.0.1]) (amavisd-new, port ****)
with ESMTP id CBSY6-Rv+mf6 for <***@igreks.jp>;
Wed, 26 May 2010 18:24:28 +0900 (JST)
Received: from ezweb.ne.jp (nx3oBP07-06.ezweb.ne.jp [59.135.39.240])
by ***.igreks.jp (Postfix) with ESMTP id D7FC88B40B0
for <***@igreks.jp>; Wed, 26 May 2010 18:24:28 +0900 (JST)
Received: from nxev04mp06 (localhost [127.0.0.1])
by nxev04mp06.ezweb.ne.jp (EZweb Mail) with SMTP id 955ED5BC640B4
for <***@igreks.jp>; Wed, 26 May 2010 18:24:28 +0900 (JST)
From: ****@ezweb.ne.jp
To: ***@igreks.jp
Subject: =?iso-2022-jp?B?GyRCMSsbKEI=?=
Message-ID: <2010052618242860697200007c12@nxev04mp06.ezweb.ne.jp>
Date: Wed, 26 May 2010 18:24:28 +0900
Mime-Version: 1.0
Content-Type: text/plain; charset="iso-2022-jp"
Content-Transfer-Encoding: 7bit

$B%6!<%6!<(B
----------------------------------------------------------------------------------


■ Softbank
----------------------------------------------------------------------------------
From ****@softbank.ne.jp Wed May 26 18:24:36 2010
Return-Path: <****@softbank.ne.jp>
X-Original-To: ***@igreks.jp
Delivered-To: ***@igreks.jp
Received: from localhost (localhost.localdomain [127.0.0.1])
by ***.igreks.jp (Postfix) with ESMTP id 975BD8B41B2
for <***@igreks.jp>; Wed, 26 May 2010 18:24:36 +0900 (JST)
X-Virus-Scanned: amavisd-new at igreks.jp
Received: from ***.igreks.jp ([127.0.0.1])
by localhost (igreks.jp [127.0.0.1]) (amavisd-new, port ****)
with ESMTP id HQrmiDMGcT3n for <***@igreks.jp>;
Wed, 26 May 2010 18:24:36 +0900 (JST)
Received: from mmrts049p01c.softbank.ne.jp (mmrts049p01c.softbank.ne.jp [123.108.236.27])
by ***.igreks.jp (Postfix) with SMTP id 6D9898B40B0
for <***@igreks.jp>; Wed, 26 May 2010 18:24:36 +0900 (JST)
Subject: =?ISO-2022-JP?B?GyRCJCYkcyQzGyhC?=
Mime-Version: 1.0
Content-Type:text/plain;charset=ISO-2022-JP
Content-Transfer-Encoding:7bit
Date: Wed, 26 May 2010 18:24:35 +0900
Message-ID: <20100526182435672870.2aea@0016E68F5982>
From: <****@softbank.ne.jp>
To: ***@igreks.jp
Sender:****@softbank.ne.jp
X-Priority: 3

$B%b%j%b%j(B
----------------------------------------------------------------------------------


仕様としては、

・DBはMySQL
・空メの送信先は「reg_kara_mail@igreks.jp」とする
・空メの件名に名前を指定できる。
・登録済みの場合はエラーメールを返す。
・空メ本文にメルマガIDをあらかじめ記載しておく。
・メルマガIDが認識できない場合もエラーメールを返す。
・読者データテーブル名は仮に「user_'メルマガID'」、文字コードはUTF8とする。

これを受けて、スクリプトはざっとこんな感じに。
※おおまかな流れだけで、細かい点は割愛してます


■ 空メール登録処理用CGI(karame.cgi)
-----------------------------------------------------------------------------------
#!/usr/bin/perl

package main;

use strict;
use CGI;
use DBI;
use Unicode::Japanese;

require './lib/get.pl'; #各種データ取得用ライブラリ(詳細割愛)
require './lib/proc.pl'; #各種処理用ライブラリ(詳細割愛)
require './lib/db.pl'; #DB接続、各種SQL実行用ライブラリ(詳細割愛)
require './lib/start.pl'; #メール配信用ライブラリ(詳細割愛)

&db::open(); #DB接続

my $sys_msg = ''; #エラー返信用メッセージ
my ($email, $name, $id);

###### メールから標準入力をパース
my $grep = '[\d\w-.+]+\@[\d\w-]+(\.[\d\w-]+)+'; #メールアドレスの正規表現

while(<>){
 if($_ =~ /^From.*?($grep)/i || $_ =~ /^Return-Path:.*?<($grep)>/i){
  $email = $1; #メールアドレス
 }
 if($_ =~ /^Subject:\s*(.+)$/i){
  $name .= $1; #名前
 }
 if($_ =~ /^\s*(=\?ISO-2022-JP.+\?=)\s*$/i){ #件名が途中で改行されてる時のため
  $name .= $1;
 }
 if($_ =~ /^mid:(.+)$/){
  $id = $1; #メルマガID
 }
}
if($email){
 if($id){
  my $stg = &get::setting_data($id); #メルマガ設定データ取得
  if($stg){
  ####### 読者テーブル内重複チェック
  &db::query("
   SELECT * FROM user_$id WHERE email='$email'
  ");
  my $href = $db::sth->fetchrow_hashref();
  if($href){
   #すでに登録済みの場合
   $sys_msg = <<EOM;
    送信いただいたメールアドレス「$email」はすでに登録済みです。
    このまま次回の配信をお待ちください。
EOM
   $sys_msg =~ s/\t//g;
   #メール返信処理
   &start::return_mail(
    '空メール登録処理エラー',
    $sys_msg,
   );
   exit;
  }
  if($name){
   #名前の入力があった場合
   $name = Unicode::Japanese->new(
    &proc::base64decode($name), #Base64デコード処理
    'jis'
   )->utf8; #JISからUTF8に変換
  }
  ######## 新規登録
  &db::query("
   INSERT IGNORE INTO user_$$pd{'id'} (
    no,  #オートインクリメント
    email,
    name,
    ・
    ・
    ・
   )
   VALUES(
    '',
    '$email',
    '$name',
    ・
    ・
    ・
   )
  ");
 }
 else{
  $sys_msg = <<EOM;
   登録しようとしたメールマガジンは存在しないか、すでに廃刊されています。
   お手数ですが、詳しくは発行者までお問い合わせください。
EOM
 }
}
else{
 $sys_msg = <<EOM;
  送信いただいた情報に不備があり登録できませんでした。
  空メール本文の内容は変更せずに送信してください。
  何度もこのエラーメールが返信される場合は、お手数ですが発行者までお問い合わせください。
EOM
}

########## エラー返信処理
if($sys_msg){
 &start::return_mail(
  '空メール登録処理エラー',
  $sys_msg,
 );
}
&db::close(); #DB切断

exit;

-----------------------------------------------------------------------------------



■Postfixエイリアス設定ファイル(/etc/aliase)に以下を追加
-----------------------------------------------------------------------------------
reg_kara_mail:  "|cd 'karame.cgiがあるディレクトリの絶対パス'; ./karame.cgi"
-----------------------------------------------------------------------------------

■リスタート
-----------------------------------------------------------------------------------
# newaliases

# /etc/init.d/postfix restart
-----------------------------------------------------------------------------------


■以下のようなメールを「reg_kara_mail@igreks.jp」宛に送る

 件名:自分の名前もしくは空白
 本文:mid:melmagaID

以上。


※レンタルサーバなどでエイリアスが設定できない場合は、後からcronなどでPOPアクセスして処理するなどの方法もある。(参考→http://www.igreks.jp/dev/2009/06/pop.html
この場合は返信メールが即時配信されない。


※ディズニーモバイルとかwilcomは調べてないけど、まあだいたい同じだべってことで。
メールの件名だけのためにMIME::Base64とか使いたくないなーと思ったので。

ほぼ、こちら(http://nabe.blog.abk.nu/064)の丸写しでごめんなさい。


-----------------------------------------------------------------------

#エンコード

my $subject1 = &base64_Encode($target1);

#デコード

my $subject2 = &base64_Decode($target2);



sub base64_Encode {
my ($target) = @_;
my ($base) = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
."abcdefghijklmnopqrstuvwxyz"
."0123456789+/";
my $eStr = "";
my $pStr = unpack("B*",$target);
for(my $i = 0;my $cStr = substr($pStr,$i,6); $i += 6){
$eStr .= substr($base,ord(pack("B*","00".$cStr)),1);
if(length($cStr) == 2){
$eStr .= "==";
}
elsif(length($cStr) == 4){
$eStr .= "=";
}
}
return("=?ISO-2022-JP?B?$eStr?=");
}

sub base64_Decode {
my ($str) = @_;
$str =~ s/=\?ISO-2022-JP\?B\?([A-Za-z0-9\+\/=]*)\?=/{&base64_Decode2($1)}/egi;
return $str;
}
sub base64_Decode2 {
my ($str) = @_;
my @base64ary = (
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 0x00〜0x1f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, # 0x10〜0x1f
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,62, 0, 0, 0,63, # 0x20〜0x2f
52,53,54,55, 56,57,58,59, 60,61, 0, 0, 0, 0, 0, 0, # 0x30〜0x3f
0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, # 0x40〜0x4f
15,16,17,18, 19,20,21,22, 23,24,25, 0, 0, 0, 0, 0, # 0x50〜0x5f
0,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, # 0x60〜0x6f
41,42,43,44, 45,46,47,48, 49,50,51, 0, 0, 0, 0, 0 # 0x70〜0x7f
);
my $ret;
my $buf;
my $f;
if (substr($str, -1) eq '=') { $f=1; }
if (substr($str, -2) eq '==') { $f=2; }
for(my $i=0; $i<length($str); $i+=4){
$buf = ($buf<<6) + $base64ary[ ord(substr($str,$i ,1)) ];
$buf = ($buf<<6) + $base64ary[ ord(substr($str,$i+1,1)) ];
$buf = ($buf<<6) + $base64ary[ ord(substr($str,$i+2,1)) ];
$buf = ($buf<<6) + $base64ary[ ord(substr($str,$i+3,1)) ];
$ret .= chr(($buf & 0xff0000)>>16) . chr(($buf & 0xff00)>>8) . chr($buf & 0xff);
}
if ($f>0) { chop($ret); }
if ($f>1) { chop($ret); }
return $ret;
}

-----------------------------------------------------------------------
毎日決まった時刻に送られてくる自動メルマガがなぜか今日は送られてこない。

おかしいなと思いつつもこういうときに限って日曜日で出かけたりしてる。


帰ってからサーバをチェックしてみるとなんと配送されてないキュー(deferedにたまりっぱなし)が約7000件・・・。


maillogをチェックしようにもどうやらログが膨大になりすぎてなかなか問題の部分が表示されない・・・

ようやく確認できたのが、

LibClamAV Warning: ***********************************************************
LibClamAV Warning: *** This version of the ClamAV engine is outdated. ***
LibClamAV Warning: *** DON'T PANIC! Read http://www.clamav.net/support/faq ***
LibClamAV Warning: ***********************************************************
LibClamAV Error: cli_hex2str(): Malformed hexstring: This ClamAV version has reached End of Life! Please upgrade to version 0.95 or later. For more information see www.clamav.net/eol-clamav-094 and www.clamav.net/download (length: 169)
LibClamAV Error: Problem parsing database at line 742
LibClamAV Error: Can't load daily.ndb: Malformed database
LibClamAV Error: cli_tgzload: Can't load daily.ndb
LibClamAV Error: Can't load /usr/XXXXXX/clamav/daily.cld: Malformed database
ERROR: Malformed database

とゆー部分。


最初の4行はいつものことなのでいいんだが、その後になんか見慣れないエラーが。

ここに気づくまでにかなりハマってしまい、amavisの設定ファイルやclamavの設定ファイルをいろいろいじくってみたり、パッケージをインストールしなおしてみたりしてもさっぱり変わらず、ようやくこちらのサイト
http://homepage.mac.com/yuji_okamura/iSawIt/archives/2010/04/entry_2452.html
のおかげで原因ぽいのがわかりました。

つまり簡単に言うと、clamavのバージョンを0.95以上にしないと、ついにファイルのスキャンができなくなりましたってゆーことらしい。

たしかにmaillogの他の部分を見てみると、「clamavのソケットに接続できなくてウイルススキャンに失敗しました」→「配送に失敗しました」みたいな流れがずらっと続いている。
(でもRPMで定期的に更新してんのはamavisの方なんだから、フツーは最初にamavisの方を疑うよなぁ・・・)


ほんでもって例のごとく、clamavの最新パッケージはyumから引っ張れないので以下を参考に、最新版のソースをダウンロードしてアップデートをしたわけです。

http://d.hatena.ne.jp/ooolong/20080102/1199294335
http://yokensaka.com/fedora/index.php?itemid=158

一個目のリンクはどうやら2個目のリンクの対処法を元に書いてあるみたいなんだけど、「yumのclamavパッケージ(依存も含む)を削除しておく」ってゆーのがウチの環境ではいけなかったみたいですね。
yumで削除したらinit.dのリストまでいじくられるわけで、デーモン(サービス)一覧からamavisデーモンまで無くなってしまったわけで・・・

ほんなわけで、再度yumからamavis-new、clamav、clamav-serverなどなど依存関係も含めてまるごとインストールしなおしてinit.dを元に戻した状態で、clamavの最新ソースを入れ直し(configure→make→make install)たらうまく行ったのです。


以上の作業が終わった時点から、溜まってたキューがどんどん送信され始めました。


ただ、GUIでサービス一覧を確認すると、amavisdは復活したけど今まであったclam.amavisdは復活しない。

でもコマンドで、
-----------------------------------

chkconfig --list

-----------------------------------
で確認すると、デーモン一覧にclamd.amavisdが入っている。
どうやらGUIにはパッケージでインストールしたものしか反映されないっぽい。

とりあえず、起動してみる。
-------------------------------------

/etc/init.d/clamd.amavisd start

------------------------------------


よし!起動成功!
ついでにランレベル3から5をONにする。

----------------------------------------

chkconfig clamd.amavisd on

---------------------------------------


デーモンを起動するとmaillogで騒いでた、「clamd.sock」が「/var/spool/amavisd」の中に出現。

これで「clamd.sockが見つかりません」というエラーが解決でき、点と点がつながったわけだ。

あ、そうそう、freshclamの定期実行もデフォルトに戻ってしまうみたいなので、
「/etc/sysconfig/freshclam」を開いて、最後の行をコメントアウトする。

-----------------------------------------------------------------------

FRESHCLAM_DELAY=disabled-warn # REMOVE ME
     ↓
#FRESHCLAM_DELAY=disabled-warn # REMOVE ME

----------------------------------------------------------------------


しかしclamavのおかげで費やした労力は今までも計りしれず、これからもこんな感じでやんなきゃなんねーのかと思うと、他のアンチウィルスソフトを使いたくなってしまうのは私だけではないはず。



メルマガを発行していて部数が増えてくるとよくあるのが、宛先からの自動返信。

「勝手に送ってくんなボケ」とか
「Re:クソスパマーへ返信します。」

とかいろいろ。

あと最近多いのが、
「いつも、メルマガ、楽しみに拝見しています。
実は、今回、メルマガ発行者さんに伝えたいことがあってメールしました。
 ・
 ・
 ・
このシステムで毎月、収入を得ています。
何もできない私でできるのですから、こんな素晴らしいメルマガを
書かれる方なら、きっとできることでしょう。
一度、下記のホームページをご覧になって下さい。
→URL」

といった、稼げる系の情報商材を自動返信で売り込んでくるメール。

だいたい送信元はgooメールかGメールのどちらか。

もちろん私は同意を得ていないメールアドレスをメルマガに勝手に登録したりしませんから、ボケとかクソとかは完全に向こう側のおかどちがいなのですが、さすがに毎回ボケとかクソとか言われるのは腹が立つ。

過去記事(http://www.igreks.jp/dev/2009/06/pop.html)で書いたPOPアクセスを用いてメルマガなどのエラーアドレスを自動で処理できる。

しかし、このやり方だけだと自動返信メールが処理できない。
なぜなら故意に設定された自動返信メールは、本文中にFROMとかTOとかSMTPサーバの返すメッセージが書いてないから。
つまり、フツーのメールを装って送信されてくる。

なので、前回のスクリプトを改良して自動返信メールにも対応させてみる。




[動作条件]
・perl5以上(確認した環境はは5.10)かつMail::POP3Clientモジュールがインストールされていること。
・POPアクセスにSSLを用いる場合(Gmailなど)は、さらにIO::Socket::SSLモジュール(要コンパイラ)が必要。
・あらかじめエラーメール受信専用のアドレスを作っておいて、メール送信する時にエンペロープFROMをそのアドレスに指定しておく。
・CRONなどで以下のスクリプトを実行する。
-------------------------------------------------------------------
#/usr/bin/perl -w
use strict;
use Mail::POP3Client;

my $log = "POPサーバに接続中・・・\n";

#コンストラクタ 
my $pop = new Mail::POP3Client(
  USER => 'pop_user', #popサーバのユーザ名
  PASSWORD => 'pop_pwd', #同パスワード
  HOST => 'pop.gmail.com', #popサーバ名
  AUTH_MODE => 'BEST', #認証モード(たいていはBESTでOK)
  DEBUG => 0, #デバッグ有無(正の整数を指定すると、プロンプトにサーバとのやりとりが表示される)
  TIMEOUT => 10, #接続タイムアウト秒(デフォルトは60)
  USESSL => 'true' #SSLを使わない場合は0(Gmailは使う)
 );


if($pop->Count < 0){ #接続に失敗すると-1が返るらしい
 $log .= "POPサーバに接続できませんでした";
}
else{
 $log .= "ID[$id]のエラーメールアドレスを調査中・・・\n";
  if($pop->Count != 0){
   my $cnt = $pop->Count;
   my @err = ();
   my $suc = 0;
   my %diag = (); ### エラーの原因格納用ハッシュ(任意)
   my @next_ck = ();
   $log .= "  $cnt件のエラーメールアドレスを処理中・・・\n";
   for(my $i=1; $i<=$cnt; $i++){
    my $pars_ok = 0; #### パース成功フラグ
    foreach($pop->Body($i)){
     ###本文(SMTPサーバが返すメッセージ)からエラーになった宛先をパース
     if(
      $_ =~ /^To:.*?\?iso-2022-jp\?B\?.+?\?=.*?<([\d\w-.+]+\@[\d\w-]+(\.[\d\w-]+)+)>/i ||
      $_ =~ /^Delivered-To:.*?([\d\w-.+]+\@[\d\w-]+(\.[\d\w-]+)+)/i ||
      $_ =~ /^X-Yahoo-Forwarded:.*?from.*?([\d\w-.+]+\@[\d\w-]+(\.[\d\w-]+)+).*?to/i
     ){
      $pars_ok = 1;
      push(@err,$1);
      $suc++;
      $pop->Delete($i);
      ####### Diagnosis エラーの原因チェック
      if($_ =~ /5\.4\.4|Host.*?not found/i){
       $diag{'Host_not_exists'}++; #ホストまたはドメインが存在しない
      }
      elsif($_ =~ /550|553|554|5\.0\.0|5\.1\.1|5\.7\.1/){
       $diag{'Account_not_exists'}++; #受信先又は転送先のアカウントが存在しない
      }
      elsif($_ =~ /421|450|4\.2\.1|User disk quota/i){
       $diag{'Mailbox_unavailable'}++; #受信先又は転送先のメールボックスが一時的に利用不可
      }
      elsif($_ =~ /451|452/){
       $diag{'Server_error'}++; #配信時サーバエラー
      }
      elsif($_ =~ /432|454|534|535|538/){
       $diag{'Authen_error'}++; #認証エラー
      }
      else{
       $diag{'Other'}++; #その他
      }
      last;
     }
    }#foreach
    ###### パースできなかったエラーメールは次のチェックへ
    push(@next_ck,$i) if !$pars_ok;
   }#for
   if(@next_ck != 0){
    for(my $i=0; $i<@next_ck; $i++){
     ####### 今度はヘッダをチェック
     foreach($pop->Head($next_ck[$i])){
      if(
       ###### 自動返信元をパース
       $_ =~ /^From:.*?([\d\w-.+]+\@[\d\w-]+(\.[\d\w-]+)+)/i ||
       $_ =~ /^From:.*?\?iso-2022-jp\?B\?.+?\?=.*?<([\d\w-.+]+\@[\d\w-]+(\.[\d\w-]+)+)>/i
      ){
       ### パースされたアドレスがデータベースにあるかチェック
       &db::query("SELECT email FROM usertable WHERE email='$1'");
       my $exists = $db::sth->fetchrow_array();
       if($exists){
        ### あれば削除対象に
        push(@err,$1);
       }
       $suc++;
       ##### メール削除フラグ
       $pop->Delete($next_ck[$i]);
       $diag{'Auto_return'}++; #自動返信が設定されている
       last;
      }
     }#foreach
    }#for
   }
   if(@err){
    ####エラーアドレスをDBから削除する処理など
    foreach(@err){
     &db::query("DELETE FROM usertable WHERE email='$_'");
    }
    $log .= "$suc件のエラーメールアドレスをデータベースから削除しました\n";
   }
   ###パースに失敗することもあるでしょう。
   if(($cnt- $suc) > 0){
    $log .= "※".($cnt- $suc)."件のエラーメールアドレスを削除できませんでした\n";
   }
 }
 else{
  $log .= "エラーメールアドレスはありませんでした\n";
 }
}

#接続終了(このときメールがサーバから削除される)
$pop->Close;

print $log;

------------------------------------------------------------------------




あー疲れた。




久々にメールアドレスでも新しく作ろうと思って、nologinで新規ユーザ作成してみた。

thunderbirdでちゃちゃちゃとアカウント設定して、いざ受信って思ったら、
「接続:ホストにログイン情報を送信しています・・・」
のまま音沙汰なし。

パスワードとかいろいろ確認しても特におかしくないので、とりあえずmaillogを確認したら、こんなメッセージが。

-----------------------------------------------------------------------------------------
dovecot.... mail_location not set and autodetection failed: Mail storage autodetection failed with home=......
-----------------------------------------------------------------------------------------

うーん、これはなんだろう。

なんか雰囲気的に、メールボックスの場所が指定されてないから、メールを取りにいけません的な感じだろうか・・・

おかしいなあ、前までは普通にユーザ作成してメーラーの設定するだけですぐにいけたんだけどなあ・・・
と思いつつ、とりあえず問題ありそうな「/etc/dovecot.conf」を確認。

ってゆうか、このファイル一回も中身見たことないし〜。

メールサーバ入れたときもdovecotの設定なんか一回もやんなかったのに・・・
デフォルトでいままでいけてたんですけどね。


でもなんかこの辺が臭かったので、

-------------------------------------------------------------------------------------------
# Location for users' mailboxes. This is the same as the old default_mail_env
# setting. The default is empty, which means that Dovecot tries to find the
# mailboxes automatically. This won't work if the user doesn't have any mail
# yet, so you should explicitly tell Dovecot the full location.
#
# If you're using mbox, giving a path to the INBOX file (eg. /var/mail/%u)
# isn't enough. You'll also need to tell Dovecot where the other mailboxes are
# kept. This is called the "root mail directory", and it must be the first
# path given in the mail_location setting.
#
# There are a few special variables you can use, eg.:
#
# %u - username
# %n - user part in user@domain, same as %u if there's no domain
# %d - domain part in user@domain, empty if there's no domain
# %h - home directory
#
# See for full list. Some examples:
#
# mail_location = maildir:~/Maildir
# mail_location = mbox:~/mail:INBOX=/var/mail/%u
# mail_location = mbox:/var/mail/%d/%1n/%n:INDEX=/var/indexes/%d/%1n/%n
#
#
#
#mail_location =
-------------------------------------------------------------------------------------------

google様で直訳してみると、locationの設定は必須ですよ〜みたいな感じなのかな?

とりあえず例のまんま設定してみる。

-------------------------------------------------------------------------------------------
mail_location = mbox:~/mail:INBOX=/var/mail/%u
-------------------------------------------------------------------------------------------

てなかんじ。

すると・・・おお!メーラーがちゃんとメールを読みに行った!

なんじゃそりゃ。


ユーザディレクトリにも「mail」ディレクトリが作成されてる。
さっきまで無かったのに・・・
この「mail」ディレクトリって何なのかわかんなかったけど、dovecotが勝手に作ってるんですね・・・


とりあえずpopはOKそうなので、ついでにSMTP(postfix)の方も確認。
/etc/postfix/main.cfを開いて、それっぽい部分を見てみると、

-------------------------------------------------------------------------------------------
# DELIVERY TO MAILBOX
#
# The home_mailbox parameter specifies the optional pathname of a
# mailbox file relative to a user's home directory. The default
# mailbox file is /var/spool/mail/user or /var/mail/user. Specify
# "Maildir/" for qmail-style delivery (the / is required).
#
#home_mailbox = Mailbox
#home_mailbox = Maildir/
-------------------------------------------------------------------------------------------

どうやら、デフォルトのメールボックスは/var/spool/mail/userか、 /var/mail/userで、何もいじらないとここにメールを届けに行くらしい。
というわけで、さっきのdovecotの設定とつじつまがあってるのでOK。

まあ、ちゃんとした人たちはMaildir形式にちゃんと設定してるんだろうけど、横着な私はこれで問題無し。


一応確認のためにpostfixから送信して、dovecotで取りに行ってちゃんと読めたのでめでたし。

しかし軽く7時間かかったのは痛かった。


サーバ立ち上げた時から今まで問題なかったのに、いつからdovecotのデフォルト設定変わったの?って感じ。

アップデートなんてまったくやってないのに。

・・・よくわからないなー
とりあえずサンプルスクリプト↓

-----------------------------------------------------------------------

use strict;
use CGI::Carp qw(fatalsToBrowser);
use Net::SMTP;
use Net::SMTP::SSL;
use Net::SMTP::TLS;
use Encode qw(from_to encode);

#メールアドレス設定
my $from = 'hoge@gmail.com';
my $mailto= 'huga@nantoka.com';

#件名設定
my $subject = 'SMTPを指定してメール送信!';
from_to($subject, 'shiftjis', 'iso-2022-jp');
encode('MIME-Header-ISO_2022_JP', $subject);

#メールヘッダー設定
my $header = << "MAILHEADER";
From: $from
To: $mailto
Subject: $subject
Mime-Version: 1.0
Content-Type: text/plain; charset = 'ISO-2022-JP"
Content-Trensfer-Encoding: 7bit

MAILHEADER

#メール本文設定
my $message = << 'MAILBODY';
本日は晴天なり。

あああいいいううう
さよーなら!
MAILBODY

#文字コードをJISに変換
from_to($message, 'shiftjis', 'iso-2022-jp');

#SMTP設定(適宜変更してください)
my $server = 'smtp.gmail.com'; #ホスト名
my $auth = 1; #SMTPAuthを使用する場合は1
my $tls = 0; #TLS接続を使う場合は1
my $ssl = 1; #SSL接続を使う場合は1
my $port = 465; #ポート指定。指定しないと25番が勝手に設定される
my $user = 'hoge@gmail.com'; #SMTPAuthのユーザ名
my $pass = 'passwaord'; #SMTPAuthのパスワード

#メール送信オブジェクト設定
my $smtp = '';

#TLSのときは、オブジェクト生成時にAuthデータを渡す
if($tls){
 $smtp = Net::SMTP::TLS->new(
  $server,
  Port => $port ,
  User => $user ,
  Password => $pass
 ) || die "Connect failed over tls";
}
elsif($ssl){
 $smtp = Net::SMTP::SSL->new(
  $server,
  Port => $port
 ) || die "Connect failed over ssl";
}
else{
 $smtp = Net::SMTP->new($server, Port => $port) || die "Connect failed";
}
if(!$tls && $auth){
 $smtp->auth($user, $pass) || die "auth failed";
}

#メール送信
$smtp->mail($from);
$smtp->to($mailto);
$smtp->data();
$smtp->datasend($header);
$smtp->datasend($message);
$smtp->dataend();
$smtp->quit;

print <<EOM;
Content-type: text/html

送信しました~!
EOM

exit;
1;

---------------------------------------------------------------------------------

※Net::SMTP::SSLおよびNet::SMTP::TLSを使うには、IO::Socket::SSLモジュールが必要。
※IO::Socket::SSLをインストールするには、Net::SSLeayモジュールが必要
※試したところ、gmailの場合は、TLS使用&ポート587でも送信できた。
※例ではエンコードにEncodeモジュールを使っているが、Jcodeとか使いたい人はお好きなように。
前回、postfixのaliasを設定して、rootレベルで自動処理する方法を書いた。

http://www.igreks.jp/dev/2008/11/postfix2.html
http://www.igreks.jp/dev/2008/11/post.html

しかし、この方法はpostfixのconfigファイルを書き換えたり、エイリアス設定をしたりしなくてはならないので、root権限を持たないレンタルサーバの1ユーザレベルでは、かなり敷居が高い。

というわけで、少し自動処理の正確性は落ちるけれども、もっと簡単な方法を紹介。

おそらく「N●O」とかはこのやり方じゃないかと思われる。
「N●O」はPHPみたいだけど、私はPHPあまり得意じゃないのでPerlで。


[動作条件]
・perl5以上(確認した環境はは5.10)かつMail::POP3Clientモジュールがインストールされていること。
・POPアクセスにSSLを用いる場合(Gmailなど)は、さらにIO::Socket::SSLモジュール(要コンパイラ)が必要。
・あらかじめエラーメール受信専用のアドレスを作っておいて、メール送信する時にエンペロープFROMをそのアドレスに指定しておく。
・CRONなどで以下のスクリプトを実行する。
-------------------------------------------------------------------
#/usr/bin/perl -w
use strict;
use Mail::POP3Client;

my $log = "POPサーバに接続中・・・\n";

#コンストラクタ 
my $pop = new Mail::POP3Client(
  USER => 'pop_user', #popサーバのユーザ名
  PASSWORD => 'pop_pwd', #同パスワード
  HOST => 'pop.gmail.com', #popサーバ名
  AUTH_MODE => 'BEST', #認証モード(たいていはBESTでOK)
  DEBUG => 0, #デバッグ有無(正の整数を指定すると、プロンプトにサーバとのやりとりが表示される)
  TIMEOUT => 10, #接続タイムアウト秒(デフォルトは60)
  USESSL => 'true' #SSLを使わない場合は0(Gmailは使う)
 );


if($pop->Count < 0){ #接続に失敗すると-1が返るらしい
 $log .= "POPサーバに接続できませんでした";
}
else{
 $log .= "ID[$id]のエラーメールアドレスを調査中・・・\n";
  if($pop->Count != 0){
   my @err = ();
   my $suc = 0;
   $log .= $pop->Count."件のエラーメールアドレスを処理中・・・\n";

   #サーバにある数だけ読み込む
   for(my $i=1; $i <= $pop->Count; $i++){

    #本文を1行ずつ取得
    foreach($pop->Body($i)){

    #予めbase64エンコードしておいて「名前<メールアドレス>」の形にしておくとToヘッダのパースが楽ちん。
    if($_ =~ /To:\s=\?iso-2022-jp\?B\?.+?\?=<(.+\@.+(\..+)+)>/i){
     push(@err,$1);
     $suc++;

     #パースに成功したらサーバのメッセージ削除フラグを立てる
     $pop->Delete($i);
     last;
    }
   }
  }
  if(@err){

   ####ここらでエラーアドレスをDBから削除する処理など
   foreach(@err){
    &db::query("DELETE FROM usertable WHERE email='$_'");
   }
   $log .= "$suc件のエラーメールアドレスをデータベースから削除しました\n";
  }

  #相手先MTAによっては、パースに失敗することもあるでしょう。
  if($pop->Count - $suc > 0){
   $log .= "※".$pop->Count - $suc."件のエラーメールアドレスを削除できませんでした\n";
  }
 }
 else{
  $log .= "エラーメールアドレスはありませんでした\n";
 }
}

#接続終了(このときメールがサーバから削除される)
$pop->Close;

print $log;

------------------------------------------------------------------------

※Mail::POP3ClientモジュールはPurePerlなのでなければCPANからダウンロードして、同じフォルダに置けば使える。
※IO::Socket::SSLが使えなければ、POPサーバをYahooとかの110番を使えばOK。
※SSL未対応でもよければNet::POP3モジュールとか使ってもOK。

追記:このスクリプトの発展形はこちら→http://www.igreks.jp/dev/2010/04/post-6.html
メルマガ配信した累計秒数を管理画面で参照したりするのに、秒数をhh:mm:ss形式に
したいなーということで・・・

例えば、

10  → 00:00:10
60  → 00:01:00
3601  → 01:00:01

みたいな感じ。

これをスクリプト側で操作するとなると、累計秒をローカルタイム形式に直して
sprintfとかで整形してだらだらだら・・・

と、意外とけっこう面倒くさかったり無駄なメモリを消費したり、なんか効率悪い。
てゆーか、こういう処理はなるべく書きたくないし。

そう思って、ググってたら、目的を一発で達成できるなんとも便利なSQL文を
発見。

------------------------------------------------------------------

「SEC_TO_TIME( 秒数 )」

------------------------------------------------------------------

これで、秒数を勝手にhh:mm:ss形式に変換してくれる。


SELECT SEC_TO_TIME(秒数)

で変換&参照できるが、DBから参照するんであれば、最初から
データ型をTIMEにしておき、

※$timeには秒数を格納

INSERT INTO table VALUES('SEC_TO_TOME($time)')

とかで、あらかじめhh:mm:ss形式でデータをぶち込んでおいてやれば、
インタフェイスが変わったときも便利。


あらためて、データベースって素晴らしいと思った次第。


ちなみにこれの逆は、

TIME_TO_SEC( hh:mm:ss )で、

TIME_TO_SEC(01:00:01) は、 3601 となる。
こういった場合、今まで

-------------------------------------------------
##直後に"mogemoge"含まない文字列にマッチ

$str =~ /hogehoge[^(mogemoge)]/;

-------------------------------------------------

これでいけたような気がしたんだが、久々に同じような処理を書いてみたら
これではダメだった。

前回はなんでうまくいったのか分からないが、クラスのマッチはやはり
一文字分しか指定できないらしいので、セオリーどおり、否定先読みを使う。


-------------------------------------------------
##直後に"mogemoge"含まない文字列にマッチ

$str =~ /hogehoge(?!mogemoge)/;



##"mogemoge"の部分はさらに正規表現を使ったり、変数を使ったりもできる

my $later = "moge";

$str1 = "hogehoge";
$str2 = "hogehogemogemoge";

$str1 =~ s/hogehoge(?!($later){1,})/oeoe/;
$str2 =~ s/hogehoge(?!($later){1,})/oeoe/;

print $str1."
".$str2;


結果:
hogehogeoeoe
hogehogemogemoge
-------------------------------------------------

逆に、特定の文字列を後方に含む場合は

(?!mogemoge) を (?=mogemoge)

にかればいいだけ。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちメール配信カテゴリに属しているものが含まれています。

前のカテゴリはブログです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

Powered by Movable Type 4.22-ja