欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

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

程序员文章站 2022-06-14 16:26:52
...
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] $version[2] ){            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 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 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: [email protected] // 根据情况填写

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

  存在的问题:

    1、不支持ssl模式;

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

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

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。

相关文章

相关视频


网友评论

文明上网理性发言,请遵守 新闻评论服务协议

我要评论
  • PHP 进展统一邮箱登陆的代理实现(swoole)
  • 专题推荐

    作者信息
    PHP 进展统一邮箱登陆的代理实现(swoole)

    认证0级讲师

    推荐视频教程
  • PHP 进展统一邮箱登陆的代理实现(swoole)javascript初级视频教程
  • PHP 进展统一邮箱登陆的代理实现(swoole)jquery 基础视频教程
  • 视频教程分类