過去メールを Gmail に一括移行する


職場内のメール環境変更に対する圧力を軽減するために、全ユーザについて過去メールの移行を管理者側で行うことにしました。これはその時に使ったスクリプトです。ドメイン名等は変えてあります。

本スクリプトを流した後、Gmail の管理画面から IMAP による一括移行を行います。

なお、php 標準の IMAP 関数を使おうとするとc クライアントライブラリがインストールされていることだの--with-imap[=DIR] を指定して PHP をコンパイルする必要があるだのという前提事項が必要ですが、PEAR::IMAP ライブラリを使えばこれらが不要なので楽です。

一括移行支援スクリプト(gmail-migrate.php)

#!/usr/bin/php
<?php
//
//  gmail-migrate.php   2011.02.08 by M.Hotta
//      過去メールを Gmail に移行する準備を行う
//
//  使用法  gmail-migrate.php < XXXX.csv(書式:id,id,pass)
//
//  前提:
//      1.本スクリプトは現行 IMAP サーバとは別に建ててある Gmail 移行用
//        IMAP(Dovecot)サーバ上で動作する。
//      2.dovecot は(本番用ではなく)移行用の ldap を見ており、ここでは
//        全ユーザで同じパスワードを設定してある。
//      3.csv ファイルの書式は Gmail の IMAP 移行指示に従ったもの
//        (ドメインの管理→高度なツール→メール (IMAP) 移行を設定)
//  処理内容:
//      指定された CSV の内容に従って、該当ユーザの
//      1.旧 LDAP の mail 属性に Gmail 転送用アドレスを追加
//      2.テストメールを送信し、Gmail へ転送されることを確認する
//      3.旧 LDAP の mail 属性を Gmail のみにする
//      4.Maildir を本番 IMAP サーバから移行用サーバにコピーする。その際、
//        インデックスの不整合を防ぐために dovecot index は除外する。
//      5.移行用IMAPサーバで dovecot index を再作成する
//        (Gmail の IMAP 接続テストでタイムアウトになるのを防ぐため)
//
ini_set('mbstring.internal_encoding', 'UTF-8');
require_once "Net/IMAP.php";
require_once "Net/LDAP2.php";
$official_domain = '@example.com';    //  generic mail domain
$gmail_domain = '@g.example.com';     //  subdomain for gmail
$mta = "ipsum.example.com";           //  mail relay server
$ldap_host = "hiace.example.com";
$binddn = 'cn=Manager,dc=example,dc=com';
$bindpw = 'naisho';
$basedn = 'dc=example,dc=com';
$admin_email = "support$official_domain";
$mailbody = "/home/admin/mailbody.txt";

function    cmd_exec($cmd)
{
    echo $cmd."\n";
    return  `$cmd`;
}   //  cmd_exec()

