仕事納め 2019

今年(2019年)は昨日(12月28日)で仕事納め。年明け早々にメールサーバ移行というミッションが控えているので、全然納まった感はありません。これも「月額料金の安いさくらインターネットのVPSサーバを使い続けているツケが回ってきたのだ」としか言われないのでしょう。はい、そのことに異論はなく、自分もそう思います(笑)。

さて、仕事納めの日に、会社のWebページが数年振り(いや10数年振りかも?)にリニューアルされました。そして制作途中から危惧していた通り、盛大にリンク切れ発生...。とてもIT系の会社とは思えないやっつけ仕事感満載です。リンク切れチェックなどはWebsite Explolerを使えば楽に検出できるのに、残念ながら何もやっていないのです...。

前述したメールサーバ移行も、メールサーバ自体は後輩が構築しましたが、正常に動作するか?はチェックされていません。実際に切り替えるまでは不具合がわからない状態のまま、バックアップサーバとして放置されています。で、私が後輩の設定した内容の粗探しをし、問題点を見つけて修正、2020年1月19日まで間に合わせることになっています。下手したらOSを初期化して、最初からメールサーバを構築しなおした方が早い場合もありえることは、主任会議の場において、会社役員と同席した営業に対して通知済みです。おそらく「そんな話は聞いていない」と言うのでしょうが(人間は自分に都合の良いことしか覚えていない生き物です)。

実際問題として、テストもしないで実戦投入は出来ません。「こういうテストをして動いた」という根拠を残さないと、不具合が発生したとき袋叩きの憂い目に遭います。が、会社を去る身としては、何が起きても知らんこっちゃないわ...というのも理解していただけるとは思います(だから辞めると決まった人に重要な仕事を振ってはいけないのです)。

では、どうやって実際にメールサーバを切り替える前(MXレコードを書き換える前)にテストをするか?その答えはテスト用プログラム(コード)を書けば良いのです。最近ではサーバエンジニアであっても、ある程度のプログラミングスキルを求められるのはこういった事があるからです。サーバの管理ツールがWebブラウザで動いているのもそうです。

メールサーバのテストについては最低でも以下3点を確認する必要があります。

  1. 当該メールサーバから外部メールサーバへメールが送信できること。SMTP認証を通過したユーザのみMail Relayできること(メールはInsideからOutsideへ)
  2. 外部メールサーバから当該メールサーバへメールの送信(受信)が出来ること。また添付ファイルが正しく処理されること(メールはOutsideからInsideへ)
  3. メールボックスにあるメールをユーザ認証後に読み出せること

25番、110番、587番ポートを使った暗号化通信(STARTTLS)や、465番ポートを使ったSMTP over SSL、995番ポートを使ったPOP3 over SSLなどもサービスを実装および提供しているのであればテストした方が良いでしょう。

(1)メールサーバの587番ポートに接続してユーザ認証を行い、メール送信するPerlスクリプト

#!/usr/bin/perl

use strict;
use utf8;
use Encode;
use Net::SMTP;
use Authen::SASL;

my $server   = 'テストするメールサーバのホスト名(またはIPアドレス)';
my $userfile = shift;
my $from;
my $to       = '送信先メールアドレス';
my $user;
my $password;
my $subject  = 'テストメール';
my $header;
my $smtp;

my $contents = '
テキストのメールです。
この内容がメール本文に含まれます。
通常はフォームメールの送信フォームなどから引き渡されます。
';

$contents = Encode::encode( 'iso-2022-jp', $contents );
$subject  = Encode::encode( 'MIME-Header-ISO_2022_JP', $subject );

die "Usage: $0 [file]\n" unless $userfile;
open(IN, $userfile) || die "can't open file: $!\n";

