FreeBSDで運用する(その12)

随分と久しぶりだ。
諸事情があって若干田舎に引っ越したため、光ケーブルの引き込みに2ヶ月弱掛かってしまった。
お陰で、自分と家族は「インターネットから切り離されて生きることが難しいことを自覚する」と言う貴重な体験をした。


前回は、会社内外から送信された影舞レポートメールを影舞ユーザで受信し、内容が正しければwwwユーザに転送すると言う説明をした。
影舞ユーザの登録はrootユーザで次のように行う。

# adduser
Username: kagemai
Full name: Bug Tracking System Kagemai
Uid (Leave empty for default):
Login group [kagemai]:
Login group is kagemai. Invite kagemai into other groups? []:
Login class [default]:
Shell (sh csh tcsh bash fdsh nologin) [sh]: nologin
Home directory [/home/hoge]: /usr/local/www/htdocs/kagemai
Use password-based authentication? [yes]:
Use an empty password? (yes/no) [no]:
Use a random password? (yes/no) [no]:
Enter password:*****
Enter password again:*****
Lock out the account after creation? [no]:
Username   : kagemai
Password   : *****
Full Name  : Bug Tracking System Kagemai
Uid        : 1002
Class      :
Groups     : kagemai
Home       : /usr/local/www/htdocs/kagemai
Shell      : /usr/sbin/nologin
Locked     : no
OK? (yes/no):y

qmail上で影舞ユーザがメールを受信したときに、指定したプログラムを実行するためには~kagemai/.qmailを次のように記述する。

#↓これはデバッグ用
#|cat >> ~/mail.txt
#↓これはプログラム実行用
|ruby ~/mailtrn.rb

そして、影舞ユーザがメールを受信したときに実行するrubyスクリプトの~kagemai/mailtrn.rbを以下のように記述する。

#!/usr/bin/env ruby

=begin
    このスクリプトは影舞をインストールしたディレクトリ直下に置くこと。
    kagemai@DOMAIN.co.jp → kagemai@KAGEMAI.LOCALDOMAIN.co.jp に送信
    されたメールを www@KAGEMAI.LOCALDOMAIN.co.jp に転送し、そちらで
    影舞へのバグ報告を投稿する。
    これは、CGIの実効ユーザである www が正常に読み書きできるようにす
    るために、影舞ディレクトリの所有者を www に設定しており、影舞への
    バグ報告メールの受け手である kagemai では、直接書き込めないファイ
    ルやディレクトリが存在するからである。
=end

$kagemai_root = File.dirname(File.expand_path(__FILE__)).untaint # setup
config_file = "#{$kagemai_root}/html/kagemai.conf" # setup

$:.unshift("#{$kagemai_root}/lib")
require 'kagemai/config'
Kagemai::Config.initialize($kagemai_root, config_file)

require 'nkf'
require 'base64'
require 'kagemai/mail/mailer'

$transfer = "www@KAGEMAI.LOCALDOMAIN.co.jp"
$magic_guid = "{322A5CD3-8A74-4c2a-A988-18B65C57CE6B}"
$recv_email = "kagemai@DOMAIN.co.jp"
$my_email = "kagemai@KAGEMAI.LOCALDOMAIN.co.jp"
$project_dir = "#{$kagemai_root}/project"

=begin
    strを影舞をインストールしたディレクトリ直下のdebug.txtに出力する。
=end
def debug(str)
    open("#{$kagemai_root}/debug.txt", "a") do |file|
        file.puts str
    end
end

=begin
    strからメールアドレスを取り出す。
    具体的には "name" <name@domain> や name <name@domain> などから
    name@domain を取り出す。
    それ以外のパターンはそのまま返す。
=end
def get_email(str)
    if (str =~ /.*<(.+?)>/)
        return $1
    else
        return str
    end
end

=begin
    nameに一致するプロジェクトを検索する。
=end
def match_project(name)
    Dir["#{$project_dir}/*"].each do |dir|
        dir.gsub!("#{$project_dir}/", "")
        if (name == dir)
            return true
        end
    end
    return false
end

