You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
6.9 KiB
PHP

<?php
class Email {
public $from;
public $subject;
public $body;
public $signature;
private $to = array();
private $cc = array();
private $bcc = array();
private $reply_to = array();
private $headers = array();
private $gpg_sign = true;
public function __construct() {
global $config;
$this->from = array('email' => $config['email']['from_address'], 'name' => $config['email']['from_name']);
$this->signature = $config['web']['baseurl']."\nLIAM system";
}
public function add_recipient($email, $name = null) {
$this->to[] = array('email' => $email, 'name' => $name);
}
public function add_cc($email, $name = null) {
$this->cc[] = array('email' => $email, 'name' => $name);
}
public function add_bcc($email, $name = null) {
$this->bcc[] = array('email' => $email, 'name' => $name);
}
public function add_reply_to($email, $name = null) {
$this->reply_to[] = array('email' => $email, 'name' => $name);
}
public function set_from($email, $name = null) {
$this->from = array('email' => $email, 'name' => $name);
$this->gpg_sign = false;
}
public function send() {
global $config;
if(!empty($config['email']['reroute'])) {
$rcpt_summary = '';
foreach(array('to', 'cc', 'bcc') as $rcpt_type) {
if(count($this->$rcpt_type) > 0) {
$rcpt_summary .= ucfirst($rcpt_type).":\n";
foreach($this->$rcpt_type as $rcpt) {
if(is_null($rcpt['name'])) {
$rcpt_summary .= " $rcpt[email]\n";
} else {
$rcpt_summary .= " $rcpt[name] <$rcpt[email]>\n";
}
}
}
}
$this->body = $rcpt_summary."\n".$this->body;
$this->to = array(array('email' => $config['email']['reroute'], 'name' => null));
$this->cc = array();
$this->bcc = array();
}
$this->headers[] = "MIME-Version: 1.0";
$this->headers[] = "Content-Transfer-Encoding: 8bit";
$this->headers[] = "Auto-Submitted: auto-generated";
$this->headers[] = "Precedence: bulk";
$this->flow();
$this->append_signature();
if(function_exists('gnupg_init') && $this->gpg_sign && isset($config['gpg']['key_id'])) {
$this->sign();
}
if(is_null($this->from['name'])) {
$this->headers[] = "From: {$this->from['email']}";
} else {
$this->headers[] = "From: {$this->from['name']} <{$this->from['email']}>";
}
$to = array();
foreach($this->to as $rcpt) {
if(is_null($rcpt['name'])) {
$to[] = "$rcpt[email]";
} else {
$to[] = "$rcpt[name] <$rcpt[email]>";
}
}
if(count($this->reply_to) > 0) {
$header = 'Reply-To: ';
foreach($this->reply_to as $addr) {
if(is_null($addr['name'])) {
$header .= "$addr[email], ";
} else {
if(strrpos($header, "\n") === false) $indent = strlen($header);
else $indent = strlen($header) - strrpos($header, "\n") - 1;
$header .= $this->header_7bit_safe($addr['name'], $indent)." <$addr[email]>, ";
}
}
$this->headers[] = substr($header, 0, -2);
}
foreach(array('cc', 'bcc') as $rcpt_type) {
foreach($this->$rcpt_type as $rcpt) {
if(is_null($rcpt['name'])) {
$this->headers[] = ucfirst($rcpt_type).": $rcpt[email]";
} else {
$this->headers[] = ucfirst($rcpt_type).": ".$this->header_7bit_safe($rcpt['name'], strlen($rcpt_type) + 2)." <$rcpt[email]>";
}
}
}
if(!empty($config['email']['enabled'])) {
mail(implode(', ', $to), $this->header_7bit_safe($this->subject, 9), $this->body, implode("\n", $this->headers));
}
}
private function flow() {
$message = $this->body;
/* Excerpt from RFC 3676 - 4.2. Generating Format=Flowed
A generating agent SHOULD:
o Ensure all lines (fixed and flowed) are 78 characters or fewer in
length, counting any trailing space as well as a space added as
stuffing, but not counting the CRLF, unless a word by itself
exceeds 78 characters.
o Trim spaces before user-inserted hard line breaks.
A generating agent MUST:
o Space-stuff lines which start with a space, "From ", or ">".
*/
// Trimming spaces before user-inserted hard line breaks, and wrapping.
$lines = explode("\n", $message);
foreach($lines as $ref => $line) {
$lines[$ref] = wordwrap(rtrim($line), 76, " \n", false);
}
$message = implode("\n", $lines);
// Space-stuffing lines which start with a space, "From ", or ">".
$lines = explode("\n", $message);
foreach($lines as $ref => $line) {
if(strpos($line, " ") === 0 || strpos($line, "From ") === 0 || strpos($line, ">") === 0) $lines[$ref] = " ".$line;
}
$message = implode("\n", $lines);
$message = "$message\n\n";
$this->body = $message;
$this->headers[] = "Content-Type: text/plain; charset=utf-8; format=flowed";
}
private function header_7bit_safe($string, $indent = 0) {
if(is_null($string)) return null;
return mb_encode_mimeheader($string, 'UTF-8', 'Q', "\n", $indent);
}
private function append_signature() {
//Add a signature
$this->body .= "-- \n";
$this->body .= $this->signature;
}
private function sign() {
$localheaders = array();
foreach($this->headers as $k => $v) {
if(preg_match('/^Content-Type:/i', $v)) {
$localheaders[] = $v;
unset($this->headers[$k]);
}
}
$localheaders[] = "Content-Transfer-Encoding: quoted-printable";
$lines = explode("\n", $this->body);
foreach($lines as $ref => $line) {
$line = quoted_printable_encode($line);
if(substr($line, -1) == ' ') $line = substr($line, 0, -1).'=20';
$lines[$ref] = $line;
}
$boundary = uniqid(php_uname('n'));
$innerboundary = uniqid(php_uname('n').'1');
$this->headers[] = 'Content-Type: multipart/signed; micalg=pgp-sha1; protocol="application/pgp-signature"; boundary="'.$boundary.'"';
$message = "Content-Type: multipart/mixed; boundary=\"{$innerboundary}\";\r\n";
$message .= " protected-headers=\"v1\"\r\n";
$message .= "From: {$this->from['email']}\r\n";
foreach(array('to', 'cc') as $rcpt_type) {
foreach($this->$rcpt_type as $rcpt) {
if(is_null($rcpt['name'])) {
$message .= ucfirst($rcpt_type).": $rcpt[email]\r\n";
} else {
$message .= ucfirst($rcpt_type).": ".$this->header_7bit_safe($rcpt['name'], strlen($rcpt_type) + 2)." <$rcpt[email]>\r\n";
}
}
}
$message .= "Subject: ".$this->header_7bit_safe($this->subject, 9)."\r\n\r\n";
$message .= "--{$innerboundary}\r\n".implode("\r\n", $localheaders)."\r\n\r\n".implode("\r\n", $lines)."\r\n--{$innerboundary}--\r\n";
$signature = $this->get_gpg_signature($message);
$message = "This is an OpenPGP/MIME signed message (RFC 4880 and 3156)\r\n--{$boundary}\r\n{$message}\r\n--{$boundary}\r\n";
$message .= "Content-Type: application/pgp-signature; name=\"signature.asc\"\r\n";
$message .= "Content-Description: OpenPGP digital signature\r\n";
$message .= "Content-Disposition: attachment; filename=\"signature.asc\"\r\n\r\n";
$message .= $signature;
$message .= "\r\n--$boundary--";
$this->body = $message;
}
private function get_gpg_signature($message) {
global $config;
$gpg = new gnupg();
$gpg->addsignkey($config['gpg']['key_id']);
$gpg->setsignmode(GNUPG::SIG_MODE_DETACH);
return $gpg->sign($message);
}
}