ラベル Mail の投稿を表示しています。 すべての投稿を表示
ラベル Mail の投稿を表示しています。 すべての投稿を表示

2024年4月18日木曜日

cakePHP3でメールの受信

XSERVER でも使えるように

cakePHP3を使ったプロジェクトでメールを受信して添付ファイルを保存しようとしましたら。
まず、POP3用のライブラリをインストールしなきゃいけないことがわかりました。
で、VM上の Linux にインストールしてみました。

# pear channel-update pear.php.net
/usr/share/pear/Net にPOP3.php, Socket.php がインストールされました。
# pear install Mail_mimeDecode
/usr/share/pear/Mail にインストールされました。

でも、本番環境の XSERVER では root 権限でインストールなんてできないよね。
と、いうことで。 /usr/share/pear/のファイルを CAKEPHP_DIR /appendix/pear に無理やりコピーして使うことにしました。

require が動かない

 require('Net/POP3.php');
require('Mail/mimeDecode.php');
としなきゃいけないのに、パスが効いてないから読み込めない。
PHP で get_include_path() を呼び出すと、次のようなインクルードパスが設定されていました。
".:/usr/share/pear:/usr/share/php"
そこで、
set_include_path(get_include_path() . PATH_SEPARATOR . CAKEPHP_DIR . '/appendix/pear/');
を呼び出すことで require がうまくいってくれました。

ポート番号995(POP3S)での受信設定

アカウント情報は、とりあえず MailServers データベースにでも置いといて、次のように取り込むようにしました。

  $account = $this->MailServers->find()
    ->select(['pop3_server', 'pop3_port', 'pop3_account', 'pop3_psw'])
    ->first();
実際に POP3サーバに接続してログインする部分のコードは、こんな感じ
  $pop3 = new Net_POP3();
  $pop3->connect($account->pop3_server, $account->pop3_port);
  $pop3->login($account->pop3_account, $account->pop3_psw);
  $message_count = $pop3->numMsg();
$account->pop3_port を 995 にすると、接続ができずに悩みました。
pop3_server にプロトコルヘッダ ssl:// を付けてあげることで接続できました(^^)v

メール受信処理

実際には、message-id をDBに保存しておいて、すでに処理済みのメールかどうかをチェックしたりするのですが
メールの取り込みは次のような感じです

    for($mail_no = 1; $mail_no <= $message_count; $mail_no++)
    {
        $msg = $pop3->getMsg($mail_no);
        $decoder = new Mail_mimeDecode($msg);
        $mime = $decoder->decode([
            'include_bodies' => true,
            'decode_bodies' => true,
            'decode_headers' => true,
        ]);
        $messageid = $mime->headers['message-id'];
        $subject = $mime->headers['subject'];
        $sender = $mime->headers['from'];
        $date = $mime->headers['date'];
        // メール解析
        [$body, $attachments] = $this->analyze($mime);
        // 必要な処理
    }

今回は本文と添付ファイル情報を必要としたので、メールの mime 解析部分は、こんな感じです。

function analyze($mime)
{
    $body = "";
    $attachments = [];

    if(!isset($mime->parts))
    {
        // シングルパート(テキストのみ)
        $body = trim(mb_convert_encoding( $mime->body, "UTF-8", 'ASCII, JIS, UTF-8, SJIS' ));
    }
    else
    {
        // マルチパート
        foreach($mime->parts as $part)
        {
            if((strtolower($part->ctype_primary) == 'text') && (strtolower($part->ctype_secondary) == 'plain'))
            {
                $body .= trim(mb_convert_encoding( $part->body, "UTF-8", 'ASCII, JIS, UTF-8, SJIS' )) . "\n";
            }
            else if((strtolower($part->ctype_primary) == 'application')) 
            {
                $filename = $part->ctype_parameters['name'];
                if(empty($filename))
                {
                    $filename - $part->d_parameters['filename'];
                }
                if(0 < strlen($filename))
                {
                    if(isset($part->body))
                    {
                        $attachment = [];
                        $attachment['filename'] = $filename;
                        $attachment['body'] = $part->body;
                        $attachments[] = $attachment;
                    }
                }
            }
            else if(strtolower($part->ctype_primary) == 'multipart')
            {
                foreach($part->parts as $part2)
                {
                    if(strtolower($part2->ctype_primary) == "text") 
                    {
                        $charset = $part2->ctype_parameters['charset'];
                        $body = mb_convert_encoding($part2->body, 'UTF-8', $charset);
                        break;
                    }
                }
            }
        }
    }  
    return [$body, $attachments];
}