$ldap_config = array(
    'host'              =>  $ldap_host,
    'port'              =>  389,
    'version'           =>  3,
    'starttls'          =>  false,
    'binddn'            =>  $binddn,
    'bindpw'            =>  $bindpw,
    'basedn'            =>  $basedn,
    'filter'            =>  '(objectClass=*)',
    'scope'             =>  'sub',
);
//-----------------------
//  LDAPサーバへの接続
//-----------------------
$ldap = Net_LDAP2::connect($ldap_config);
if (Net_LDAP2::isError($ldap)) {
    die(sprintf("LDAP CONNECT FAILED: %s\n", $ldap->getMessage()));
}
$ldap->bind();
if (Net_LDAP2::isError($ldap)) {
    die(sprintf("LDAP BIND FAILED: %s\n", $ldap->getMessage()));
}
//----------------------
//  IMAPサーバへの接続
//----------------------
$imap = new Net_IMAP('localhost', 143, false);
if (PEAR::isError($imap)) {
    die(sprintf("IMAP Connection FAILED: %s\n", $imap->getMessage()));
}
//-------------------------------
//  標準入力から CSV を読み込む
//-------------------------------
$start = true;
while ($line = fgets(STDIN))    {
    $line = rtrim($line);   //  remove LineFeed
    list($user,$dummy,$pass) = split(',', $line);
    if ($start) {
        $start = false;
        if ($user != 'username')    {
            die(sprintf("BAD FORMAT csv file.\n"));
        }
        continue;       //  Skip header
    }
    //---------------------------
    //  uid により LDAP 検索
    //---------------------------
    $filter = Net_LDAP2_Filter::create('uid', 'equals', $user);
    $requested_attributes = array('campuscode', 'mail', 'mailquota');
    $search = $ldap->search(null, $filter, 
        array('attributes' => $requested_attributes));
    if (Net_LDAP2::isError($search)) {
        die(sprintf("LDAP search FAILED: %s\n", $search->getMessage()));
    }
    //----------------------------
    //  Gmail への転送設定を追加
    //----------------------------
    $entry = $search->shiftEntry();
    $mail = $entry->getValue('mail');
    $campus = $entry->getValue('campuscode');
    $npucapamail = $entry->getValue('mailquota');
    printf("Current mail value: %s\n", $mail);
    $gmail = strtolower("$user$gmail_domain");
    $subject = strtolower($mail);
    if (preg_match("/$gmail/", $subject))   {
        printf("gmail address exists. Do nothing.\n");
    } else  {
        $new_mail = $mail . ',' . $gmail;
        $result = $entry->replace(array('mail' => array($new_mail)));
        if (Net_LDAP2::isError($result)) {
            die(sprintf("LDAP modify FAILED: %s\n", $search->getMessage()));
        }
        $entry->update();
        sleep(3);       //  wait for LDAP data propagation
    }
    //-----------------------------------------------
    //  キャンパス番号により MailBox サーバ名を決定
    //-----------------------------------------------
    switch ($campus)    {
    case    '1':
        $imap_server = 'presage';
        break;
    case    '2':
        $imap_server = 'cresta';
        break;
    default:
        die(sprintf("Unknown Campus: %s\n", $campus));
    }
    //----------------------------
    //  テストメールを送信
    //----------------------------
    $FROM   =   $admin_email;
    $TO     =   "$user$official_domain";
    $SUBJECT=   "TEST for $TO";
    $HEAD   =   "From: $FROM";
    $body   =   "This is TEST. Please ignore.";
    mb_send_mail($TO, $SUBJECT, $body, $HEAD);
    //----------------------------
    //  テストメールの通過を確認
    //----------------------------
    $output = cmd_exec("ssh $mta grep $gmail /var/log/maillog");
    if (!preg_match('/GOOGLE.COM/', $output))   {
        die("Can't confirm mail transfer\n");
    }
    //----------------------------------------
    //  Gmail 切り替え済みの案内メールを送る
    //----------------------------------------
    $SUBJECT="Gmailへの切替が完了しました";
    $body = "$user 様\n\n" .  file_get_contents($mailbody);
    mb_send_mail($TO, $SUBJECT, $body, $HEAD);
    sleep(3);       //  wait for mail transfer
    //-------------------------------------
    //  メールアドレスを Gmail のみにする
    //  また、メール容量制限値を大きくしておく
    //-------------------------------------
    $result = $entry->replace(array(
        'mail'        =>  array($gmail),
        'mailquota'   =>  999999));
    if (Net_LDAP2::isError($result)) {
        die(sprintf("LDAP modify FAILED: %s\n", $search->getMessage()));
    }
    $entry->update();
    //---------------------------------------
    //  MailBox サーバから Maildir をコピー
    //---------------------------------------
    cmd_exec("sudo rm -rf /home/$user");    //  just in case
    cmd_exec("sudo rsync -az -e ssh --exclude='dovecot*' --bwlimit=102400 $imap_server:/home/$user /home");
    //-------------------------------------------------------------------
    //  IMAP サーバにログインしてメール一覧を取得(インデックス再作成)
    //-------------------------------------------------------------------
    $imap->login($user, $pass);
    if (PEAR::isError($imap)) {
        die(sprintf("IMAP Login FAILED: %s\n", $imap->getMessage()));
    }
    printf("user=%s: IMAP login OK\n", $user);
    $msgs = $imap->getMessagesList();
    if (PEAR::isError($msgs)) {
        die(sprintf("IMAP getMessagesList FAILED: %s\n", $msgs->getMessage()));
    }
    printf("user=%s: got %d messages\n", $user, count($msgs));
}   //  while()
  

ソース中で読み込んでいるメールの文面ファイル(mailbody.txt)

情報センター Gmail 移行担当です。

Gmail早期移行のお申し込みありがとうございました。
Gmailが有効になりましたのでご利用ください。
Gmailへの入口は https://mail.google.com/a/example.com/ です。
Campusインデックスにもリンクを置いてあります。
 
これ以降、旧学内メール(ThunderBird/Active!mail)には新着メールが
届かなくなりますのでご注意ください。
また、これより過去メールの移行(コピー)を開始します。
過去メールの移行には、数時間~最大3日程度かかる予定です。
過去メールの移行中もGmailをご利用いただけます。
 
過去メールは Migrated というラベルがついた形で、徐々に皆さんのメール
ボックスに取り込まれると思います。また、過去メールへの返信の形で
届いた新着メールにも Migrated ラベルがつくことがあるようです。
情報センターでは過去メールの移行完了を個別には監視いたしませんので、
移行完了のメールは差し上げません。
 
なお、学内メールの転送設定は自動移行されないのでご注意ください。
必要であれば、Gmail 側で再度メールの転送設定を行ってください。
 
Gmail について、簡単なマニュアルを 
http://pukiwiki.example.com/index.php?Gmail%2F%A5%DE%A5%CB%A5%E5%A5%A2%A5%EB
に置いておりますのでご一読ください。

以上です。もし何か不都合などありましたらお知らせください。
  
up