PHP进行统一邮箱登陆的代理实现(swoole)

王朝学院·作者佚名  2016-08-27
窄屏简体版  字體: 小  |  中  |  大  |  超大  

在工作的过程中,经常会有很多应用有发邮件的需求,这个时候需要在每个应用中配置smtp服务器。一旦公司调整了smtp服务器的配置,比如修改了密码等,这个时候对于维护的人员来说要逐一修改应用中smtp的配置。这样的情况虽然不多见,但遇上了还是很头痛的一件事情。

知道了问题,解决起来就有了方向。于是就有了自己开发一个简单的smtp代理的想法,这个代理主要的功能(参照问题)主要是:

1.接受指定ip应用的smtp请求;

2.应用不需要知道smtp的用户和密码;

3.转发应用的smtp请求。

开发的环境:linux,php(swoole);

代码如下:

<?php/**

*

* SMTPPRoxy Server

* @author Terry Zhang, 2015-11-13

*

* @version 1.0

*

* 注意:本程序只能运行在cli模式,且需要扩展Swoole 1.7.20+的支持。

*

* Swoole的源代码及安装请参考 https://github.com/swoole/swoole-src/

*

* 本程序的使用场景:

*

* 在多个分散的系统中使用同一的邮件地址进行系统邮件发送时,一旦邮箱密码修改,则要修改每个系统的邮件配置参数。

* 同时,在每个系统中配置邮箱参数,使得邮箱的密码容易外泄。

*

* 通过本代理进行邮件发送的客户端,可以随便指定用户名和密码。

*

**///error_reporting(0);defined('DEBUG_ON') ordefine('DEBUG_ON',false);//主目录defined('BASE_PATH') ordefine('BASE_PATH',__DIR__);classCSmtpProxy{//软件版本constVERSION = '1.0';constEOF = "\r\n";publicstatic$software= "SMTP-Proxy-Server";privatestatic$server_mode=SWOOLE_PROCESS;privatestatic$pid_file;privatestatic$log_file;private$smtp_host= 'localhost';private$smtp_port= 25;private$smtp_user= '';private$smtp_pass= '';private$smtp_from= '';//待写入文件的日志队列(缓冲区)private$queue=array();public$host= '0.0.0.0';public$port= 25;public$setting=array();//最大连接数public$max_connection= 50;/**

* @var swoole_server*/protected$server;protected$connection=array();publicstaticfunctionsetPidFile($pid_file){

self::$pid_file=$pid_file;

}publicstaticfunctionstart($startFunc){if(!extension_loaded('swoole')){exit("Require extension `swoole`.\n");

}$pid_file= self::$pid_file;$server_pid= 0;if(is_file($pid_file)){$server_pid=file_get_contents($pid_file);

}global$argv;if(empty($argv[1])){

goto usage;

}elseif($argv[1] == 'reload'){if(empty($server_pid)){exit("SMTP Proxy Server is not running\n");

}

posix_kill($server_pid,SIGUSR1);exit;

}elseif($argv[1] == 'stop'){if(empty($server_pid)){exit("SMTP Proxy is not running\n");

}

posix_kill($server_pid,SIGTERM);exit;

}elseif($argv[1] == 'start'){//已存在ServerPID,并且进程存在if(!empty($server_pid) and posix_kill($server_pid,(int) 0)){exit("SMTP Proxy is already running.\n");

}//启动服务器$startFunc();

}else{

usage:exit("Usage: php {$argv[0]} start|stop|reload\n");

}

}publicfunction__construct($host,$port){$flag=SWOOLE_SOCK_TCP;$this->server =newswoole_server($host,$port,self::$server_mode,$flag);$this->host =$host;$this->port =$port;$this->setting =array('backlog' => 128,

'dispatch_mode' => 2,);

}publicfunctiondaemonize(){$this->setting['daemonize'] = 1;

}publicfunctiongetConnectionInfo($fd){return$this->server->connection_info($fd);

}/**

* 启动服务进程

* @param array $setting

* @throws Exception*/publicfunctionrun($setting=array()){$this->setting =array_merge($this->setting,$setting);//不使用swoole的默认日志if(isset($this->setting['log_file'])){

self::$log_file=$this->setting['log_file'];unset($this->setting['log_file']);

}if(isset($this->setting['max_connection'])){$this->max_connection =$this->setting['max_connection'];unset($this->setting['max_connection']);

}if(isset($this->setting['smtp_host'])){$this->smtp_host =$this->setting['smtp_host'];unset($this->setting['smtp_host']);

}if(isset($this->setting['smtp_port'])){$this->smtp_port =$this->setting['smtp_port'];unset($this->setting['smtp_port']);

}if(isset($this->setting['smtp_user'])){$this->smtp_user =$this->setting['smtp_user'];unset($this->setting['smtp_user']);

}if(isset($this->setting['smtp_pass'])){$this->smtp_pass =$this->setting['smtp_pass'];unset($this->setting['smtp_pass']);

}if(isset($this->setting['smtp_from'])){$this->smtp_from =$this->setting['smtp_from'];unset($this->setting['smtp_from']);

}$this->server->set($this->setting);$version=explode('.',SWOOLE_VERSION);if($version[0] == 1 &&$version[1] < 7 &&$version[2] <20){thrownewException('Swoole version require 1.7.20 +.');

}//事件绑定$this->server->on('start',array($this,'onMasterStart'));$this->server->on('shutdown',array($this,'onMasterStop'));$this->server->on('ManagerStart',array($this,'onManagerStart'));$this->server->on('ManagerStop',array($this,'onManagerStop'));$this->server->on('WorkerStart',array($this,'onWorkerStart'));$this->server->on('WorkerStop',array($this,'onWorkerStop'));$this->server->on('WorkerError',array($this,'onWorkerError'));$this->server->on('Connect',array($this,'onConnect'));$this->server->on('Receive',array($this,'onReceive'));$this->server->on('Close',array($this,'onClose'));$this->server->start();

}publicfunctionlog($msg,$level= 'debug',$flush=false){if(DEBUG_ON){$log=date('Y-m-d H:i:s').' ['.$level."]\t" .$msg."\n";if(!empty(self::$log_file)){$debug_file=dirname(self::$log_file).'/debug.log';file_put_contents($debug_file,$log,FILE_APPEND);if(filesize($debug_file) > 10485760){//10Munlink($debug_file);

}

}echo$log;

}if($level!= 'debug'){//日志记录$this->queue[] =date('Y-m-d H:i:s')."\t[".$level."]\t".$msg;

}if(count($this->queue)>10 && !empty(self::$log_file) ||$flush){if(filesize(self::$log_file) > 209715200){//200Mrename(self::$log_file,self::$log_file.'.'.date('His'));

}$logs= '';foreach($this->queueas$q){$logs.=$q."\n";

}file_put_contents(self::$log_file,$logs,FILE_APPEND);$this->queue =array();

}

}publicfunctionshutdown(){return$this->server->shutdown();

}publicfunctionclose($fd){return$this->server->close($fd);

}publicfunctionsend($fd,$data){$data=strtr($data,array("\n" => "", "\0" => "", "\r" => ""));$this->log("[P --> C]\t" .$data);return$this->server->send($fd,$data.self::EOF);

}/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++

