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

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

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

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

    3.转发应用的smtp请求。

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

  代码如下:

<?php
/**
*
* SMTP Proxy 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') or define('DEBUG_ON', false); //主目录
defined('BASE_PATH') or define('BASE_PATH', __DIR__); class CSmtpProxy{ //软件版本
const VERSION = '1.0'; const EOF = "\r\n"; public static $software = "SMTP-Proxy-Server"; private static $server_mode = SWOOLE_PROCESS; private static $pid_file; private static $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(); public static function setPidFile($pid_file){
self::$pid_file = $pid_file;
} public static function start($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");
}
} public function __construct($host,$port){
$flag = SWOOLE_SOCK_TCP;
$this->server = new swoole_server($host,$port,self::$server_mode,$flag);
$this->host = $host;
$this->port = $port;
$this->setting = array(
'backlog' => 128,
'dispatch_mode' => 2,
);
} public function daemonize(){
$this->setting['daemonize'] = 1;
} public function getConnectionInfo($fd){
return $this->server->connection_info($fd);
} /**
* 启动服务进程
* @param array $setting
* @throws Exception
*/
public function run($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){
throw new Exception('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();
} public function log($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){//10M
unlink($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){ //200M
rename(self::$log_file,self::$log_file.'.'.date('His'));
}
$logs = '';
foreach ($this->queue as $q){
$logs .= $q."\n";
}
file_put_contents(self::$log_file, $logs,FILE_APPEND);
$this->queue = array();
}
} public function shutdown(){
return $this->server->shutdown();
} public function close($fd){
return $this->server->close($fd);
} public function send($fd,$data){
$data = strtr($data,array("\n" => "", "\0" => "", "\r" => ""));
$this->log("[P --> C]\t" . $data);
return $this->server->send($fd,$data.self::EOF);
} /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 事件回调
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ public function onMasterStart($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.');
} public function onMasterStop($serv){
if (!empty($this->setting['pid_file'])){
unlink(self::$pid_file);
}
$this->shm->delete();
$this->log('Master stop.');
} public function onManagerStart($serv){
global $argv;
swoole_set_process_name('php '.$argv[0].': manager');
$this->log('Manager started.');
} public function onManagerStop($serv){
$this->log('Manager stop.');
} public function onWorkerStart($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.");
} public function onWorkerStop($serv,$worker_id){
$this->log("Worker {$worker_id} stop.");
} public function onWorkerError($serv,$worker_id,$worker_pid,$exit_code){
$this->log("Worker {$worker_id} error:{$exit_code}.");
} public function onConnect($serv,$fd,$from_id){
if(count($this->server->connections) <= $this->max_connection){
$info = $this->getConnectionInfo($fd);
if($this->isIpAllow($info['remote_ip'])){
//建立服务器连接
$cli = new Client(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);
}
} public function onReceive($serv,$fd,$from_id,$recv_data){
$info = $this->getConnectionInfo($fd);
$this->log("[P <-- C]\t".trim($recv_data));
//禁止使用STARTTLS
if(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 from
if(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));
}
} } public function onClose($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);
} /*---------------------------------------------
*
* 服务器连接事件回调
*
----------------------------------------------*/ public function onServerConnect($cli){
$this->log('Connected to SMTP server.');
} public function onServerReceive($cli,$data){
$this->log("[P <-- S]\t".trim($data));
if($this->server->send($cli->fd,$data)){
$this->log("[P --> C]\t".trim($data));
}
} public function onServerError($cli){
$this->server->close($cli->fd);
$this->log('Connection on SMTP server error: '.$cli->errCode.' '.socket_strerror($cli->errCode),'warn');
} public function onServerClose($cli){
$this->log('Connection on SMTP server close.');
$this->server->close($cli->fd);
} /**
* IP地址过滤
* @param unknown $ip
* @return boolean
*/
public function isIpAllow($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;
} } class Client extends swoole_client{
/**
* 记录当前连接
* @var unknown
*/
public $fd ; public $user = ''; /**
* smtp登陆密码
* @var unknown
*/
public $pass = '';
}

  配置文件例子:

  