pear の修正

いろいろエラーが出たので、pear 側のソースも変更しました。

■Net/POP3.php 変更点
/* 第3パラメータが非推奨になっていたので削除
define('NET_POP3_STATE_DISCONNECTED',  1, true);
define('NET_POP3_STATE_AUTHORISATION', 2, true);
define('NET_POP3_STATE_TRANSACTION',   4, true);
*/
define('NET_POP3_STATE_DISCONNECTED',  1);
define('NET_POP3_STATE_AUTHORISATION', 2);
define('NET_POP3_STATE_TRANSACTION',   4);

// クラス名と同名のコンストラクタが非推奨なので変更
// function Net_POP3()
function __construct()

■Mail/mimeDecode.php 変更点
// ISO-2022-JPのメールヘッダを正しく解析してくれない。
function _decodeHeader($input, $default_charset=false)
{
    if (!$this->_decode_headers) {
        return $input;
    }
    // iconv_mime_decode を使うように変更。
    $input = iconv_mime_decode($input, ICONV_MIME_DECODE_CONTINUE_ON_ERROR);
    // Remove white space between encoded-words 以下を削除。
    return $input;
}

// はだかのHTMLテキストが渡されてエラーでまくりなので変更
function _quotedPrintableDecode($input)
{
    // Remove soft line breaks
    $input = preg_replace("/=\r?\n/", '', $input);
    $input = quoted_printable_decode($input);
    // Replace encoded characters 以下を削除
    return $input;
}

まとめ

XSERVERにインストールできないとなると、AWS EC2 あたりにサーバー移動しなきゃならないかな?と悩んでいたので、受信できるようになって助かりました。

めでたしめでたし。

2023年7月26日水曜日

WEBサーバで、一定時間だけ有効な認証キーで登録する

メールアドレスの入力確認のための方法

2度入力させるUIって、ほんとダメですよね。

メールアドレスを入力してください。(*)
確認のため、再度メールアドレスを入力してください。(*)
(*)必須項目。しかもコピペできないw

んでも、入力ミスっていたら、その後メール来ないし。(笑)
これ、よく見かけますよねぇ。
昔はうちもやってましたが(笑)

認証キーを送信して、入力させる方法

そこで、30分とか1時間だけ有効な認証キーを送って、それを入力してもらうようにするといいんですね。
これも、よく見かけるようになりましたが(笑)

こんな画面です。

メールアドレスを入力して「認証キー送信」ボタンを押してもらう。
受信したメールに記載の認証キーを入力して、本来の登録処理にする。
こうすることで、メールが届かなかったら、入力ミスだとわかるし、メールが届けば間違いないメールアドレスだと判断できますね
認証キーは一定時間(たとえば30分)だけ有効で、それを過ぎると無効になります。

で、これをどのようにサーバー側で実装しようか、ということですよ。

HTMLページ上の Ajax を使った Javascript は以下のような感じになります。

// 認証キーを送信する処理
onButtonSendAuthKey = function()
{
    // メールアドレスに認証コードを発行するサーバープログラム
    var url = '/regist-temporary';
    var params = {
        mail: $('#input-mail').val()
    };
    $.post({
        url: url,
        data: params,
    }).then(function(response){
        // 略
    });
}

// 入力されたメールアドレスと認証キーで登録する処理
onButtonEntry = function()
{
    // メールアドレスを登録するサーバープログラム
    var url = '/regist-user';
    var params = {
        mail: $('#input-mail').val(),
        auth_key: $('#input-auth_key').val()
    };
    $.post({
        url: url,
        data: params,
    }).then(function(response){
        // 略
    });
}

30分有効な認証キーをどう管理する?

