FuelPHPのEmailパッケージをS/MIMEに対応させる
FuelPHPのEmailパッケージをS/MIMEに対応させるお話です。(今回は電子署名のみ)
Emailパッケージを拡張するのには、以下のような方法で行いました。
#! /bin/blog: [FuelPHP]ネームスペースを上書きしてパッケージのクラスを書き換える
変更が必要なのは Email\Email_Driver
だけなので、Emailパッケージから必要なファイルをコピーして以下のような構成を作ります。
fuel └ packages └myemail ├─ bootstrap.php |─ classes | └─ email | └─ driver.php └─ config └─ email.php
bootstrap.php は以下のように指定して、コピーした方のファイルでEmail_Driverを上書きします。
また、fuel/app/config 以下の config.php のalways_load に myemail を追加するのも忘れずに。
<?php
\Autoloader::add_core_namespace('Email');
\Autoloader::add_classes(array(
/**
* Email classes.
*/
'Email\\Email_Driver' => __DIR__.'/classes/email/driver.php',
));
これで下準備は完了です。それでは、コピーしたファイルに変更を加えていきましょう。<br />
driver.php
は build_message メソッドを以下のように変更します。
<protected function build_message($no_bcc = false)
{
$newline = $this->config['newline'];
$charset = $this->config['charset'];
$encoding = $this->config['encoding'];
$headers = '';
$parts = array('Date', 'Return-Path', 'From', 'To', 'Cc', 'Bcc', 'Reply-To', 'Subject', 'Message-ID', 'X-Priority', 'X-Mailer', 'MIME-Version', 'Content-Type');
$no_bcc and array_splice($parts, 5, 1);
$this->config['smime']['enabled'] and array_splice($parts, -2, 2);
foreach ($parts as $part)
{
$headers .= $this->get_header($part);
}
foreach ($this->extra_headers as $header => $value)
{
$headers .= $header.': '.$value.$newline;
}
$headers .= $newline;
$body = '';
if ($this->type === 'plain' or $this->type === 'html')
{
if ($this->config['smime']['enabled'])
{
$body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
}
$body .= $this->body;
}
else
{
if ($this->config['smime']['enabled'])
{
$body .= $this->get_header('Content-Type').$newline;
}
switch ($this->type)
{
case 'html_alt':
$body .= '--'.$this->boundaries[0].$newline;
$body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->alt_body.$newline.$newline;
$body .= '--'.$this->boundaries[0].$newline;
$body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->body.$newline.$newline;
$body .= '--'.$this->boundaries[0].'--';
break;
case 'plain_attach':
case 'html_attach':
case 'html_inline':
$body .= '--'.$this->boundaries[0].$newline;
$text_type = (stripos($this->type, 'html') !== false) ? 'html' : 'plain';
$body .= 'Content-Type: text/'.$text_type.'; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->body.$newline.$newline;
$attach_type = (stripos($this->type, 'attach') !== false) ? 'attachment' : 'inline';
$body .= $this->get_attachment_headers($attach_type, $this->boundaries[0]);
$body .= '--'.$this->boundaries[0].'--';
break;
case 'html_alt_inline':
$body .= '--'.$this->boundaries[0].$newline;
$body .= 'Content-Type: text/plain'.'; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->alt_body.$newline.$newline;
$body .= '--'.$this->boundaries[0].$newline;
$body .= 'Content-Type: multipart/related;'.$newline."\tboundary=\"{$this->boundaries[1]}\"".$newline.$newline;
$body .= '--'.$this->boundaries[1].$newline;
$body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->body.$newline.$newline;
$body .= $this->get_attachment_headers('inline', $this->boundaries[1]);
$body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
$body .= '--'.$this->boundaries[0].'--';
break;
case 'html_alt_attach':
case 'html_inline_attach':
$body .= '--'.$this->boundaries[0].$newline;
$body .= 'Content-Type: multipart/alternative;'.$newline."\t boundary=\"{$this->boundaries[1]}\"".$newline.$newline;
if (stripos($this->type, 'alt') !== false)
{
$body .= '--'.$this->boundaries[1].$newline;
$body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->alt_body.$newline.$newline;
}
$body .= '--'.$this->boundaries[1].$newline;
$body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->body.$newline.$newline;
if (stripos($this->type, 'inline') !== false)
{
$body .= $this->get_attachment_headers('inline', $this->boundaries[1]);
$body .= $this->alt_body.$newline.$newline;
}
$body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
$body .= $this->get_attachment_headers('attachment', $this->boundaries[0]);
$body .= '--'.$this->boundaries[0].'--';
break;
case 'html_alt_inline_attach':
$body .= '--'.$this->boundaries[0].$newline;
$body .= 'Content-Type: multipart/alternative;'.$newline."\t boundary=\"{$this->boundaries[1]}\"".$newline.$newline;
$body .= '--'.$this->boundaries[1].$newline;
$body .= 'Content-Type: text/plain; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->alt_body.$newline.$newline;
$body .= '--'.$this->boundaries[1].$newline;
$body .= 'Content-Type: multipart/related;'.$newline."\t boundary=\"{$this->boundaries[2]}\"".$newline.$newline;
$body .= '--'.$this->boundaries[2].$newline;
$body .= 'Content-Type: text/html; charset="'.$charset.'"'.$newline;
$body .= 'Content-Transfer-Encoding: '.$encoding.$newline.$newline;
$body .= $this->body.$newline.$newline;
$body .= $this->get_attachment_headers('inline', $this->boundaries[2]);
$body .= $this->alt_body.$newline.$newline;
$body .= '--'.$this->boundaries[2].'--'.$newline.$newline;
$body .= '--'.$this->boundaries[1].'--'.$newline.$newline;
$body .= $this->get_attachment_headers('attachment', $this->boundaries[0]);
$body .= '--'.$this->boundaries[0].'--';
break;
}
}
if ($this->config['smime']['enabled'])
{
$tmp_dir = $this->config['smime']['tmp_dir'];
$infilename = 'email_'.md5($body.microtime());
$outfilename = $infilename.'_out';
$signcert = 'file://'.$this->config['smime']['signcert'];
$privkey = 'file://'.$this->config['smime']['privkey'];
$headers = substr($headers, 0, -strlen($newline)*2);
if ( ! is_writable($tmp_dir))
{
throw new \FuelException('Cannot write into directory "'.$tmp_dir.'"');
}
if ( ! is_readable($signcert) || ! is_readable($privkey))
{
throw new \FuelException('Cannot read certificate or key.');
}
if ($this->config['smime']['keypass'])
{
$privkey = array($privkey, $this->config['smime']['keypass']);
}
\File::create($tmp_dir, $infilename, $body);
openssl_pkcs7_sign($tmp_dir.$infilename, $tmp_dir.$outfilename, $signcert, $privkey, array($headers));
$body = \File::read($tmp_dir.$outfilename, true);
\File::delete($tmp_dir.$infilename);
\File::delete($tmp_dir.$outfilename);
$headers = '';
$body = strtr($body, array_fill_keys(array("\r\n", "\r", "\n"), $newline));
}
return array(
'header' => $headers,
'body' => $body,
);
}
config/email.php
には、以下のような設定を追加します。
'defaults' => array(
<省略>
/**
* S/MIME
*/
'smime' => array(
'enabled' => false,
'tmp_dir' => APPPATH.'tmp/',
'signcert' => '',
'privkey' => '',
'keypass' => '',
),
),
tmp_dir は、openssl_pkcs7_sign の入出力に必要なファイルが読み書きされます。適切なパーミッションが与えられたディレクトリを指定する必要があります。
証明書と秘密鍵はsigncert, privkeyでファイルパスを指定します。PEM形式じゃないとダメっぽいです。
詳しことは「PHP: OpenSSL キー/証明書パラメータ」をご覧ください。
これで設定が正しければS/MIME電子署名メールが送れるはずです。
注意としては、Emailのドライバがmail, smtp, sendmailでないとうまくいかないはずです。
mailgunだとWeb API経由で送信しており、こちらで組み立てたメール本文がそのまま配送されないためです。
mailgunでもSMTP経由で送信することも可能ですので、smtpドライバを用いてmailgunのSMTPサーバーで設定を行いましょう。