/**
* 运行配置
*/
return array(
'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还是要填写正确,否则发出的邮件发件人会显示错误。

PHP 进行统一邮箱登陆的代理实现(swoole)的更多相关文章

  1. 作业:用HTML制作邮箱登陆界面

    <body leftmargin="200" rightmargin="200"> <font size="45" > ...

  2. HTML写的第一个邮箱登陆界面

    自己动手去写才会有收获,宁可模仿也不要全部复制粘贴 不说了,直接上代码.CSS有注释,适合新手. <!doctype html> <html> <head> < ...

  3. django 登陆增加除了用户名之外的手机和邮箱登陆

    在setting内增加 # Application definition AUTHENTICATION_BACKENDS = ( 'users.views.CustomBackend', ) 在vie ...

  4. CSS练习一(模仿163邮箱登陆)

    // '); code = code.replace(/&/g, '&'); return code; }; var runCode = function (code) { if (c ...

  5. python 模拟126邮箱登陆

    #coding=utf-8from selenium import webdriverimport time mydriver=webdriver.Firefox()mydriver.get(&quo ...

  6. apk接入google play邮箱登陆及充值注意事项

    unity3d 接入google play商店相关sdk,相关要求A.环境配置: 1.手机安装谷歌安装器 2.使用谷歌安装器安装Google 服务框架.Google Play服务.Google Pla ...

  7. EasyUI+MVC4实现后台管理系统一:登陆和进入后台界面

    首先实现登陆: 未完待续...

  8. python+selenium实现163邮箱登陆—iframe动态ID定位 及常用定位方法

    今天发现之前的登录163邮箱脚本定位不到iframe了,原因是iframe拼接了动态ID,修改后的脚本如下: from selenium import webdriver driver = webdr ...

  9. selenium2自动化测试学习笔记(五)-参数化编程,自动登陆网易QQ邮箱

    学习python下使用selenium2自动测试第6天,参数化编程这节课花了两天时间. 本次编程主要时间是花在熟悉python上 知识点or坑点: 1.读取txt.xml.csv等文件存储的账号.密码 ...

随机推荐

  1. ServletContextListener作用(转)

    ServletContext 被 Servlet 程序用来与 Web 容器通信.例如写日志,转发请求.每一个 Web 应用程序含有一个Context,被Web应用内的各个程序共享.因为Context可 ...

  2. Spring dataSource

    1.何为dataSource DataSource 接口是 JDBC 2.0 API 中的新增内容,它提供了连接到数据源的另一种方法. 作为 DriverManager 工具的替代项,DataSour ...

  3. Hosting Multiple Service Implementations On The Same Port With WCF

    Hosting Multiple Service Implementations On The Same Port With WCF Recently I have been playing arou ...

  4. 【传】玩转Android---UI篇---ImageButton(带图标的按钮)

    原文网址:http://hualang.iteye.com/blog/964049 除了Android系统自带的Button按钮一万,还提供了带图标的按钮ImageButton 要制作带图标的按钮,首 ...

  5. DIP开放计算平台介绍

    随着平台业务的发展,依赖于Portal(Web)构建的服务架构已逐渐不能满足现有的一些复杂需求(如:使用Hive SQL无法完成计算逻辑),而且对于一些具备编程能力的程序员或数据分析师而言,能够自主控 ...

  6. Linux 查看磁盘分区、文件系统、使用情况的命令和相关工具介绍

    磁盘分区表.文件系统的查看.统计的工具很多,有些工具是多功能的,不仅仅是查看磁盘的分区表,而且也能进行磁盘分区的操作:但在本文,我们只讲磁盘分区的查看,以及分区的使用情况的查看:本文只是给新手上路之用 ...

  7. 未能导入activex控件,请确保它正确注册"的完美解决方案

    这个错误“未能导入activex控件,请确保它正确注册”昨天下午让我和我同事花费了3个小时来调试这个错误,在使用VS2010的winfrom编程时加入com组件的时候,报这个错误(以一个客户的机器上) ...

  8. VS2010 MFC GDI+ 实现PNG透明图片显示

    网上找了一些资料学习了一下PNG图的显示,这里总结一下. 参考:http://blog.csdn.net/czyt1988/article/details/7965066 一.VS2010配置GDI+ ...

  9. 390. Elimination Game

    正规解法直接跳到代码上面一点的部分就可以了.但我想记录下自己的思考和尝试过程,希望二刷能看到问题所在. 找规律的时候写了好多,虽然规律很简单. 只要随便写3以上的例子,就应该发现,相邻的2个最后结果是 ...

  10. PHP+IIS7+PHPMangerForIIS搭建开发环境

    准备工作 1.安装IIS 2.安装php 3.安装phpmanager 准备工作做好后,开始搭建环境 1.在Internet信息服务管理器下找到PHPManager 2.点击Register new ...