+ 事件回调

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/publicfunctiononMasterStart($serv){global$argv;

swoole_set_process_name('php '.$argv[0].': master -host='.$this->host.' -port='.$this->port);if(!empty($this->setting['pid_file'])){file_put_contents(self::$pid_file,$serv->master_pid);

}$this->log('Master started.');

}publicfunctiononMasterStop($serv){if(!empty($this->setting['pid_file'])){unlink(self::$pid_file);

}$this->shm->delete();$this->log('Master stop.');

}publicfunctiononManagerStart($serv){global$argv;

swoole_set_process_name('php '.$argv[0].': manager');$this->log('Manager started.');

}publicfunctiononManagerStop($serv){$this->log('Manager stop.');

}publicfunctiononWorkerStart($serv,$worker_id){global$argv;if($worker_id>=$serv->setting['worker_num']) {

swoole_set_process_name("php {$argv[0]}: worker [task]");

}else{

swoole_set_process_name("php {$argv[0]}: worker [{$worker_id}]");

}$this->log("Worker {$worker_id} started.");

}publicfunctiononWorkerStop($serv,$worker_id){$this->log("Worker {$worker_id} stop.");

}publicfunctiononWorkerError($serv,$worker_id,$worker_pid,$exit_code){$this->log("Worker {$worker_id} error:{$exit_code}.");

}publicfunctiononConnect($serv,$fd,$from_id){if(count($this->server->connections) <=$this->max_connection){$info=$this->getConnectionInfo($fd);if($this->isIpAllow($info['remote_ip'])){//建立服务器连接$cli=newClient(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);//异步非阻塞$cli->on('connect',array($this,'onServerConnect'));$cli->on('receive',array($this,'onServerReceive'));$cli->on('error',array($this,'onServerError'));$cli->on('close',array($this,'onServerClose'));$cli->fd =$fd;$ip=gethostbyname($this->smtp_host);if($cli->connect($ip,$this->smtp_port) !==false){$this->connection[$fd] =$cli;

}else{$this->close($fd);$this->log('Cannot connect to SMTP server. Connection #'.$fd.' close.');

}

}else{$this->log('Blocked clinet connection, IP deny : '.$info['remote_ip'],'warn');$this->server->close($fd);$this->log('Connection #'.$fd.' close.');

}

}else{$this->log('Blocked clinet connection, too many connections.','warn');$this->server->close($fd);

}

}publicfunctiononReceive($serv,$fd,$from_id,$recv_data){$info=$this->getConnectionInfo($fd);$this->log("[P <-- C]\t".trim($recv_data));//禁止使用STARTTLSif(strtoupper(trim($recv_data)) == 'STARTTLS'){$this->server->send($fd,"502 Not implemented".self::EOF);$this->log("[P --> C]\t502 Not implemented");

}else{//重置登陆验证if(preg_match('/^AUTH\s+LOGIN(.*)/',$recv_data,$m)){$m[1] =trim($m[1]);if(empty($m[1])){//只发送AUTH LOGIN 接下来将发送用户名$this->connection[$fd]->user =$this->smtp_user;

}else{$recv_data= 'AUTH LOGIN '.base64_encode($this->smtp_user).self::EOF;$this->connection[$fd]->pass =$this->smtp_pass;

}

}else{

//if(preg_match('/^HELO.*|^EHLO.*/',$recv_data)){

//$recv_data= 'HELO '.$this->smtp_host.self::EOF;

//}//重置密码if(!empty($this->connection[$fd]->pass)){$recv_data=base64_encode($this->connection[$fd]->pass).self::EOF;$this->connection[$fd]->pass = '';

}//重置用户名if(!empty($this->connection[$fd]->user)){$recv_data=base64_encode($this->connection[$fd]->user).self::EOF;$this->connection[$fd]->user = '';$this->connection[$fd]->pass =$this->smtp_pass;

}//重置mail fromif(preg_match('/^MAIL\s+FROM:.*/',$recv_data)){$recv_data= 'MAIL FROM:<'.$this->smtp_from.'>'.self::EOF;

}

}if($this->connection[$fd]->isConnected()){$this->connection[$fd]->send($recv_data);$this->log("[P --> S]\t".trim($recv_data));

}

}

}publicfunctiononClose($serv,$fd,$from_id){if(isset($this->connection[$fd])){if($this->connection[$fd]->isConnected()){$this->connection[$fd]->close();$this->log('Connection on SMTP server close.');

}

}$this->log('Connection #'.$fd.' close. Flush the logs.','debug',true);

}/*---------------------------------------------

*

* 服务器连接事件回调

*

----------------------------------------------*/publicfunctiononServerConnect($cli){$this->log('Connected to SMTP server.');

}publicfunctiononServerReceive($cli,$data){$this->log("[P <-- S]\t".trim($data));if($this->server->send($cli->fd,$data)){$this->log("[P --> C]\t".trim($data));

}

}publicfunctiononServerError($cli){$this->server->close($cli->fd);$this->log('Connection on SMTP server error: '.$cli->errCode.' '.socket_strerror($cli->errCode),'warn');

}publicfunctiononServerClose($cli){$this->log('Connection on SMTP server close.');$this->server->close($cli->fd);

}/**

* IP地址过滤

* @param unknown $ip

* @return boolean*/publicfunctionisIpAllow($ip){$pass=false;if(isset($this->setting['ip']['allow'])){foreach($this->setting['ip']['allow']as$addr){$pattern= '/'.str_replace('*','\d+',str_replace('.', '\.',$addr)).'/';if(preg_match($pattern,$ip) && !empty($addr)){$pass=true;

break;

}

}

}if($pass){if(isset($this->setting['ip']['deny'])){foreach($this->setting['ip']['deny']as$addr){$pattern= '/'.str_replace('*','\d+',str_replace('.', '\.',$addr)).'/';if(preg_match($pattern,$ip) && !empty($addr)){$pass=false;

break;

}

}

}

}return$pass;

}

}classClientextendsswoole_client{/**

* 记录当前连接

* @var unknown*/public$fd;public$user= '';/**

* smtp登陆密码

* @var unknown*/public$pass= '';

}

