PHP 进行统一邮箱登陆的代理实现(swoole)
在工作的过程中,经常会有很多应用有发邮件的需求,这个时候需要在每个应用中配置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)的更多相关文章
- 作业:用HTML制作邮箱登陆界面
<body leftmargin="200" rightmargin="200"> <font size="45" > ...
- HTML写的第一个邮箱登陆界面
自己动手去写才会有收获,宁可模仿也不要全部复制粘贴 不说了,直接上代码.CSS有注释,适合新手. <!doctype html> <html> <head> < ...
- django 登陆增加除了用户名之外的手机和邮箱登陆
在setting内增加 # Application definition AUTHENTICATION_BACKENDS = ( 'users.views.CustomBackend', ) 在vie ...
- CSS练习一(模仿163邮箱登陆)
// '); code = code.replace(/&/g, '&'); return code; }; var runCode = function (code) { if (c ...
- python 模拟126邮箱登陆
#coding=utf-8from selenium import webdriverimport time mydriver=webdriver.Firefox()mydriver.get(&quo ...
- apk接入google play邮箱登陆及充值注意事项
unity3d 接入google play商店相关sdk,相关要求A.环境配置: 1.手机安装谷歌安装器 2.使用谷歌安装器安装Google 服务框架.Google Play服务.Google Pla ...
- EasyUI+MVC4实现后台管理系统一:登陆和进入后台界面
首先实现登陆: 未完待续...
- python+selenium实现163邮箱登陆—iframe动态ID定位 及常用定位方法
今天发现之前的登录163邮箱脚本定位不到iframe了,原因是iframe拼接了动态ID,修改后的脚本如下: from selenium import webdriver driver = webdr ...
- selenium2自动化测试学习笔记(五)-参数化编程,自动登陆网易QQ邮箱
学习python下使用selenium2自动测试第6天,参数化编程这节课花了两天时间. 本次编程主要时间是花在熟悉python上 知识点or坑点: 1.读取txt.xml.csv等文件存储的账号.密码 ...
随机推荐
- 修改窗口属性(全部都是SetWindowLong设置)
说明: 以下函数对于POPUP窗口有效,对于子窗口好像不行. //最小化按钮有效 ::SetWindowLong(m_hWnd,GWL_STYLE,GetWindowLong(m_hWnd,GWL_S ...
- Lua function 函数
Lua支持面向对象,操作符为冒号‘:’.o:foo(x) <==> o.foo(o, x). Lua程序可以调用C语言或者Lua实现的函数.Lua基础库中的所有函数都是用C实现的.但这些细 ...
- What does the number on the visual studio solution icon represent?
The numbers correspond to the internal version numbers of various editions of Visual Studio http://e ...
- Android MonkeyRunner自动拨打电话
from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice import time device = MonkeyRunner.wa ...
- 【转】Markus Persson:Minecraft 游戏背后的奇才
转自酷勤网 Markus Persson是沙盒游戏Minecraft的开发者,在游戏中以Notch的名字为众多玩家所知,是游戏界绝对的巨星.美国<滚石>杂志的专栏作家David Peisn ...
- 文件夹oradiag_是如何产生的
如果sqlnet.ora不可用或者ADR_BASE参数未定义,那么11g的 SQL*Net将创建这些文件夹 (详情:http://download.oracle.com/docs/cd/B28359_ ...
- SRM 408(1-250pt, 1-500pt)
DIV1 250pt 题意:每天晚上需要点蜡烛,且每晚蜡烛燃烧1cm,第i天晚上需要点i根蜡烛.第一天白天的时候,拥有一些蜡烛,用vector<int>can表示他们的长度,问最多能烧几个 ...
- Apache Mesos_百度百科
Apache Mesos_百度百科 Apache Mesos
- windows 下文件上传到fastdfs
php.ini 配置 [fastdfs]; the base pathfastdfs_client.base_path = D:/tmp ; connect timeout in seconds; d ...
- ngnix 配置
#运行用户 user www-data; #启动进程,通常设置成和cpu的数量相等 worker_processes 1; #全局错误日志及PID文件 error_log /var/log ...