在工作的过程中,经常会有很多应用有发邮件的需求,这个时候需要在每个应用中配置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. 14.7.2 Changing the Number or Size of InnoDB Redo Log Files 改变InnoDB Redo Log Files的数量和大小

    14.7.2 Changing the Number or Size of InnoDB Redo Log Files 改变InnoDB Redo Log Files的数量和大小 改变 InnoDB ...

  2. 【canvas】基于坐标的碰撞检测 / 基本的动画 / 多物体动画

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  3. FAT32,NTFS,EXT3,支持的最大分区和单个文件大小?

    FAT32 Filesystem: 最大单一档案大小 4GB 最大文件系统总容量 128GB NTFS Filesystem: 最大单一档案大小 64GB 最大文件系统总容量 2TB Ext3 Fil ...

  4. Longest Consecutive Sequence——Leetcode

    Given an unsorted array of integers, find the length of the longest consecutive elements sequence. F ...

  5. Linux学习笔记29——IPC状态命令

    一 IPC IPC是进程间通讯,在前面,我们相继学习了进程间通讯机制有信号量,内存共享,消息队列.状态命令(ipcs)和删除命令(ipcrm)提供了一种检查和清理IPC机制的方法. 二 状态命令 1 ...

  6. 【转】unity3d input输入

    Input 输入 按键 Input.GetKey(“up”) = Input.GetKey(KeyCode.UpArrow) 按住键盘上键 Input.GetKeyDown (“up”) 按下键盘上键 ...

  7. Apache+PHP 环境上传文件配置

    打开php.ini 配置文件,查找 File Uploads ,在这个区域有以下3个选项: file_uploads = On 是否允许HTTP文件上传.默认值为On允许HTTP文件上传,此选项不能设 ...

  8. TCP 的那些事儿(上)

    TCP是一个巨复杂的协议,因为他要解决很多问题,而这些问题又带出了很多子问题和阴暗面.所以学习TCP本身是个比较痛苦的过程,但对于学习的过程却能让人有很多收获.关于TCP这个协议的细节,我还是推荐你去 ...

  9. Drawing Lines - SGU 135(简单递推)

    求N条直线最多能把一个平面分成几部分. 代码如下: ========================================================================== ...

  10. Corn Fields - POJ 3254(状态压缩)

    题目大意:有一个M*N的牧场,G(i, j) = 1表示这块地营养丰富,可以喂养牛,等于0表示贫瘠,不能喂养牛,所有的牛都讨厌与别的牛相邻,求有多少种放置牛的方式. 分析:算是炮兵那个题的弱化版吧,先 ...