配置文件例子:

/**

* 运行配置*/returnarray('worker_num' => 12,

'log_file' => BASE_PATH.'/logs/proxyserver.log',

'pid_file' => BASE_PATH.'/logs/proxyserver.pid',

'heartbeat_idle_time' => 300,

'heartbeat_check_interval' => 60,

'max_connection' => 50,

//配置真实的smtp信息

'smtp_host' => '',

'smtp_port' => 25,

'smtp_user' => '',

'smtp_pass' => '',

'smtp_from' => '',

'ip' =>array('allow' =>array('192.168.0.*'),

'deny' =>array('192.168.10.*','192.168.100.*'),)

);

运行例子:

defined('BASE_PATH') or define('BASE_PATH', __DIR__);

defined('DEBUG_ON') or define('DEBUG_ON', true);

//服务器配置

require BASE_PATH.'/CSmtpProxy.php';

$settings = require BASE_PATH.'/conf/config.php';

CSmtpProxy::setPidFile($settings['pid_file']);

CSmtpProxy::start(function(){

global $settings;

$serv = new CSmtpProxy('0.0.0.0', 25);

$serv->daemonize();

$serv->run($settings);

});

应用配置:

smtp host: 192.168.0.* //指定smtpproxy 运行的服务器IP。

port: 25

user: xxxx //随意填写

pass: xxxx //随意填写

from: xxxx@xxxx.com // 根据情况填写

——————————————————————————————————————————————————————

存在的问题:

1、不支持ssl模式;

2、应用的from还是要填写正确,否则发出的邮件发件人会显示错误。

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航