<?php
/**
 * SMTP邮件发送类
 * 来源: uhome:http://u.discuz.net/ 
 * 目录: source/function_sendmail.php
 * 改造: 改成类的方式,增加对tls的支持,增加socket超时控制,去除多余配置
 * 对tls的支持要加上openssl扩展.可以用程序判断: in_array('ssl',stream_get_transports()) ? 'Yes' : 'No!'
 * oo::smtp()->send('zjmaslow@gmail.com,zjmaslow@126.com,ColinZhou@boyaa.com', '我是邮件标题', '我是邮件内容');
 */
class smtp{
	public $tls = true; //是否是tls服务器.不是请改成false
	public $server = 'mail.boyaa.com'; //服务器地址.GMail为:ssl://smtp.gmail.com:465 且socket要支持ssl:in_array('ssl',stream_get_transports()) 如果是tls则此处填服务器地址(无需tls://)并把$tls设为true
	public $port = '587'; //服务器端口
	public $auth = true; //smtp服务器是否需要验证
	public $user = 'noreply-game@boyaa.com'; //SMTP用户名
	public $password = 'Dd@12wZ#r5'; //SMTP密码
	public $from = 'noreply-game@boyaa.com'; //回复邮箱地址,126的强制跟$user相同,gmail的可以不同
	public $mailer = "boyaa.com"; //客户端标志

	public $errno = 0; //错误代码
	public $errstr = ''; //错误信息
	
	public function __construct(){
		// $this->server = '113.105.73.52'; //境外用另一个邮件服务器
		$this->server = 'mail1.boyaa.com'; //境外用另一个邮件服务器
		if (defined('PRODUCTION_SERVER') && PRODUCTION_SERVER == false) {
			$this->server = 'mail.boyaa.com'; // 内网用内网的邮件服务器
		}
	}
	
	/**
	 * 发送邮件
	 * @param String $sMails 收件人邮件地址,如果有多个则以,号隔开
	 * @param String $sSubject 邮件主题
	 * @param String $sMsg 邮件正文内容.支持HTML格式
	 * @param String $sFrom 发送者名称.用于对方收件箱中显示
	 */
	public function send($sMails, $sSubject, $sMsg, $sFrom='Boyaa-9K'){
		
		$fromuser = strlen( $sFrom) ? '=?utf-8?B?'.base64_encode( $sFrom)."?= <{$this->from}>" : $this->from; //自定义发送者名字
		
		foreach(explode(',', $sMails) as $touser) {
			$tousers[] = preg_match('/^(.+?) \<(.+?)\>$/', $touser, $to) ? '=?utf-8?B?'.base64_encode($to[1])."?= <$to[2]>" : $touser; //使得支持: 接收人名字<***@***.com> 地址形式
		}
		$sHeader = "From: {$fromuser}\r\nX-Priority: 3\r\nX-Mailer: {$this->mailer}!\r\nMIME-Version: 1.0\r\nContent-type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n";
		$sHeader .= 'Message-ID: <'.gmdate('YmdHs').'.'.substr(md5($sMsg.microtime()), 0, 6).rand(100000, 999999).'@'.$_SERVER['HTTP_HOST'].">\r\n";
		$sMails = implode(',', $tousers);
		$sSubject = '=?utf-8?B?'.base64_encode(str_replace("\r", '', str_replace("\n", '', $sSubject))).'?=';
		$sMsg = chunk_split(base64_encode(str_replace("\r\n.", " \r\n..", str_replace("\n", "\r\n", str_replace("\r", "\n", str_replace("\r\n", "\n", str_replace("\n\r", "\r", $sMsg)))))));
	
		if(! $fp = fsockopen($this->server, $this->port, $errno, $errstr, 5)) { //5秒链接超时
			list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$errstr);
			return false;
		}
		
		stream_set_timeout($fp, 5, 0); //读写操作不能超过5秒
	 	stream_set_blocking($fp, true);
		