while(<IN>){
    s/[\r\n]*$//;
    ($user, $password) = split(/[\t\x20]+/, $_, 2);
    if ( $user =~ /^#|^$/  or $password =~ /^$/) { next; }
    my $message;

    $from   = "$user";
    $header = "From: $from\n";
    $header .= "To: $to\n";
    $header .= "Subject: $subject ( $from )\n";
    $header .= "MIME-Version: 1.0\n";
    $header .= "Content-type: text/plain; charset=ISO-2022-JP\n";
    $header .= "Content-Transfer-Encoding: 7bit\n\n";

#    print Encode::encode( 'utf-8', "$from: SMTP(MSA)接続 " );
    $smtp = Net::SMTP->new( $server, Port=>587 );
    if ( $smtp ) {
#        print Encode::encode( 'utf-8', "→ 成功 → SMTP認証 " );
        if ( $smtp->auth( $from, $password ) ) {
#            print Encode::encode( 'utf-8', "→ 成功 " );
            $smtp->mail( $from );
            $smtp->to( $to );
            $smtp->data();
            $smtp->datasend( $header );
            $smtp->datasend( $contents );
            $smtp->dataend();
            $smtp->quit;
            $message = "Mail送信完了";
        } else {
            $message = "Mail送信失敗(Abort)";
        }
    } else {
        $message = "SMTP(MSA)接続失敗(Abort)";
    }
    printf("%-50s%s\n",$user, Encode::encode( 'utf-8', $message) );
}
close(IN);
exit;

このスクリプトはSMTP認証とメール中継のテストを行います。スクリプト引数(第1引数)としてテキストファイルを指定します。テキストファイルのフォーマットはTSVで第1フィールドにアカウント(メールアドレス)、 第2フィールドにパスワードとなります(Excelにちゃちゃっと入力してコピペすれば簡単に出来上がります)。

[email protected]<TAB>password1
[email protected]<TAB>password2
[email protected]<TAB>password3

アカウント=メールアドレスでない場合、適宜コードを修正してください(それほど難しい話ではないですよね?)。

チェックするポイントは「ユーザ認証後、当該メールサーバから外部メールサーバへ、メールが送信(中継)されるか?」です。

(2)メールサーバの25番ポートに接続して添付ファイルつきのメールを送信(受信)させるPerlスクリプト

#!/usr/bin/perl

use strict;
use utf8;
use Encode;
use Net::SMTP;
use MIME::Lite;

my $server   = 'テストするメールサーバのホスト名(またはIPアドレス)';
my $userfile = shift;
my $from     = '送信元メールアドレス';
my $to;
my $user;
my $password;
my $subject  = 'テストメール';
my $contents = '
テキストのメールです。
この内容がメール本文に含まれます。
通常はフォームメールの送信フォームなどから引き渡されます。
';

$contents = Encode::encode( 'iso-2022-jp', $contents );
$subject  = Encode::encode( 'MIME-Header-ISO_2022_JP', $subject );

die "Usage: $0 [file]\n" unless $userfile;
open(IN, $userfile) || die "can't open file: $!\n";

while(<IN>){
    s/[\r\n]*$//;
    ($user, $password) = split(/[\t\x20]+/, $_, 2);
    if ( $user =~ /^#|^$/  or $password =~ /^$/) { next; }
    my $message;
    $to = $user;

    # コンテナを作成
    my $msg = MIME::Lite->new(
        From => $from,
        To => $to,
        Subject => $subject,
        Type => 'multipart/mixed',
    );

    # メッセージ部分
    $msg->attach(
        Type => 'text/plain; charset="iso-2022-jp"',
        Encoding => '7bit',
        Data => $contents,
    );

    # 添付ファイル
    $msg->attach(
        Type => 'application/octet-stream',
        Disposition => 'attachment',
        Path => "ファイルへのパス(フルパス)",
        Filename => 'ファイル名',
    );
 
    # 送信
    print Encode::encode( 'utf-8', "$from → $to: 添付メール送信:" );
    if ($msg->send('smtp', $server, Debug=>0)) {
       print Encode::encode( 'utf-8', "成功\n" );
    } else {
       print Encode::encode( 'utf-8', "失敗\n" );
    }
}
close(IN);
exit;

このスクリプトは添付ファイルつきメールの送信(受信)テストを行います。スクリプト引数(第1引数)としてテキストファイルを指定します。送信元メールアドレスの元データとなるテキストファイルは(1)メールサーバの587番ポートに接続してユーザー認証を行い、メール送信するPerlスクリプトで作成したファイルを使い回します。もちろん別に作っても構いません。

チェックするポイントは「外部サーバからメールを受信後、添付ファイルが適切(正常)に処理されるか?」です。ファイルには画像ファイルを指定したり、ExcelやWordファイル、あるいはテスト用ウィルスを指定したりします。受け取った添付ファイルが正しく開けるかどうか?また、ウィルスの場合はユーザへ配送される前に検知して、駆除や隔離など適切に処理される必要がありますよね?

実際問題として実行するサービス(デーモン)のUIDやGIDが適切でなかったり、作業用ディレクトリのUIDやGIDやパーミッションが適切でなかったりした結果、添付ファイルを処理できずにエラーで受信できなかったケースが過去にありました。「添付ファイル無しのメールは届くのに、添付ファイル付きのメールは届かない」というクレームがきて、慌てて対処したことがあります...。メールサーバでウイルス対策やスパム対策をしている場合、それぞれ別のサービス(デーモン)と連携していることがほとんどなので、それぞれの設定が適切でないと動きません(ここがノウハウというか経験値がものをいうところ)。

(3)メールサーバの110番ポートに接続してユーザ認証を行うPerlスクリプト

#!/usr/bin/perl

use strict;
use utf8;
use Encode;
use Net::POP3;
use Email::MIME;

my $server = '153.126.197.240';
my $userfile = shift;
my $layout = << 'TMPL';
[TITLE]
%s

[BODY]
%s


TMPL

die "Usage: $0 [file]\n" unless $userfile;
open(IN, $userfile) || die "can't open file: $!\n";

while(<IN>){
    s/[\r\n]*$//;
    my ($user, $password) = split(/[\t\x20]+/, $_, 2);
    if ( $user =~ /^#|^$/  or $password =~ /^$/) { next; }

#    print Encode::encode( 'utf-8', "$user: POP3接続 " );
    my $pop = Net::POP3->new( $server, Port=>110 );
    my $result = $pop->login( $user, $password );
    my $message;

    if ( $result ) {
        if ( $result == "0E0" ) {
            $message = "認証成功(0)件";
        } else {
            $message = "認証成功($result)件";
            my $list_href = $pop->list;        # メール一覧のハッシュのリファレンスを得る
            foreach my $msg_id ( keys %{ $list_href } ) {
                my $message = join q(), @{ $pop->get( $msg_id ) }; # メール本文を取得
                my $parsed  = Email::MIME->new($message);
                print sprintf $layout,
                    Encode::encode( 'utf8', $parsed->header('Subject') ),
                    Encode::encode( 'utf8', Encode::decode( 'jis', $parsed->body ) );

#                $pop->delete( $msg_id );      # 受信したメールを削除する
            }
        }
    } else {
        $message = "認証失敗";
    }
    printf("%-50s%s\n",$user, Encode::encode( 'utf-8', $message) );
    $pop->quit;
}
close(IN);
exit;

このスクリプトはユーザ認証およびメールの受信テストを行います。スクリプト引数(第1引数)としてテキストファイルを指定します。ユーザ認証に使うテキストファイルは(1)メールサーバの587番ポートに接続してユーザー認証を行い、メール送信するPerlスクリプトで作成したファイルを使い回します。もちろん別に作っても構いません。

チェックするポイントは「ユーザ認証が正しく行われ、メールの受信(表示)が行われるか?」です。

サーバエンジニアは評価されない悲しい職種

ここまで細かいことをやっても「そんな事は当たり前だ」と言われ、トラブルが起きれば「何やってんの!」と叱責・詰問される悲しき職種なのです。転職においては「サーバのことがそこそこ分かる、ネットワークこともそこそこも分かる、簡単なプログラム(コード)も書ける」のだけれど、スペシャリストじゃないから要らないとか言われたり...。