ランダムな認証キーは乱数で構成すれば簡単にできますが、作成した認証キーをどのように管理するのか?
というのが今回のテーマです。
Session と Cookie を使って、ブラウザ側に記憶させる、という方法もあるでしょうけど、クライアント側に覚えさせるのは、ちょっと抵抗を感じてしまいます。
んでは、サーバー側で対応するとすると、どうすべきでしょう?
データベースに一時ユーザーテーブルを用意しておく、という方法が一般的のようです。

一定時間を経過したら、その情報を削除しなければならないわけで、それには cron でシェル作ってDBの作成時間を見て delete、と。
と考えるとですね、おっさんプログラマにはリソースの膨大な無駄遣いのように思えて仕方がないのですよ(笑)

サーバー側の認証キー送信処理(/regist-temporary)では以下の手続きが必要になります。
・認証キーを生成する。
・メールアドレスに認証キーを送信する。
・メールアドレスに対して発行した認証キーを記録する。
・30分経っても登録されない場合、認証キーを削除する。
サーバー側の登録処理(/regist-user)は以下の手続きが必要です。
・認証キーが無効になっていないかを判断する。
・メールアドレスと認証キーが一致するか判断する。
・本来の登録処理を行う。
という流れなんですが、リソースの使用を最小限に留めて、サクサク動くようにできないもんでしょうかね。

/dev/shm を使う!

そこで、思いついたのが、一般的な Linuxサーバに必ず用意されていて、あまり利用されている風ではない、/dev/shm (RAMディスク)を利用する方法です。
1.ユーザーごとに生成した認証キーを /dev/shm/サービス名/temp_auth/ ディレクトリに記録しておく。
2.登録時には、記録内容を照合する。
3.cron で記録ディレクトリの一定時間経過したファイルを削除する。
という方法ですね。

メールアドレスごとに認証キーを生成して、RAMディスクに記録する

// メールアドレスごとの認証キーを生成して、送信する処理
function send_auth_key($mail)
{
    // ランダムな認証キーを生成
    $auth_key = $this->create_auth_key();
    $path = '/dev/shm/service/temp_auth';
    $rc = mkdir($path, 0777, true);
    if(!$rc)
    {
        //エラー処理
    }
    //メールアドレスを Base64(URL)エンコード
    $mail_hash = $this->Base64UrlEncode($mail);
    // 拡張子に .key を追加(なんとなくw)
    $fname =  $mail_hash . '.key';
    $filename = "$path/$fname";
    $rc = (0 < file_put_contents($filename, $auth_key));
    if($rc)
    {
        $rc = chmod($filename, 0666);
        if($rc)
        {
            // 認証キーをメールで送信(省略)
            $rc = $this->send_auth_key($mail, $auth_key);
        }
    }
}

// ランダムな認証キー生成
function create_auth_key($column = 6)
{
    $authKey = '';
    $digits = '0123456789';

    for ($i = 0; $i < $column; $i++) {
        $authKey .= $digits[rand(0, strlen($digits) - 1)];
    }

    return $authKey;
}

// Base64URL encoder
function Base64UrlEncode($data) { 
    return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); 
} 
  
// Base64URL decoder
function Base64UrlDecode($data) { 
    // = 埋めしなくてもいける
    return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); 
} 

メールアドレスを BASE64エンコードするのは、メールアドレスの中にファイル名に使用できない文字が含まれるかもしれないのを避けるためですね。
BASE64で利用される +と/ の記号は -と_ に変換することで、ファイル名として機能するようにしています。
あと、chmod 0666 しておくのは、apache と cron でアクセスしてくるユーザーが異なるためです。

一時ファイルを一定時間後に削除する、のはcron で以下のように設定しました。(例は3分ごとに実行してるので、少しタイムラグあります)

#crontab で認証キーの制限時間超えたファイルを消す
*/3 * * * * find /dev/shm/service/temp_auth/ -name '*.key' -mmin +30 -delete > /dev/null 2>&1

実際に認証キーとメールアドレスが送られてきたときの処理コードを入れておきます。