	 	$lastmessage = fgets($fp, 512);
		if( ($errno = substr($lastmessage, 0, 3)) != '220') {
			list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
			return false;
		}
		
		fputs($fp, ($this->auth ? 'EHLO' : 'HELO')." {$this->mailer}\r\n");
		
		$lastmessage = fgets($fp, 512);
		if( ! in_array( $errno = substr($lastmessage, 0, 3), array(220, 250))) {
			list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
			return false;
		}
		while(1) {
			if(substr($lastmessage, 3, 1) != '-' || empty($lastmessage)) {
	 			break;
	 		}
	 		$lastmessage = fgets($fp, 512);
		}
	
		if( $this->auth) {
			if( $this->tls){ 
				fputs($fp, "STARTTLS\r\n");
				$lastmessage = fgets($fp, 512);
				if(($errno = substr($lastmessage, 0, 3)) != 220) {
					list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
					return false;
				}
				if(! stream_socket_enable_crypto($fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
					list($this->errno, $this->errstr) = array($errno, __LINE__);
					return false;
				}
				
				fputs($fp, ($this->auth ? 'HELO' : 'EHLO')." ".$this->mailer."\r\n"); //HELO是EHLO的扩展,支持验证的
				
				$lastmessage = fgets($fp, 512);
				if(($errno = substr($lastmessage, 0, 3)) != 250) {
					list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
					return false;
				}
			}
			fputs($fp, "AUTH LOGIN\r\n");
			$lastmessage = fgets($fp, 512);
			if( ($errno = substr($lastmessage, 0, 3)) != 334) {
				list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
				return false;
			}
			fputs($fp, base64_encode( $this->user)."\r\n");
			$lastmessage = fgets($fp, 512);
			if( ($errno = substr($lastmessage, 0, 3)) != 334) {
				list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
				return false;
			}
	
			fputs($fp, base64_encode( $this->password)."\r\n");
			$lastmessage = fgets($fp, 512);
			if( ($errno = substr($lastmessage, 0, 3)) != 235) {
				list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
				return false;
			}
		}
	
		fputs($fp, "MAIL FROM: <".preg_replace("/.*\<(.+?)\>.*/", "\\1", $fromuser).">\r\n");
		$lastmessage = fgets($fp, 512);
		if(substr($lastmessage, 0, 3) != 250) {
			fputs($fp, "MAIL FROM: <".preg_replace("/.*\<(.+?)\>.*/", "\\1", $fromuser).">\r\n");
			$lastmessage = fgets($fp, 512);
			if( ($errno = substr($lastmessage, 0, 3)) != 250) {
				list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
				return false;
			}
		}
	
		$email_tos = array();
		foreach(explode(',', $sMails) as $touser) {
			$touser = trim($touser);
			if($touser) {
				fputs($fp, "RCPT TO: <".preg_replace("/.*\<(.+?)\>.*/", "\\1", $touser).">\r\n");
				$lastmessage = fgets($fp, 512);
				if( ($errno = substr($lastmessage, 0, 3)) != 250) {
					list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
					return false;
				}
			}
		}
	
		fputs($fp, "DATA\r\n");
		$lastmessage = fgets($fp, 512);
		if( ($errno = substr($lastmessage, 0, 3)) != 354) {
			list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
			return false;
		}
	
		fputs($fp, "Date: ".gmdate('r')."\r\n");
		fputs($fp, "To: ".$sMails."\r\n");
		fputs($fp, "Subject: ".$sSubject."\r\n");
		fputs($fp, $sHeader."\r\n");
		fputs($fp, "\r\n\r\n");
		fputs($fp, "$sMsg\r\n.\r\n");
		$lastmessage = fgets($fp, 512);
		if( ($errno = substr($lastmessage, 0, 3)) != 250) {
			list($this->errno, $this->errstr) = array($errno, __LINE__.':'.$lastmessage);
			return false;
		}
	
		fputs($fp, "QUIT\r\n");
		fclose($fp);
		return true;
	}
}