=begin
    メールの To が影舞ユーザか?
    Subject が "[PROJECT[:<BTS#>]]"(例:"[test:263]")に一致するか?
    をチェックする。
=end
def check_mail(lines)
    project = ""
    lines.each do |line|
        if (line =~ /^To: /)
            email = get_email($')
            if (email != $recv_email)
                return "invalid recipient: #{email}"
            end
        elsif (line =~ /^Subject: /)
            subject = Base64.decode_b($')
            subject = NKF::nkf("-m0j", subject)
            if (subject =~ /^.*?\[(.+?)[:\]]/)
                project = $1
                if (!match_project(project))
                    return "unknown project: #{project}"
                end
            else
                return "subject not include project name: #{subject}"
            end
        end
    end
    if (project == "")
        return "mail not include subject"
    end
    return ""
end

=begin
    WWWユーザにlinesのメールを転送する。
=end
def transfer_mail(lines)
    body = ""
    body += "Delivered-To: #{$my_email}\n"
    # $magic_guidでWWWユーザに影舞ユーザから送信していることを通知
    body += "X-Kagemai-Mail-Transfer: #{$magic_guid}\n"
    lines.each do |line|
        body += "#{line}\n"
    end
    smtp = Kagemai::SmtpMailer.new
    smtp.sendmail(body, $recv_email, $transfer);
end

=begin
    lines からメールアドレスを取り出す。
=end
def get_ret_email(lines)
    level = 0
    email = ""
    lines.each do |line|
        cur_level = 0
        cur_email = ""
        if (line =~ /^Return-Path: /)
            cur_level = 10
            cur_email = get_email($')
        elsif (line =~ /^From: /)
            cur_level = 20
            cur_email = get_email($')
        elsif (line =~ /^Reply-To: /)
            #cur_level = 30
            #cur_email = get_email($')
            return get_email($')
        elsif (line == "")
            return email
        end
        if (cur_level > level)
            level = cur_level;
            email = cur_email;
        end
    end
    return email
end

=begin
    エラーメールを通知する。
=end
def error_mail(lines, errmsg)
    email = get_ret_email(lines)
    if (email == "" || email == $my_email || email == $recv_email)
        return;
    end
    debug("error mail to #{email}\n");
    body = ""
    body += "From: #{$recv_email}\n"
    body += "To: #{email}\n"
    body += "Subject: kagemai mail error\n"
    dup_item = ["Content-Type", "Content-Transfer-Encoding"]
    lines.each do |line|
        if (line == "")
            break
        else
            dup_item.each do |item|
                if (line =~ /^#{item}: /)
                    body += "#{line}\n"
                end
            end
        end
    end
    body += "\n"
    body += "#{errmsg}\n"
    body += "\n"
    body += "--- original message ---\n"
    body += "\n"
    in_header = true
    dup_item = ["From", "To"]
    lines.each do |line|
        if (in_header)
            if (line == "")
                in_header = false
                body += "\n"
            elsif (line =~ /^Subject: /)
                subject = Base64.decode_b($')
                subject = NKF::nkf("-m0j", subject)
                body += "Subject: #{subject}\n"
            else
                dup_item.each do |item|
                    if (line =~ /^#{item}: /)
                        body += "#{line}\n"
                    end
                end
            end
        else
            body += "#{line}\n"
        end
    end
    smtp = Kagemai::SmtpMailer.new
    smtp.sendmail(body, $recv_email, email);
end

#ここ↓からメイン
lines = STDIN.read.split(/\n/) # メールを文字列配列に読み込む
errmsg = check_mail(lines); # メールの内容をチェック
# debug("check mail: #{errmsg}\n")
if (errmsg == "")
    transfer_mail(lines) # エラーがなかったらWWWユーザに転送
else
    error_mail(lines, errmsg) # エラーだったらエラー通知を返信
end

exit 0

qmailのセキュリティでは、以下のようにファイルのパーミッションを変更して置かないと、正常に動作しないので注意する必要がある。

# cd ~kagemai
# chown kagemai:kagemai .qmail mailrcv.rb
# chmod 600 .qmail mailrcv.rb

これで、影舞ユーザの設定は完了である。