function checkAuthKey($mail, $auth_key)
{
    $rc = false;
    $error_message = '認証キーが異なっているか、または期限が切れています';
    $path = '/dev/shm/service/temp_auth';
    $mail_hash = $this->Base64UrlEncode($mail);
    $fname =  $mail_hash . '.key';
    $filename = "$path/$fname";
    $saved_key = false;
    if(file_exists($filename))
    {
        $saved_key = file_get_contents($filename);
    }
    if($saved_key)
    {
        $rc = (strcmp($auth_key, $saved_key) == 0);
    }
    return [$rc, $filename, $mail_hash, $error_message];
}

まとめ

今回はサーバー側で一時的な認証キーを生成してメールアドレスの確認と登録処理に至るプロセスで、Cookieやデータベースを使わない方法をまとめてみました
こういった需要って、よくあることだと思うので、参考にしてくださいまし。
実際、サクサク動いてくれるし間違いも起こりにくいので、安全に運用できています。
めでたしめでたし。

2022年6月17日金曜日

Outlookで行間の改行が広くなるのを防ぐ

Outlookで作成されて送られてくるHTML形式のメール、行間が広くなってることがありますね。
メールの書式設定で、行間が多めに取られているのが原因のようです。
以下の方法で行間を狭くすることができます。
1.Outlook で「新しいメール」を作成する。
2.メールの本文に適当に何か書く。
3.リボンメニューの「書式設定」タブをクリックし「スタイルの変更」のサブメニューから 「段落の間隔」-「段落間隔なし」または「狭い」をクリック。

4.「スタイルの設定」メニューの「既定に設定」をクリック。

5.どうもこれだけではダメっぽいとの情報がありました。段落も設定したほうがいいようです。

段落前と段落後を「0行」に設定。
行間を「最小値」に設定します。
間隔は「12 pt」にしました。

とりあえず、これで行間を小さくできます。

むーん・・・DIVタグ入れてるのに表示がずれるなぁ・・・

2017年12月21日木曜日

先ほどのメールの添付ファイルのパスワードは***

メールの添付ファイルは、暗号化して添付すること。
暗号化パスワードは別メールで送付すること。

といったルールで運用されている企業が多いのはご存知のことと思います。
特に電器系のメーカーさんとかは全社でルール化されているようです。

主な理由は『メールが漏えいした場合に、重要なファイルの内容が漏えいすることを防ぐ』というものなのでしょう。
もし、メールが漏えいしているのであれば、別メールで送られたパスワード通知メールも当然漏えいしているわけですから、添付ファイルを暗号化してそのパスワードを別メールで送るなんてナンセンス極まりない。
送信時の手順もめんどくさいし、メールを受け取られた受信者側にもパスワードを入力してファイルを展開させる必要があるので、めんどくさいし失礼ではないか!
という御意見はもっともだと思います。
https://seasoft.co.jp/products/BizAttacher.html
たしかにご指摘の点についてはもっともではあります。
でも、この面倒な手順が事故を未然に防ぐことに一役買っていることも否定できません。

メールの送信事故というのは毎日のように発生しています。
・ファイルを添付するのを忘れて送信してしまった。
・複数の宛先あてのメールを Bcc ではなく To で送ってしまった。
・個人情報を含んだファイルを第三者に送ってしまった。
・見積書のファイルを別のライバル社に送ってしまった。
などなど。
メールを操作している方ならば、心当たりが少なからずあるはずです。

ファイルの暗号化は「メールが漏えいした場合の対策」だけではなく、メールの利用者がこういった人為的なミスを犯した場合にできるだけ事故の被害を少なくするのにとても有効です。

『自分はそんなミスはしないから大丈夫』

個人で運用しているメールであれば、そういった自信があれば問題ありません。
が、会社や団体などの組織の場合、『すべての人が何のミスもなくメールを運用している!』という保証はどこにもありません。

大手企業ならまだしも、多くの組織にはセキュリティの監視をするための部署や担当者さえいないのが現状で、誤送信の事故は頻繁に発生しています。

添付ファイルを暗号化しておいて、パスワードメールを別に送るようにルール化することは必ずしも無駄とは限らないのです。

Outlookをご利用の場合
https://seasoft.co.jp/products/BizAttacher.html

Becky!をご利用の場合
https://seasoft.co.jp/products/BkBizAttacher.html

これらのソフトで誤送信の事故を未然に防ぐのはいかがでしょう?
以上、CMでした。