加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 站长学院 > PHP教程 > 正文

使用PHP如何实现高效安全的ftp服务器(二)

发布时间:2020-12-12 21:15:49 所属栏目:PHP教程 来源:网络整理
导读:在上篇文章给大家介绍了使用,感兴趣的朋友可以点击了解详情。接下来通过本篇文章给大家介绍使用PHP如何实现高效安全的ftp服务器(二),具体内容如下所示: 1.实现用户类CUser。 用户的存储采用文本形式,将用户数组进行json编码。 用户文件格式: array(* 'p

在上篇文章给大家介绍了使用,感兴趣的朋友可以点击了解详情。接下来通过本篇文章给大家介绍使用PHP如何实现高效安全的ftp服务器(二),具体内容如下所示:

1.实现用户类CUser。

  用户的存储采用文本形式,将用户数组进行json编码。  

用户文件格式:

array( * 'pass'=>'',* 'group'=>'',* 'home'=>'/home/ftp/',//ftp主目录 * 'active'=>true,* 'expired=>'2015-12-12',* 'description'=>'',* 'email' => '',* 'folder'=>array( * //可以列出主目录下的文件和目录,但不能创建和删除,也不能进入主目录下的目录 * //前1-5位是文件权限,6-9是文件夹权限,10是否继承(inherit) * array('path'=>'/home/ftp/','access'=>'RWANDLCNDI'),* //可以列出/home/ftp/a/下的文件和目录,可以创建和删除,可以进入/home/ftp/a/下的子目录,可以创建和删除。 * array('path'=>'/home/ftp/a/','access'=>'RWAND-----'),* ),* 'ip'=>array( * 'allow'=>array(ip1,ip2,...),//支持*通配符: 192.168.0.* * 'deny'=>array(ip1,...) * ) * ) * ) * * 组文件格式: * array( * 'group1'=>array( * 'home'=>'/home/ftp/dept1/',* 'folder'=>array( * * ),* 'deny'=>array(ip1,...) * ) * ) * )

  

文件夹和文件的权限说明:

* 文件权限 * R读 : 允许用户读取(即下载)文件。该权限不允许用户列出目录内容,执行该操作需要列表权限。 * W写: 允许用户写入(即上传)文件。该权限不允许用户修改现有的文件,执行该操作需要追加权限。 * A追加: 允许用户向现有文件中追加数据。该权限通常用于使用户能够对部分上传的文件进行续传。 * N重命名: 允许用户重命名现有的文件。 * D删除: 允许用户删除文件。 * * 目录权限 * L列表: 允许用户列出目录中包含的文件。 * C创建: 允许用户在目录中新建子目录。 * N重命名: 允许用户在目录中重命名现有子目录。 * D删除: 允许用户在目录中删除现有子目录。注意: 如果目录包含文件,用户要删除目录还需要具有删除文件权限。 * * 子目录权限 * I继承: 允许所有子目录继承其父目录具有的相同权限。继承权限适用于大多数情况,但是如果访问必须受限于子文件夹,例如实施强制访问控制(Mandatory Access Control)时,则取消继承并为文件夹逐一授予权限。 *

  实现代码如下:  

<div class="jb51code">
<pre class="brush:php;">
class User{
const I = 1; // inherit
const FD = 2; // folder delete
const FN = 4; // folder rename
const FC = 8; // folder create
const FL = 16; // folder list
const D = 32; // file delete
const N = 64; // file rename
const A = 128; // file append
const W = 256; // file write (upload)
const R = 512; // file read (download)
private $hash_salt = '';
private $user_file;
private $group_file;
private $users = array();
private $groups = array();
private $file_hash = '';
public function __construct(){
$this->user_file = BASE_PATH.'/conf/users';
$this->group_file = BASE_PATH.'/conf/groups';
$this->reload();
}
/**

  • 返回权限表达式
  • @param int $access
  • @return string
    */
    public static function AC($access){
    $str = '';
    $char = array('R','W','A','N','D','L','C','I');
    for($i = 0; $i < 10; $i++){
    if($access & pow(2,9-$i))$str.= $char[$i];else $str.= '-';
    }
    return $str;
    }
    /**
  • 加载用户数据
    */
    public function reload(){
    $user_file_hash = md5_file($this->user_file);
    $group_file_hash = md5_file($this->group_file);
    if($this->file_hash != md5($user_file_hash.$group_file_hash)){
    if(($user = file_get_contents($this->user_file)) !== false){
    $this->users = json_decode($user,true);
    if($this->users){
    //folder排序
    foreach ($this->users as $user=>$profile){
    if(isset($profile['folder'])){
    $this->users[$user]['folder'] = $this->sortFolder($profile['folder']);
    }
    }
    }
    }
    if(($group = file_get_contents($this->group_file)) !== false){
    $this->groups = json_decode($group,true);
    if($this->groups){
    //folder排序
    foreach ($this->groups as $group=>$profile){
    if(isset($profile['folder'])){
    $this->groups[$group]['folder'] = $this->sortFolder($profile['folder']);
    }
    }
    }
    }
    $this->file_hash = md5($user_file_hash.$group_file_hash);
    }
    }
    /**
  • 对folder进行排序
  • @return array
    */
    private function sortFolder($folder){
    uasort($folder,function($a,$b){
    return strnatcmp($a['path'],$b['path']);
    });
    $result = array();
    foreach ($folder as $v){
    $result[] = $v;
    }
    return $result;
    }
    /**
  • 保存用户数据
    */
    public function save(){
    file_put_contents($this->user_file,json_encode($this->users),LOCK_EX);
    file_put_contents($this->group_file,json_encode($this->groups),LOCK_EX);
    }
    /**
  • 添加用户
  • @param string $user
  • @param string $pass
  • @param string $home
  • @param string $expired
  • @param boolean $active
  • @param string $group
  • @param string $description
  • @param string $email
  • @return boolean
    */
    public function addUser($user,$pass,$home,$expired,$active=true,$group='',$description='',$email = ''){
    $user = strtolower($user);
    if(isset($this->users[$user]) || empty($user)){
    return false;
    }
    $this->users[$user] = array(
    'pass' => md5($user.$this->hash_salt.$pass),'home' => $home,'expired' => $expired,'active' => $active,'group' => $group,'description' => $description,'email' => $email,);
    return true;
    }
    /**
  • 设置用户资料
  • @param string $user
  • @param array $profile
  • @return boolean
    */
    public function setUserProfile($user,$profile){
    $user = strtolower($user);
    if(is_array($profile) && isset($this->users[$user])){
    if(isset($profile['pass'])){
    $profile['pass'] = md5($user.$this->hash_salt.$profile['pass']);
    }
    if(isset($profile['active'])){
    if(!is_bool($profile['active'])){
    $profile['active'] = $profile['active'] == 'true' ? true : false;
    }
    }
    $this->users[$user] = array_merge($this->users[$user],$profile);
    return true;
    }
    return false;
    }
    /**
  • 获取用户资料
  • @param string $user
  • @return multitype:|boolean
    */
    public function getUserProfile($user){
    $user = strtolower($user);
    if(isset($this->users[$user])){
    return $this->users[$user];
    }
    return false;
    }
    /**
  • 删除用户
  • @param string $user
  • @return boolean
    */
    public function delUser($user){
    $user = strtolower($user);
    if(isset($this->users[$user])){
    unset($this->users[$user]);
    return true;
    }
    return false;
    }
    /**
  • 获取用户列表
  • @return array
    */
    public function getUserList(){
    $list = array();
    if($this->users){
    foreach ($this->users as $user=>$profile){
    $list[] = $user;
    }
    }
    sort($list);
    return $list;
    }
    /**
  • 添加组
  • @param string $group
  • @param string $home
  • @return boolean
    */
    public function addGroup($group,$home){
    $group = strtolower($group);
    if(isset($this->groups[$group])){
    return false;
    }
    $this->groups[$group] = array(
    'home' => $home
    );
    return true;
    }
    /**
  • 设置组资料
  • @param string $group
  • @param array $profile
  • @return boolean
    */
    public function setGroupProfile($group,$profile){
    $group = strtolower($group);
    if(is_array($profile) && isset($this->groups[$group])){
    $this->groups[$group] = array_merge($this->groups[$group],$profile);
    return true;
    }
    return false;
    }
    /**
  • 获取组资料
  • @param string $group
  • @return multitype:|boolean
    */
    public function getGroupProfile($group){
    $group = strtolower($group);
    if(isset($this->groups[$group])){
    return $this->groups[$group];
    }
    return false;
    }
    /**
  • 删除组
  • @param string $group
  • @return boolean
    */
    public function delGroup($group){
    $group = strtolower($group);
    if(isset($this->groups[$group])){
    unset($this->groups[$group]);
    foreach ($this->users as $user => $profile){
    if($profile['group'] == $group)
    $this->users[$user]['group'] = '';
    }
    return true;
    }
    return false;
    }
    /**
  • 获取组列表
  • @return array
    */
    public function getGroupList(){
    $list = array();
    if($this->groups){
    foreach ($this->groups as $group=>$profile){
    $list[] = $group;
    }
    }
    sort($list);
    return $list;
    }
    /**
  • 获取组用户列表
  • @param string $group
  • @return array
    */
    public function getUserListOfGroup($group){
    $list = array();
    if(isset($this->groups[$group]) && $this->users){
    foreach ($this->users as $user=>$profile){
    if(isset($profile['group']) && $profile['group'] == $group){
    $list[] = $user;
    }
    }
    }
    sort($list);
    return $list;
    }
    /**
  • 用户验证
  • @param string $user
  • @param string $pass
  • @param string $ip
  • @return boolean
    */
    public function checkUser($user,$ip = ''){
    $this->reload();
    $user = strtolower($user);
    if(isset($this->users[$user])){
    if($this->users[$user]['active'] && time() <= strtotime($this->users[$user]['expired'])
    && $this->users[$user]['pass'] == md5($user.$this->hash_salt.$pass)){
    if(empty($ip)){
    return true;
    }else{
    //ip验证
    return $this->checkIP($user,$ip);
    }
    }else{
    return false;
    }
    }
    return false;
    }
    /**
  • basic auth
  • @param string $base64
    */
    public function checkUserBasicAuth($base64){
    $base64 = trim(str_replace('Basic ','',$base64));
    $str = base64_decode($base64);
    if($str !== false){
    list($user,$pass) = explode(':',$str,2);
    $this->reload();
    $user = strtolower($user);
    if(isset($this->users[$user])){
    $group = $this->users[$user]['group'];
    if($group == 'admin' && $this->users[$user]['active'] && time() <= strtotime($this->users[$user]['expired'])
    && $this->users[$user]['pass'] == md5($user.$this->hash_salt.$pass)){
    return true;
    }else{
    return false;
    }
    }
    }
    return false;
    }
    /**
  • 用户登录ip验证
  • @param string $user
  • @param string $ip
  • 用户的ip权限继承组的IP权限。
  • 匹配规则:
  • 1.进行组允许列表匹配;
  • 2.如同通过,进行组拒绝列表匹配;
  • 3.进行用户允许匹配
  • 4.如果通过,进行用户拒绝匹配
  • /
    public function checkIP($user,$ip){
    $pass = false;
    //先进行组验证
    $group = $this->users[$user]['group'];
    //组允许匹配
    if(isset($this->groups[$group]['ip']['allow'])){
    foreach ($this->groups[$group]['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->groups[$group]['ip']['deny'])){
    foreach ($this->groups[$group]['ip']['deny'] as $addr){
    $pattern = '/'.str_replace('',$ip) && !empty($addr)){
    $pass = false;
    break;
    }
    }
    }
    }
    if(isset($this->users[$user]['ip']['allow'])){
    foreach ($this->users[$user]['ip']['allow'] as $addr){
    $pattern = '/'.str_replace('
    ',$ip) && !empty($addr)){
    $pass = true;
    break;
    }
    }
    }
    if($pass){
    if(isset($this->users[$user]['ip']['deny'])){
    foreach ($this->users[$user]['ip']['deny'] as $addr){
    $pattern = '/'.str_replace('*',$ip) && !empty($addr)){
    $pass = false;
    break;
    }
    }
    }
    }
    echo date('Y-m-d H:i:s')." [debug]tIP ACCESS:".' '.($pass?'true':'false')."n";
    return $pass;
    }
    /**

  • 获取用户主目录
  • @param string $user
  • @return string
    */
    public function getHomeDir($user){
    $user = strtolower($user);
    $group = $this->users[$user]['group'];
    $dir = '';
    if($group){
    if(isset($this->groups[$group]['home']))$dir = $this->groups[$group]['home'];
    }
    $dir = !empty($this->users[$user]['home'])?$this->users[$user]['home']:$dir;
    return $dir;
    }
    //文件权限判断
    public function isReadable($user,$path){
    $result = $this->getPathAccess($user,$path);
    if($result['isExactMatch']){
    return $result['access'][0] == 'R';
    }else{
    return $result['access'][0] == 'R' && $result['access'][9] == 'I';
    }
    }
    public function isWritable($user,$path);
    if($result['isExactMatch']){
    return $result['access'][1] == 'W';
    }else{
    return $result['access'][1] == 'W' && $result['access'][9] == 'I';
    }
    }
    public function isAppendable($user,$path){
    $result = $this->getPathAccess($user,$path);
    if($result['isExactMatch']){
    return $result['access'][2] == 'A';
    }else{
    return $result['access'][2] == 'A' && $result['access'][9] == 'I';
    }
    }
    public function isRenamable($user,$path);
    if($result['isExactMatch']){
    return $result['access'][3] == 'N';
    }else{
    return $result['access'][3] == 'N' && $result['access'][9] == 'I';
    }
    }
    public function isDeletable($user,$path);
    if($result['isExactMatch']){
    return $result['access'][4] == 'D';
    }else{
    return $result['access'][4] == 'D' && $result['access'][9] == 'I';
    }
    }
    //目录权限判断
    public function isFolderListable($user,$path);
    if($result['isExactMatch']){
    return $result['access'][5] == 'L';
    }else{
    return $result['access'][5] == 'L' && $result['access'][9] == 'I';
    }
    }
    public function isFolderCreatable($user,$path);
    if($result['isExactMatch']){
    return $result['access'][6] == 'C';
    }else{
    return $result['access'][6] == 'C' && $result['access'][9] == 'I';
    }
    }
    public function isFolderRenamable($user,$path);
    if($result['isExactMatch']){
    return $result['access'][7] == 'N';
    }else{
    return $result['access'][7] == 'N' && $result['access'][9] == 'I';
    }
    }
    public function isFolderDeletable($user,$path);
    if($result['isExactMatch']){
    return $result['access'][8] == 'D';
    }else{
    return $result['access'][8] == 'D' && $result['access'][9] == 'I';
    }
    }
    /**
  • 获取目录权限
  • @param string $user
  • @param string $path
  • @return array
  • 进行最长路径匹配
  • 返回:
  • array(
  • 'access'=>目前权限
    *,'isExactMatch'=>是否精确匹配
  • );
  • 如果精确匹配,则忽略inherit.
  • 否则应判断是否继承父目录的权限,
  • 权限位表:
  • +---+---+---+---+---+---+---+---+---+---+
  • | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
  • +---+---+---+---+---+---+---+---+---+---+
  • | R | W | A | N | D | L | C | N | D | I |
  • +---+---+---+---+---+---+---+---+---+---+
  • | FILE | FOLDER |
  • +-------------------+-------------------+
    */
    public function getPathAccess($user,$path){
    $this->reload();
    $user = strtolower($user);
    $group = $this->users[$user]['group'];
    //去除文件名称
    $path = str_replace(substr(strrchr($path,'/'),1),$path);
    $access = self::AC(0);
    $isExactMatch = false;
    if($group){
    if(isset($this->groups[$group]['folder'])){
    foreach ($this->groups[$group]['folder'] as $f){
    //中文处理
    $t_path = iconv('UTF-8','GB18030',$f['path']);
    if(strpos($path,$t_path) === 0){
    $access = $f['access'];
    $isExactMatch = ($path == $t_path?true:false);
    }
    }
    }
    }
    if(isset($this->users[$user]['folder'])){
    foreach ($this->users[$user]['folder'] as $f){
    //中文处理
    $t_path = iconv('UTF-8',$f['path']);
    if(strpos($path,$t_path) === 0){
    $access = $f['access'];
    $isExactMatch = ($path == $t_path?true:false);
    }
    }
    }
    echo date('Y-m-d H:i:s')." [debug]tACCESS:$access ".' '.($isExactMatch?'1':'0')." $pathn";
    return array('access'=>$access,'isExactMatch'=>$isExactMatch);
    }
    /**
  • 添加在线用户
  • @param ShareMemory $shm
  • @param swoole_server $serv
  • @param unknown $user
  • @param unknown $fd
  • @param unknown $ip
  • @return Ambigous <multitype:,boolean,mixed,multitype:unknown number multitype:Ambigous <unknown,number> >
    */
    public function addOnline(ShareMemory $shm,$serv,$user,$fd,$ip){
    $shm_data = $shm->read();
    if($shm_data !== false){
    $shm_data['online'][$user.'-'.$fd] = array('ip'=>$ip,'time'=>time());
    $shm_data['last_login'][] = array('user' => $user,'ip'=>$ip,'time'=>time());
    //清除旧数据
    if(count($shm_data['last_login'])>30)array_shift($shm_data['last_login']);
    $list = array();
    foreach ($shm_data['online'] as $k =>$v){
    $arr = explode('-',$k);
    if($serv->connection_info($arr[1]) !== false){
    $list[$k] = $v;
    }
    }
    $shm_data['online'] = $list;
    $shm->write($shm_data);
    }
    return $shm_data;
    }
    /**
  • 添加登陆失败记录
  • @param ShareMemory $shm
  • @param unknown $user
  • @param unknown $ip
  • @return Ambigous <number,multitype:,mixed>
    */
    public function addAttempt(ShareMemory $shm,$ip){
    $shm_data = $shm->read();
    if($shm_data !== false){
    if(isset($shm_data['login_attempt'][$ip.'||'.$user]['count'])){
    $shm_data['login_attempt'][$ip.'||'.$user]['count'] += 1;
    }else{
    $shm_data['login_attempt'][$ip.'||'.$user]['count'] = 1;
    }
    $shm_data['login_attempt'][$ip.'||'.$user]['time'] = time();
    //清除旧数据
    if(count($shm_data['login_attempt'])>30)array_shift($shm_data['login_attempt']);
    $shm->write($shm_data);
    }
    return $shm_data;
    }
    /**
  • 密码错误上限
  • @param unknown $shm
  • @param unknown $user
  • @param unknown $ip
  • @return boolean
    */
    public function isAttemptLimit(ShareMemory $shm,$ip){
    $shm_data = $shm->read();
    if($shm_data !== false){
    if(isset($shm_data['login_attempt'][$ip.'||'.$user]['count'])){
    if($shm_data['login_attempt'][$ip.'||'.$user]['count'] > 10 &&
    time() - $shm_data['login_attempt'][$ip.'||'.$user]['time'] < 600){
    return true;
    }
    }
    }
    return false;
    }
    /**
  • 生成随机密钥
  • @param int $len
  • @return Ambigous <NULL,string>
    /
    public static function genPassword($len){
    $str = null;
    $strPol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz@!#$%
    +-";
    $max = strlen($strPol)-1;
    for($i=0;$i<$len;$i++){
    $str.=$strPol[rand(0,$max)];//rand($min,$max)生成介于min和max两个数之间的一个随机整数
    }
    return $str;
    }
    }

    2.共享内存操作类

      这个相对简单,使用php的shmop扩展即可。

  • 构造函数
    /
    public function __construct(){
    $key = 'F';
    $size = 1024
    1024;
    $this->shm_key = ftok(FILE,$key);
    $this->shm_size = $size + 1;
    }
    /**
  • 读取内存数组
  • @return array|boolean
    */
    public function read(){
    if(($shm_id = shmop_open($this->shm_key,'c',$this->mode,$this->shm_size)) !== false){
    $str = shmop_read($shm_id,1,$this->shm_size-1);
    shmop_close($shm_id);
    if(($i = strpos($str,"")) !== false)$str = substr($str,$i);
    if($str){
    return json_decode($str,true);
    }else{
    return array();
    }
    }
    return false;
    }
    /**
  • 写入数组到内存
  • @param array $arr
  • @return int|boolean
    */
    public function write($arr){
    if(!is_array($arr))return false;
    $str = json_encode($arr)."";
    if(strlen($str) > $this->shm_size) return false;
    if(($shm_id = shmop_open($this->shm_key,$this->shm_size)) !== false){
    $count = shmop_write($shm_id,1);
    shmop_close($shm_id);
    return $count;
    }
    return false;
    }
    /**
  • 删除内存块,下次使用时将重新开辟内存块
  • @return boolean
    */
    public function delete(){
    if(($shm_id = shmop_open($this->shm_key,$this->shm_size)) !== false){
    $result = shmop_delete($shm_id);
    shmop_close($shm_id);
    return $result;
    }
    return false;
    }
    }

    3.内置的web服务器类

      这个主要是嵌入在ftp的http服务器类,功能不是很完善,进行ftp的管理还是可行的。不过需要注意的是,这个实现与apache等其他http服务器运行的方式可能有所不同。代码是驻留内存的。

<div class="jb51code">
<pre class="brush:php;">
class CWebServer{
protected $buffer_header = array();
protected $buffer_maxlen = 65535; //最大POST尺寸
const DATE_FORMAT_HTTP = 'D,d-M-Y H:i:s T';
const HTTP_EOF = "rnrn";
const HTTP_HEAD_MAXLEN = 8192; //http头最大长度不得超过2k
const HTTP_POST_MAXLEN = 1048576;//1m
const ST_FINISH = 1; //完成,进入处理流程
const ST_WAIT = 2; //等待数据
const ST_ERROR = 3; //错误,丢弃此包
private $requsts = array();
private $config = array();
public function log($msg,$level = 'debug'){
echo date('Y-m-d H:i:s').' ['.$level."]t" .$msg."n";
}
public function construct($config = array()){
$this->config = array(
'wwwroot' =>
DIR__.'/wwwroot/','index' => 'index.php','path_deny' => array('/protected/'),);
}
public function onReceive($serv,$data){
$ret = $this->checkData($fd,$data);
switch ($ret){
case self::ST_ERROR:
$serv->close($fd);
$this->cleanBuffer($fd);
$this->log('Recevie error.');
break;
case self::ST_WAIT:
$this->log('Recevie wait.');
return;
default:
break;
}
//开始完整的请求
$request = $this->requsts[$fd];
$info = $serv->connection_info($fd);
$request = $this->parseRequest($request);
$request['remote_ip'] = $info['remote_ip'];
$response = $this->onRequest($request);
$output = $this->parseResponse($request,$response);
$serv->send($fd,$output);
if(isset($request['head']['Connection']) && strtolower($request['head']['Connection']) == 'close'){
$serv->close($fd);
}
unset($this->requsts[$fd]);
$_REQUEST = $_SESSION = $_COOKIE = $_FILES = $_POST = $_SERVER = $_GET = array();
}
/**

  • 处理请求
  • @param array $request
  • @return array $response
  • $request=array(
  • 'time'=>
  • 'head'=>array(
  • 'method'=>
  • 'path'=>
  • 'protocol'=>
  • 'uri'=>
  • //other http header
  • '..'=>value
  • )
  • 'body'=>
  • 'get'=>(if appropriate)
  • 'post'=>(if appropriate)
  • 'cookie'=>(if appropriate)
  • )
    */
    public function onRequest($request){
    if($request['head']['path'][strlen($request['head']['path']) - 1] == '/'){
    $request['head']['path'] .= $this->config['index'];
    }
    $response = $this->process($request);
    return $response;
    }
    /**
  • 清除数据
  • @param unknown $fd
    */
    public function cleanBuffer($fd){
    unset($this->requsts[$fd]);
    unset($this->buffer_header[$fd]);
    }
    /**
  • 检查数据
  • @param unknown $fd
  • @param unknown $data
  • @return string
    */
    public function checkData($fd,$data){
    if(isset($this->buffer_header[$fd])){
    $data = $this->buffer_header[$fd].$data;
    }
    $request = $this->checkHeader($fd,$data);
    //请求头错误
    if($request === false){
    $this->buffer_header[$fd] = $data;
    if(strlen($data) > self::HTTP_HEAD_MAXLEN){
    return self::ST_ERROR;
    }else{
    return self::ST_WAIT;
    }
    }
    //post请求检查
    if($request['head']['method'] == 'POST'){
    return $this->checkPost($request);
    }else{
    return self::ST_FINISH;
    }
    }
    /**
  • 检查请求头
  • @param unknown $fd
  • @param unknown $data
  • @return boolean|array
    */
    public function checkHeader($fd,$data){
    //新的请求
    if(!isset($this->requsts[$fd])){
    //http头结束符
    $ret = strpos($data,self::HTTP_EOF);
    if($ret === false){
    return false;
    }else{
    $this->buffer_header[$fd] = '';
    $request = array();
    list($header,$request['body']) = explode(self::HTTP_EOF,$data,2);
    $request['head'] = $this->parseHeader($header);
    $this->requsts[$fd] = $request;
    if($request['head'] == false){
    return false;
    }
    }
    }else{
    //post 数据合并
    $request = $this->requsts[$fd];
    $request['body'] .= $data;
    }
    return $request;
    }
    /**
  • 解析请求头
  • @param string $header
  • @return array
  • array(
  • 'method'=>,* 'uri'=>
  • 'protocol'=>
  • 'name'=>value,...
  • }
    */
    public function parseHeader($header){
    $request = array();
    $headlines = explode("rn",$header);
    list($request['method'],$request['uri'],$request['protocol']) = explode(' ',$headlines[0],3);
    foreach ($headlines as $k=>$line){
    $line = trim($line);
    if($k && !empty($line) && strpos($line,':') !== false){
    list($name,$value) = explode(':',$line,2);
    $request[trim($name)] = trim($value);
    }
    }
    return $request;
    }
    /**
  • 检查post数据是否完整
  • @param unknown $request
  • @return string
    */
    public function checkPost($request){
    if(isset($request['head']['Content-Length'])){
    if(intval($request['head']['Content-Length']) > self::HTTP_POST_MAXLEN){
    return self::ST_ERROR;
    }
    if(intval($request['head']['Content-Length']) > strlen($request['body'])){
    return self::ST_WAIT;
    }else{
    return self::ST_FINISH;
    }
    }
    return self::ST_ERROR;
    }
    /**
  • 解析请求
  • @param unknown $request
  • @return Ambigous <unknown,multitype:string >
    */
    public function parseRequest($request){
    $request['time'] = time();
    $url_info = parse_url($request['head']['uri']);
    $request['head']['path'] = $url_info['path'];
    if(isset($url_info['fragment']))$request['head']['fragment'] = $url_info['fragment'];
    if(isset($url_info['query'])){
    parse_str($url_info['query'],$request['get']);
    }
    //parse post body
    if($request['head']['method'] == 'POST'){
    //目前只处理表单提交
    if (isset($request['head']['Content-Type']) && substr($request['head']['Content-Type'],33) == 'application/x-www-form-urlencoded'
    || isset($request['head']['X-Request-With']) && $request['head']['X-Request-With'] == 'XMLHttpRequest'){
    parse_str($request['body'],$request['post']);
    }
    }
    //parse cookies
    if(!empty($request['head']['Cookie'])){
    $params = array();
    $blocks = explode(";",$request['head']['Cookie']);
    foreach ($blocks as $b){
    $_r = explode("=",$b,2);
    if(count($_r)==2){
    list ($key,$value) = $_r;
    $params[trim($key)] = trim($value,"rn t"");
    }else{
    $params[$_r[0]] = '';
    }
    }
    $request['cookie'] = $params;
    }
    return $request;
    }
    public function parseResponse($request,$response){
    if(!isset($response['head']['Date'])){
    $response['head']['Date'] = gmdate("D,d M Y H:i:s T");
    }
    if(!isset($response['head']['Content-Type'])){
    $response['head']['Content-Type'] = 'text/html;charset=utf-8';
    }
    if(!isset($response['head']['Content-Length'])){
    $response['head']['Content-Length'] = strlen($response['body']);
    }
    if(!isset($response['head']['Connection'])){
    if(isset($request['head']['Connection']) && strtolower($request['head']['Connection']) == 'keep-alive'){
    $response['head']['Connection'] = 'keep-alive';
    }else{
    $response['head']['Connection'] = 'close';
    }
    }
    $response['head']['Server'] = CFtpServer::$software.'/'.CFtpServer::VERSION;
    $out = '';
    if(isset($response['head']['Status'])){
    $out .= 'HTTP/1.1 '.$response['head']['Status']."rn";
    unset($response['head']['Status']);
    }else{
    $out .= "HTTP/1.1 200 OKrn";
    }
    //headers
    foreach($response['head'] as $k=>$v){
    $out .= $k.': '.$v."rn";
    }
    //cookies
    if($_COOKIE){
    $arr = array();
    foreach ($_COOKIE as $k => $v){
    $arr[] = $k.'='.$v;
    }
    $out .= 'Set-Cookie: '.implode(';',$arr)."rn";
    }
    //End
    $out .= "rn";
    $out .= $response['body'];
    return $out;
    }
    /**
  • 处理请求
  • @param unknown $request
  • @return array
    */
    public function process($request){
    $path = $request['head']['path'];
    $isDeny = false;
    foreach ($this->config['path_deny'] as $p){
    if(strpos($path,$p) === 0){
    $isDeny = true;
    break;
    }
    }
    if($isDeny){
    return $this->httpError(403,'服务器拒绝访问:路径错误');
    }
    if(!in_array($request['head']['method'],array('GET','POST'))){
    return $this->httpError(500,'服务器拒绝访问:错误的请求方法');
    }
    $file_ext = strtolower(trim(substr(strrchr($path,'.'),1)));
    $path = realpath(rtrim($this->config['wwwroot'],'/'). '/' . ltrim($path,'/'));
    $this->log('WEB:['.$request['head']['method'].'] '.$request['head']['uri'] .' '.json_encode(isset($request['post'])?$request['post']:array()));
    $response = array();
    if($file_ext == 'php'){
    if(is_file($path)){
    //设置全局变量
    if(isset($request['get']))$_GET = $request['get'];
    if(isset($request['post']))$_POST = $request['post'];
    if(isset($request['cookie']))$_COOKIE = $request['cookie'];
    $_REQUEST = array_merge($_GET,$_POST,$_COOKIE);
    foreach ($request['head'] as $key => $value){
    $key = 'HTTP'.strtoupper(strreplace('-','',$key));
    $_SERVER[$_key] = $value;
    }
    $_SERVER['REMOTE_ADDR'] = $request['remote_ip'];
    $_SERVER['REQUEST_URI'] = $request['head']['uri'];
    //进行http auth
    if(isset($_GET['c']) && strtolower($_GET['c']) != 'site'){
    if(isset($request['head']['Authorization'])){
    $user = new User();
    if($user->checkUserBasicAuth($request['head']['Authorization'])){
    $response['head']['Status'] = self::$HTTP_HEADERS[200];
    goto process;
    }
    }
    $response['head']['Status'] = self::$HTTP_HEADERS[401];
    $response['head']['WWW-Authenticate'] = 'Basic realm="Real-Data-FTP"';
    $_GET['c'] = 'Site';
    $_GET['a'] = 'Unauthorized';
    }
    process:
    ob_start();
    try{
    include $path;
    $response['body'] = ob_get_contents();
    $response['head']['Content-Type'] = APP::$content_type;
    }catch (Exception $e){
    $response = $this->httpError(500,$e->getMessage());
    }
    ob_end_clean();
    }else{
    $response = $this->httpError(404,'页面不存在');
    }
    }else{
    //处理静态文件
    if(is_file($path)){
    $response['head']['Content-Type'] = isset(self::$MIME_TYPES[$file_ext]) ? self::$MIME_TYPES[$file_ext]:"application/octet-stream";
    //使用缓存
    if(!isset($request['head']['If-Modified-Since'])){
    $fstat = stat($path);
    $expire = 2592000;//30 days
    $response['head']['Status'] = self::$HTTP_HEADERS[200];
    $response['head']['Cache-Control'] = "max-age={$expire}";
    $response['head']['Pragma'] = "max-age={$expire}";
    $response['head']['Last-Modified'] = date(self::DATE_FORMAT_HTTP,$fstat['mtime']);
    $response['head']['Expires'] = "max-age={$expire}";
    $response['body'] = file_get_contents($path);
    }else{
    $response['head']['Status'] = self::$HTTP_HEADERS[304];
    $response['body'] = '';
    }
    }else{
    $response = $this->httpError(404,'页面不存在');
    }
    }
    return $response;
    }
    public function httpError($code,$content){
    $response = array();
    $version = CFtpServer::$software.'/'.CFtpServer::VERSION;
    $response['head']['Content-Type'] = 'text/html;charset=utf-8';
    $response['head']['Status'] = self::$HTTP_HEADERS[$code];
    $response['body'] = <<<html
    <!DOCTYPE html>
    <html lang="zh-CN"> FTP后台管理

    {$content}

    {$version} Copyright © 2015 by Real Data All Rights Reserved.
    html; return $response; } static $HTTP_HEADERS = array( 100 => "100 Continue",101 => "101 Switching Protocols",200 => "200 OK",201 => "201 Created",204 => "204 No Content",206 => "206 Partial Content",300 => "300 Multiple Choices",301 => "301 Moved Permanently",302 => "302 Found",303 => "303 See Other",304 => "304 Not Modified",307 => "307 Temporary Redirect",400 => "400 Bad Request",401 => "401 Unauthorized",403 => "403 Forbidden",404 => "404 Not Found",405 => "405 Method Not Allowed",406 => "406 Not Acceptable",408 => "408 Request Timeout",410 => "410 Gone",413 => "413 Request Entity Too Large",414 => "414 Request URI Too Long",415 => "415 Unsupported Media Type",416 => "416 Requested Range Not Satisfiable",417 => "417 Expectation Failed",500 => "500 Internal Server Error",501 => "501 Method Not Implemented",503 => "503 Service Unavailable",506 => "506 Variant Also Negotiates",); static $MIME_TYPES = array( 'jpg' => 'image/jpeg','bmp' => 'image/bmp','ico' => 'image/x-icon','gif' => 'image/gif','png' => 'image/png','bin' => 'application/octet-stream','js' => 'application/javascript','css' => 'text/css','html' => 'text/html','xml' => 'text/xml','tar' => 'application/x-tar','ppt' => 'application/vnd.ms-powerpoint','pdf' => 'application/pdf','svg' => ' image/svg+xml','woff' => 'application/x-font-woff','woff2' => 'application/x-font-woff',); }

    4.FTP主类

      有了前面类,就可以在ftp进行引用了。使用ssl时,请注意进行防火墙passive 端口范围的nat配置。 

  • @var swoole_server
    */
    protected $server;
    protected $connection = array();
    protected $session = array();
    protected $user;//用户类,复制验证与权限
    //共享内存类
    protected $shm;//ShareMemory
    /**
  • @var embedded http server
    /
    protected $webserver;
    /
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  • 静态方法
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
    public static function setPidFile($pid_file){
    self::$pid_file = $pid_file;
    }
    /**
  • 服务启动控制方法
    /
    public static function start($startFunc){
    if(empty(self::$pid_file)){
    exit("Require pid file.n");
    }
    if(!extension_loaded('posix')){
    exit("Require extension posix.n");
    }
    if(!extension_loaded('swoole')){
    exit("Require extension swoole.n");
    }
    if(!extension_loaded('shmop')){
    exit("Require extension shmop.n");
    }
    if(!extension_loaded('openssl')){
    exit("Require extension openssl.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("FtpServer is not runningn");
    }
    posix_kill($server_pid,SIGUSR1);
    exit;
    }elseif ($argv[1] == 'stop'){
    if (empty($server_pid)){
    exit("FtpServer is not runningn");
    }
    posix_kill($server_pid,SIGTERM);
    exit;
    }elseif ($argv[1] == 'start'){
    //已存在ServerPID,并且进程存在
    if (!empty($server_pid) and posix_kill($server_pid,(int) 0)){
    exit("FtpServer is already running.n");
    }
    //启动服务器
    $startFunc();
    }else{
    usage:
    exit("Usage: php {$argv[0]} start|stop|reloadn");
    }
    }
    /
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  • 方法
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
    public function __construct($host,$port){
    $this->user = new User();
    $this->shm = new ShareMemory();
    $this->shm->write(array());
    $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['manager_port'])){
    $this->manager_port = $this->setting['manager_port'];
    unset($this->setting['manager_port']);
    }
    if(isset($this->setting['ftps_port'])){
    $this->ftps_port = $this->setting['ftps_port'];
    unset($this->setting['ftps_port']);
    }
    if(isset($this->setting['passive_port_range'])){
    $this->pasv_port_range = $this->setting['passive_port_range'];
    unset($this->setting['passive_port_range']);
    }
    $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','onMasterStop'));
    $this->server->on('ManagerStart','onManagerStart'));
    $this->server->on('ManagerStop','onManagerStop'));
    $this->server->on('WorkerStart','onWorkerStart'));
    $this->server->on('WorkerStop','onWorkerStop'));
    $this->server->on('WorkerError','onWorkerError'));
    $this->server->on('Connect','onConnect'));
    $this->server->on('Receive','onReceive'));
    $this->server->on('Close','onClose'));
    //管理端口
    $this->server->addlistener($this->host,$this->manager_port,SWOOLE_SOCK_TCP);
    //tls
    $this->server->addlistener($this->host,$this->ftps_port,SWOOLE_SOCK_TCP | SWOOLE_SSL);
    $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" => "","" => "","r" => ""));
    $this->log("[-->]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.'/'.$this->manager_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,$from_id){
    $info = $this->getConnectionInfo($fd);
    if($info['server_port'] == $this->manager_port){
    //web请求
    $this->webserver = new CWebServer();
    }else{
    $this->send($fd,"220---------- Welcome to " . self::$software . " ----------");
    $this->send($fd,"220-Local time is now " . date("H:i"));
    $this->send($fd,"220 This is a private system - No anonymous login");
    if(count($this->server->connections) <= $this->max_connection){
    if($info['server_port'] == $this->port && isset($this->setting['force_ssl']) && $this->setting['force_ssl']){
    //如果启用强制ssl
    $this->send($fd,"421 Require implicit FTP over tls,closing control connection.");
    $this->close($fd);
    return ;
    }
    $this->connection[$fd] = array();
    $this->session = array();
    $this->queue = array();
    }else{
    $this->send($fd,"421 Too many connections,closing control connection.");
    $this->close($fd);
    }
    }
    }
    public function onReceive($serv,$from_id,$recv_data){
    $info = $this->getConnectionInfo($fd);
    if($info['server_port'] == $this->manager_port){
    //web请求
    $this->webserver->onReceive($this->server,$recv_data);
    }else{
    $read = trim($recvdata);
    $this->log("[<--]t" . $read);
    $cmd = explode(" ",$read);
    $func = 'cmd
    '.strtoupper($cmd[0]);
    $data = trim(str_replace($cmd[0],$read));
    if (!method_exists($this,$func)){
    $this->send($fd,"500 Unknown Command");
    return;
    }
    if (empty($this->connection[$fd]['login'])){
    switch($cmd[0]){
    case 'TYPE':
    case 'USER':
    case 'PASS':
    case 'QUIT':
    case 'AUTH':
    case 'PBSZ':
    break;
    default:
    $this->send($fd,"530 You aren't logged in");
    return;
    }
    }
    $this->$func($fd,$data);
    }
    }
    public function onClose($serv,$from_id){
    //在线用户
    $shm_data = $this->shm->read();
    if($shm_data !== false){
    if(isset($shm_data['online'])){
    $list = array();
    foreach($shm_data['online'] as $u => $info){
    if(!preg_match('/.
    -'.$fd.'$/',$u,$m))
    $list[$u] = $info;
    }
    $shm_data['online'] = $list;
    $this->shm->write($shm_data);
    }
    }
    $this->log('Socket '.$fd.' close. Flush the logs.','debug',true);
    }
    /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  • 工具函数
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
    /**
  • 获取用户名
  • @param $fd
    */
    public function getUser($fd){
    return isset($this->connection[$fd]['user'])?$this->connection[$fd]['user']:'';
    }
    /**
  • 获取文件全路径
  • @param $user
  • @param $file
  • @return string|boolean
    */
    public function getFile($user,$file){
    $file = $this->fillDirName($user,$file);
    if (is_file($file)){
    return realpath($file);
    }else{
    return false;
    }
    }
    /**
  • 遍历目录
  • @param $rdir
  • @param $showHidden
  • @param $format list/mlsd
  • @return string
  • list 使用local时间
  • mlsd 使用gmt时间
    */
    public function getFileList($user,$rdir,$showHidden = false,$format = 'list'){
    $filelist = '';
    if($format == 'mlsd'){
    $stats = stat($rdir);
    $filelist.= 'Type=cdir;Modify='.gmdate('YmdHis',$stats['mtime']).';UNIX.mode=d'.$this->mode2char($stats['mode']).'; '.$this->getUserDir($user)."rn";
    }
    if ($handle = opendir($rdir)){
    $isListable = $this->user->isFolderListable($user,$rdir);
    while (false !== ($file = readdir($handle))){
    if ($file == '.' or $file == '..'){
    continue;
    }
    if ($file{0} == "." and !$showHidden){
    continue;
    }
    //如果当前目录$rdir不允许列出,则判断当前目录下的目录是否配置为可以列出
    if(!$isListable){
    $dir = $rdir . $file;
    if(is_dir($dir)){
    $dir = $this->joinPath($dir,'/');
    if($this->user->isFolderListable($user,$dir)){
    goto listFolder;
    }
    }
    continue;
    }
    listFolder:
    $stats = stat($rdir . $file);
    if (is_dir($rdir . "/" . $file)) $mode = "d"; else $mode = "-";
    $mode .= $this->mode2char($stats['mode']);
    if($format == 'mlsd'){
    if($mode[0] == 'd'){
    $filelist.= 'Type=dir;Modify='.gmdate('YmdHis',$stats['mtime']).';UNIX.mode='.$mode.'; '.$file."rn";
    }else{
    $filelist.= 'Type=file;Size='.$stats['size'].';Modify='.gmdate('YmdHis',$stats['mtime']).';UNIX.mode='.$mode.'; '.$file."rn";
    }
    }else{
    $uidfill = "";
    for ($i = strlen($stats['uid']); $i < 5; $i++) $uidfill .= " ";
    $gidfill = "";
    for ($i = strlen($stats['gid']); $i < 5; $i++) $gidfill .= " ";
    $sizefill = "";
    for ($i = strlen($stats['size']); $i < 11; $i++) $sizefill .= " ";
    $nlinkfill = "";
    for ($i = strlen($stats['nlink']); $i < 5; $i++) $nlinkfill .= " ";
    $mtime = date("M d H:i",$stats['mtime']);
    $filelist .= $mode . $nlinkfill . $stats['nlink'] . " " . $stats['uid'] . $uidfill . $stats['gid'] . $gidfill . $sizefill . $stats['size'] . " " . $mtime . " " . $file . "rn";
    }
    }
    closedir($handle);
    }
    return $filelist;
    }
    /**
  • 将文件的全新从数字转换为字符串
  • @param int $int
    */
    public function mode2char($int){
    $mode = '';
    $moded = sprintf("%o",($int & 000777));
    $mode1 = substr($moded,1);
    $mode2 = substr($moded,1);
    $mode3 = substr($moded,2,1);
    switch ($mode1) {
    case "0":
    $mode .= "---";
    break;
    case "1":
    $mode .= "--x";
    break;
    case "2":
    $mode .= "-w-";
    break;
    case "3":
    $mode .= "-wx";
    break;
    case "4":
    $mode .= "r--";
    break;
    case "5":
    $mode .= "r-x";
    break;
    case "6":
    $mode .= "rw-";
    break;
    case "7":
    $mode .= "rwx";
    break;
    }
    switch ($mode2) {
    case "0":
    $mode .= "---";
    break;
    case "1":
    $mode .= "--x";
    break;
    case "2":
    $mode .= "-w-";
    break;
    case "3":
    $mode .= "-wx";
    break;
    case "4":
    $mode .= "r--";
    break;
    case "5":
    $mode .= "r-x";
    break;
    case "6":
    $mode .= "rw-";
    break;
    case "7":
    $mode .= "rwx";
    break;
    }
    switch ($mode3) {
    case "0":
    $mode .= "---";
    break;
    case "1":
    $mode .= "--x";
    break;
    case "2":
    $mode .= "-w-";
    break;
    case "3":
    $mode .= "-wx";
    break;
    case "4":
    $mode .= "r--";
    break;
    case "5":
    $mode .= "r-x";
    break;
    case "6":
    $mode .= "rw-";
    break;
    case "7":
    $mode .= "rwx";
    break;
    }
    return $mode;
    }
    /**
  • 设置用户当前的路径
  • @param $user
  • @param $pwd
    */
    public function setUserDir($user,$cdir){
    $old_dir = $this->session[$user]['pwd'];
    if ($old_dir == $cdir){
    return $cdir;
    }
    if($cdir[0] != '/')
    $cdir = $this->joinPath($old_dir,$cdir);
    $this->session[$user]['pwd'] = $cdir;
    $abs_dir = realpath($this->getAbsDir($user));
    if (!$abs_dir){
    $this->session[$user]['pwd'] = $old_dir;
    return false;
    }
    $this->session[$user]['pwd'] = $this->joinPath('/',substr($abs_dir,strlen($this->session[$user]['home'])));
    $this->session[$user]['pwd'] = $this->joinPath($this->session[$user]['pwd'],'/');
    $this->log("CHDIR: $old_dir -> $cdir");
    return $this->session[$user]['pwd'];
    }
    /**
  • 获取全路径
  • @param $user
  • @param $file
  • @return string
    */
    public function fillDirName($user,$file){
    if (substr($file,1) != "/"){
    $file = '/'.$file;
    $file = $this->joinPath($this->getUserDir( $user),$file);
    }
    $file = $this->joinPath($this->session[$user]['home'],$file);
    return $file;
    }
    /**
  • 获取用户路径
  • @param unknown $user
    */
    public function getUserDir($user){
    return $this->session[$user]['pwd'];
    }
    /**
  • 获取用户的当前文件系统绝对路径,非chroot路径
  • @param $user
  • @return string
    */
    public function getAbsDir($user){
    $rdir = $this->joinPath($this->session[$user]['home'],$this->session[$user]['pwd']);
    return $rdir;
    }
    /**
  • 路径连接
  • @param string $path1
  • @param string $path2
  • @return string
    */
    public function joinPath($path1,$path2){
    $path1 = rtrim($path1,'/');
    $path2 = trim($path2,'/');
    return $path1.'/'.$path2;
    }
    /**
  • IP判断
  • @param string $ip
  • @return boolean
    */
    public function isIPAddress($ip){
    if (!is_numeric($ip[0]) || $ip[0] < 1 || $ip[0] > 254) {
    return false;
    } elseif (!is_numeric($ip[1]) || $ip[1] < 0 || $ip[1] > 254) {
    return false;
    } elseif (!is_numeric($ip[2]) || $ip[2] < 0 || $ip[2] > 254) {
    return false;
    } elseif (!is_numeric($ip[3]) || $ip[3] < 1 || $ip[3] > 254) {
    return false;
    } elseif (!is_numeric($ip[4]) || $ip[4] < 1 || $ip[4] > 500) {
    return false;
    } elseif (!is_numeric($ip[5]) || $ip[5] < 1 || $ip[5] > 500) {
    return false;
    } else {
    return true;
    }
    }
    /**
  • 获取pasv端口
  • @return number
    */
    public function getPasvPort(){
    $min = is_int($this->pasv_port_range[0])?$this->pasv_port_range[0]:55000;
    $max = is_int($this->pasv_port_range[1])?$this->pasv_port_range[1]:60000;
    $max = $max <= 65535 ? $max : 65535;
    $loop = 0;
    $port = 0;
    while($loop < 10){
    $port = mt_rand($min,$max);
    if($this->isAvailablePasvPort($port)){
    break;
    }
    $loop++;
    }
    return $port;
    }
    public function pushPasvPort($port){
    $shm_data = $this->shm->read();
    if($shm_data !== false){
    if(isset($shm_data['pasv_port'])){
    array_push($shm_data['pasv_port'],$port);
    }else{
    $shm_data['pasv_port'] = array($port);
    }
    $this->shm->write($shm_data);
    $this->log('Push pasv port: '.implode(',',$shm_data['pasv_port']));
    return true;
    }
    return false;
    }
    public function popPasvPort($port){
    $shm_data = $this->shm->read();
    if($shm_data !== false){
    if(isset($shm_data['pasv_port'])){
    $tmp = array();
    foreach ($shm_data['pasv_port'] as $p){
    if($p != $port){
    $tmp[] = $p;
    }
    }
    $shm_data['pasv_port'] = $tmp;
    }
    $this->shm->write($shm_data);
    $this->log('Pop pasv port: '.implode(',$shm_data['pasv_port']));
    return true;
    }
    return false;
    }
    public function isAvailablePasvPort($port){
    $shm_data = $this->shm->read();
    if($shm_data !== false){
    if(isset($shm_data['pasv_port'])){
    return !in_array($port,$shm_data['pasv_port']);
    }
    return true;
    }
    return false;
    }
    /**
  • 获取当前数据链接tcp个数
    */
    public function getDataConnections(){
    $shm_data = $this->shm->read();
    if($shm_data !== false){
    if(isset($shm_data['pasv_port'])){
    return count($shm_data['pasv_port']);
    }
    }
    return 0;
    }
    /**
  • 关闭数据传输socket
  • @param $user
  • @return bool
    */
    public function closeUserSock($user){
    $peer = stream_socket_get_name($this->session[$user]['sock'],false);
    list($ip,$port) = explode(':',$peer);
    //释放端口占用
    $this->popPasvPort($port);
    fclose($this->session[$user]['sock']);
    $this->session[$user]['sock'] = 0;
    return true;
    }
    /**
  • @param $user
  • @return resource
    /
    public function getUserSock($user){
    //被动模式
    if ($this->session[$user]['pasv'] == true){
    if (empty($this->session[$user]['sock'])){
    $addr = stream_socket_get_name($this->session[$user]['serv_sock'],$addr);
    $sock = stream_socket_accept($this->session[$user]['serv_sock'],5);
    if ($sock){
    $peer = stream_socket_get_name($sock,true);
    $this->log("Accept: success client is $peer.");
    $this->session[$user]['sock'] = $sock;
    //关闭server socket
    fclose($this->session[$user]['serv_sock']);
    }else{
    $this->log("Accept: failed.");
    //释放端口
    $this->popPasvPort($port);
    return false;
    }
    }
    }
    return $this->session[$user]['sock'];
    }
    /
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  • FTP Command
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
    //==================
    //RFC959
    //==================
    /**
  • 登录用户名
  • @param $fd
  • @param $data
    */
    public function cmd_USER($fd,$data){
    if (preg_match("/^([a-z0-9.@]+)$/",$data)){
    $user = strtolower($data);
    $this->connection[$fd]['user'] = $user;
    $this->send($fd,"331 User $user OK. Password required");
    }else{
    $this->send($fd,"530 Login authentication failed");
    }
    }
    /**
  • 登录密码
  • @param $fd
  • @param $data
    */
    public function cmd_PASS($fd,$data){
    $user = $this->connection[$fd]['user'];
    $pass = $data;
    $info = $this->getConnectionInfo($fd);
    $ip = $info['remote_ip'];
    //判断登陆失败次数
    if($this->user->isAttemptLimit($this->shm,$ip)){
    $this->send($fd,"530 Login authentication failed: Too many login attempts. Blocked in 10 minutes.");
    return;
    }
    if ($this->user->checkUser($user,$ip)){
    $dir = "/";
    $this->session[$user]['pwd'] = $dir;
    //ftp根目录
    $this->session[$user]['home'] = $this->user->getHomeDir($user);
    if(empty($this->session[$user]['home']) || !is_dir($this->session[$user]['home'])){
    $this->send($fd,"530 Login authentication failed: home path error.");
    }else{
    $this->connection[$fd]['login'] = true;
    //在线用户
    $shm_data = $this->user->addOnline($this->shm,$this->server,$ip);
    $this->log('SHM: '.json_encode($shm_data) );
    $this->send($fd,"230 OK. Current restricted directory is " . $dir);
    $this->log('User '.$user .' has login successfully! IP: '.$ip,'warn');
    }
    }else{
    $this->user->addAttempt($this->shm,$ip);
    $this->log('User '.$user .' login fail! IP: '.$ip,'warn');
    $this->send($fd,"530 Login authentication failed: check your pass or ip allow rules.");
    }
    }
    /**
  • 更改当前目录
  • @param $fd
  • @param $data
    */
    public function cmd_CWD($fd,$data){
    $user = $this->getUser($fd);
    if (($dir = $this->setUserDir($user,$data)) != false){
    $this->send($fd,"250 OK. Current directory is " . $dir);
    }else{
    $this->send($fd,"550 Can't change directory to " . $data . ": No such file or directory");
    }
    }
    /**
  • 返回上级目录
  • @param $fd
  • @param $data
    */
    public function cmd_CDUP($fd,$data){
    $data = '..';
    $this->cmd_CWD($fd,$data);
    }
    /**
  • 退出服务器
  • @param $fd
  • @param $data
    */
    public function cmd_QUIT($fd,$data){
    $this->send($fd,"221 Goodbye.");
    unset($this->connection[$fd]);
    }
    /**
  • 获取当前目录
  • @param $fd
  • @param $data
    */
    public function cmd_PWD($fd,$data){
    $user = $this->getUser($fd);
    $this->send($fd,"257 "" . $this->getUserDir($user) . "" is your current location");
    }
    /**
  • 下载文件
  • @param $fd
  • @param $data
    */
    public function cmd_RETR($fd,$data){
    $user = $this->getUser($fd);
    $ftpsock = $this->getUserSock($user);
    if (!$ftpsock){
    $this->send($fd,"425 Connection Error");
    return;
    }
    if (($file = $this->getFile($user,$data)) != false){
    if($this->user->isReadable($user,$file)){
    $this->send($fd,"150 Connecting to client");
    if ($fp = fopen($file,"rb")){
    //断点续传
    if(isset($this->session[$user]['rest_offset'])){
    if(!fseek($fp,$this->session[$user]['rest_offset'])){
    $this->log("RETR at offset ".ftell($fp));
    }else{
    $this->log("RETR at offset ".ftell($fp).' fail.');
    }
    unset($this->session[$user]['rest_offset']);
    }
    while (!feof($fp)){
    $cont = fread($fp,8192);
    if (!fwrite($ftpsock,$cont)) break;
    }
    if (fclose($fp) and $this->closeUserSock($user)){
    $this->send($fd,"226 File successfully transferred");
    $this->log($user."tGET:".$file,'info');
    }else{
    $this->send($fd,"550 Error during file-transfer");
    }
    }else{
    $this->send($fd,"550 Can't open " . $data . ": Permission denied");
    }
    }else{
    $this->send($fd,"550 You're unauthorized: Permission denied");
    }
    }else{
    $this->send($fd,"550 Can't open " . $data . ": No such file or directory");
    }
    }
    /**
  • 上传文件
  • @param $fd
  • @param $data
    */
    public function cmd_STOR($fd,"425 Connection Error");
    return;
    }
    $file = $this->fillDirName($user,$data);
    $isExist = false;
    if(file_exists($file))$isExist = true;
    if((!$isExist && $this->user->isWritable($user,$file)) ||
    ($isExist && $this->user->isAppendable($user,$file))){
    if($isExist){
    $fp = fopen($file,"rb+");
    $this->log("OPEN for STOR.");
    }else{
    $fp = fopen($file,'wb');
    $this->log("CREATE for STOR.");
    }
    if (!$fp){
    $this->send($fd,"553 Can't open that file: Permission denied");
    }else{
    //断点续传,需要Append权限
    if(isset($this->session[$user]['rest_offset'])){
    if(!fseek($fp,$this->session[$user]['rest_offset'])){
    $this->log("STOR at offset ".ftell($fp));
    }else{
    $this->log("STOR at offset ".ftell($fp).' fail.');
    }
    unset($this->session[$user]['rest_offset']);
    }
    $this->send($fd,"150 Connecting to client");
    while (!feof($ftpsock)){
    $cont = fread($ftpsock,8192);
    if (!$cont) break;
    if (!fwrite($fp,$cont)) break;
    }
    touch($file);//设定文件的访问和修改时间
    if (fclose($fp) and $this->closeUserSock($user)){
    $this->send($fd,"226 File successfully transferred");
    $this->log($user."tPUT: $file","550 Error during file-transfer");
    }
    }
    }else{
    $this->send($fd,"550 You're unauthorized: Permission denied");
    $this->closeUserSock($user);
    }
    }
    /**
  • 文件追加
  • @param $fd
  • @param $data
    */
    public function cmd_APPE($fd,$file))){
    $fp = fopen($file,"rb+");
    if (!$fp){
    $this->send($fd,$this->session[$user]['rest_offset'])){
    $this->log("APPE at offset ".ftell($fp));
    }else{
    $this->log("APPE at offset ".ftell($fp).' fail.');
    }
    unset($this->session[$user]['rest_offset']);
    }
    $this->send($fd,"226 File successfully transferred");
    $this->log($user."tAPPE: $file","550 You're unauthorized: Permission denied");
    $this->closeUserSock($user);
    }
    }
    /**
  • 文件重命名,源文件
  • @param $fd
  • @param $data
    */
    public function cmd_RNFR($fd,$data){
    $user = $this->getUser($fd);
    $file = $this->fillDirName($user,$data);
    if (file_exists($file) || is_dir($file)){
    $this->session[$user]['rename'] = $file;
    $this->send($fd,"350 RNFR accepted - file exists,ready for destination");
    }else{
    $this->send($fd,"550 Sorry,but that '$data' doesn't exist");
    }
    }
    /**
  • 文件重命名,目标文件
  • @param $fd
  • @param $data
    */
    public function cmd_RNTO($fd,$data){
    $user = $this->getUser($fd);
    $old_file = $this->session[$user]['rename'];
    $new_file = $this->fillDirName($user,$data);
    $isDir = false;
    if(is_dir($old_file)){
    $isDir = true;
    $old_file = $this->joinPath($old_file,'/');
    }
    if((!$isDir && $this->user->isRenamable($user,$old_file)) ||
    ($isDir && $this->user->isFolderRenamable($user,$old_file))){
    if (empty($old_file) or !is_dir(dirname($new_file))){
    $this->send($fd,"451 Rename/move failure: No such file or directory");
    }elseif (rename($old_file,$new_file)){
    $this->send($fd,"250 File successfully renamed or moved");
    $this->log($user."tRENAME: $old_file to $new_file",'warn');
    }else{
    $this->send($fd,"451 Rename/move failure: Operation not permitted");
    }
    }else{
    $this->send($fd,"550 You're unauthorized: Permission denied");
    }
    unset($this->session[$user]['rename']);
    }
    /**
  • 删除文件
  • @param $fd
  • @param $data
    */
    public function cmd_DELE($fd,$data);
    if($this->user->isDeletable($user,$file)){
    if (!file_exists($file)){
    $this->send($fd,"550 Could not delete " . $data . ": No such file or directory");
    }
    elseif (unlink($file)){
    $this->send($fd,"250 Deleted " . $data);
    $this->log($user."tDEL: $file","550 Could not delete " . $data . ": Permission denied");
    }
    }else{
    $this->send($fd,"550 You're unauthorized: Permission denied");
    }
    }
    /**
  • 创建目录
  • @param $fd
  • @param $data
    */
    public function cmd_MKD($fd,$data){
    $user = $this->getUser($fd);
    $path = '';
    if($data[0] == '/'){
    $path = $this->joinPath($this->session[$user]['home'],$data);
    }else{
    $path = $this->joinPath($this->getAbsDir($user),$data);
    }
    $path = $this->joinPath($path,'/');
    if($this->user->isFolderCreatable($user,$path)){
    if (!is_dir(dirname($path))){
    $this->send($fd,"550 Can't create directory: No such file or directory");
    }elseif(file_exists($path)){
    $this->send($fd,"550 Can't create directory: File exists");
    }else{
    if (mkdir($path)){
    $this->send($fd,"257 "" . $data . "" : The directory was successfully created");
    $this->log($user."tMKDIR: $path","550 Can't create directory: Permission denied");
    }
    }
    }else{
    $this->send($fd,"550 You're unauthorized: Permission denied");
    }
    }
    /**
  • 删除目录
  • @param $fd
  • @param $data
    /
    public function cmd_RMD($fd,$data){
    $user = $this->getUser($fd);
    $dir = '';
    if($data[0] == '/'){
    $dir = $this->joinPath($this->session[$user]['home'],$data);
    }else{
    $dir = $this->fillDirName($user,$data);
    }
    $dir = $this->joinPath($dir,'/');
    if($this->user->isFolderDeletable($user,$dir)){
    if (is_dir(dirname($dir)) and is_dir($dir)){
    if (count(glob($dir . "/
    "))){
    $this->send($fd,"550 Can't remove directory: Directory not empty");
    }elseif (rmdir($dir)){
    $this->send($fd,"250 The directory was successfully removed");
    $this->log($user."tRMDIR: $dir","550 Can't remove directory: Operation not permitted");
    }
    }elseif (is_dir(dirname($dir)) and file_exists($dir)){
    $this->send($fd,"550 Can't remove directory: Not a directory");
    }else{
    $this->send($fd,"550 Can't create directory: No such file or directory");
    }
    }else{
    $this->send($fd,"550 You're unauthorized: Permission denied");
    }
    }
    /**
  • 得到服务器类型
  • @param $fd
  • @param $data
    */
    public function cmd_SYST($fd,"215 UNIX Type: L8");
    }
    /**
  • 权限控制
  • @param $fd
  • @param $data
    */
    public function cmd_SITE($fd,$data){
    if (substr($data,6) == "CHMOD "){
    $user = $this->getUser($fd);
    $chmod = explode(" ",3);
    $file = $this->fillDirName($user,$chmod[2]);
    if($this->user->isWritable($user,$file)){
    if (chmod($file,octdec($chmod[1]))){
    $this->send($fd,"200 Permissions changed on {$chmod[2]}");
    $this->log($user."tCHMOD: $file to {$chmod[1]}","550 Could not change perms on " . $chmod[2] . ": Permission denied");
    }
    }else{
    $this->send($fd,"500 Unknown Command");
    }
    }
    /**
  • 更改传输类型
  • @param $fd
  • @param $data
    */
    public function cmd_TYPE($fd,$data){
    switch ($data){
    case "A":
    $type = "ASCII";
    break;
    case "I":
    $type = "8-bit binary";
    break;
    }
    $this->send($fd,"200 TYPE is now " . $type);
    }
    /**
  • 遍历目录
  • @param $fd
  • @param $data
    */
    public function cmd_LIST($fd,"425 Connection Error");
    return;
    }
    $path = $this->joinPath($this->getAbsDir($user),'/');
    $this->send($fd,"150 Opening ASCII mode data connection for file list");
    $filelist = $this->getFileList($user,$path,true);
    fwrite($ftpsock,$filelist);
    $this->send($fd,"226 Transfer complete.");
    $this->closeUserSock($user);
    }
    /**
  • 建立数据传输通
  • @param $fd
  • @param $data
    */
    // 不使用主动模式
    // public function cmd_PORT($fd,$data){
    // $user = $this->getUser($fd);
    // $port = explode(",",$data);
    // if (count($port) != 6){
    // $this->send($fd,"501 Syntax error in IP address");
    // }else{
    // if (!$this->isIPAddress($port)){
    // $this->send($fd,"501 Syntax error in IP address");
    // return;
    // }
    // $ip = $port[0] . "." . $port[1] . "." . $port[2] . "." . $port[3];
    // $port = hexdec(dechex($port[4]) . dechex($port[5]));
    // if ($port < 1024){
    // $this->send($fd,"501 Sorry,but I won't connect to ports < 1024");
    // }elseif ($port > 65000){
    // $this->send($fd,but I won't connect to ports > 65000");
    // }else{
    // $ftpsock = fsockopen($ip,$port);
    // if ($ftpsock){
    // $this->session[$user]['sock'] = $ftpsock;
    // $this->session[$user]['pasv'] = false;
    // $this->send($fd,"200 PORT command successful");
    // }else{
    // $this->send($fd,"501 Connection failed");
    // }
    // }
    // }
    // }
    /**
  • 被动模式
  • @param unknown $fd
  • @param unknown $data
    /
    public function cmd_PASV($fd,$data){
    $user = $this->getUser($fd);
    $ssl = false;
    $pasv_port = $this->getPasvPort();
    if($this->connection[$fd]['ssl'] === true){
    $ssl = true;
    $context = stream_context_create();
    // local_cert must be in PEM format
    stream_context_set_option($context,'ssl','local_cert',$this->setting['ssl_cert_file']);
    // Path to local private key file
    stream_context_set_option($context,'local_pk',$this->setting['ssl_key_file']);
    stream_context_set_option($context,'allow_self_signed',true);
    stream_context_set_option($context,'verify_peer',false);
    stream_context_set_option($context,'verify_peer_name',false);
    stream_context_set_option($context,'passphrase','');
    // Create the server socket
    $sock = stream_socket_server('ssl://0.0.0.0:'.$pasv_port,$errno,$errstr,STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,$context);
    }else{
    $sock = stream_socket_server('tcp://0.0.0.0:'.$pasv_port,STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
    }
    if ($sock){
    $addr = stream_socket_get_name($sock,$addr);
    $ipArr = swoole_get_local_ip();
    foreach($ipArr as $nic => $addr){
    $ip = $addr;
    }
    $this->log("ServerSock: $ip:$port");
    $ip = str_replace('.',$ip);
    $this->send($fd,"227 Entering Passive Mode ({$ip},".(intval($port) >> 8 & 0xff).",".(intval($port) & 0xff)."). ".$port." ".($ssl?'ssl':''));
    $this->session[$user]['serv_sock'] = $sock;
    $this->session[$user]['pasv'] = true;
    $this->pushPasvPort($port);
    }else{
    fclose($sock);
    $this->send($fd,"500 failed to create data socket: ".$errstr);
    }
    }
    public function cmd_NOOP($fd,"200 OK");
    }
    //==================
    //RFC2228
    //==================
    public function cmd_PBSZ($fd,'200 Command okay.');
    }
    public function cmd_PROT($fd,$data){
    if(trim($data) == 'P'){
    $this->connection[$fd]['ssl'] = true;
    $this->send($fd,'200 Set Private level on data connection.');
    }elseif(trim($data) == 'C'){
    $this->connection[$fd]['ssl'] = false;
    $this->send($fd,'200 Set Clear level on data connection.');
    }else{
    $this->send($fd,'504 Command not implemented for that parameter.');
    }
    }
    //==================
    //RFC2389
    //==================
    public function cmd_FEAT($fd,'211-Features supported');
    $this->send($fd,'MDTM');
    $this->send($fd,'SIZE');
    $this->send($fd,'SITE CHMOD');
    $this->send($fd,'REST STREAM');
    $this->send($fd,'MLSD Type
    ;Size;Modify;UNIX.mode*;');
    $this->send($fd,'PBSZ');
    $this->send($fd,'PROT');
    $this->send($fd,'211 End');
    }
    //关闭utf8对中文文件名有影响
    public function cmd_OPTS($fd,'502 Command not implemented.');
    }
    //==================
    //RFC3659
    //==================
    /**
  • 获取文件修改时间
  • @param unknown $fd
  • @param unknown $data
    */
    public function cmd_MDTM($fd,$data){
    $user = $this->getUser($fd);
    if (($file = $this->getFile($user,'213 '.date('YmdHis.u',filemtime($file)));
    }else{
    $this->send($fd,'550 No file named "'.$data.'"');
    }
    }
    /**
  • 获取文件大小
  • @param $fd
  • @param $data
    */
    public function cmd_SIZE($fd,'213 '.filesize($file));
    }else{
    $this->send($fd,'550 No file named "'.$data.'"');
    }
    }
    /**
  • 获取文件列表
  • @param unknown $fd
  • @param unknown $data
    */
    public function cmd_MLSD($fd,"425 Connection Error");
    return;
    }
    $path = $this->joinPath($this->getAbsDir($user),true,'mlsd');
    fwrite($ftpsock,$filelist);
    $this->send($fd,"226 Transfer complete.");
    $this->closeUserSock($user);
    }
    /**
  • 设置文件offset
  • @param unknown $fd
  • @param unknown $data
    */
    public function cmd_REST($fd,$data){
    $user = $this->getUser($fd);
    $data= preg_replace('/[^0-9]/',$data);
    if($data != ''){
    $this->session[$user]['rest_offset'] = $data;
    $this->send($fd,'350 Restarting at '.$data.'. Send STOR or RETR');
    }else{
    $this->send($fd,'500 Syntax error,offset unrecognized.');
    }
    }
    /**
  • 获取文件hash值
  • @param unknown $fd
  • @param unknown $data
    */
    public function cmd_HASH($fd,$data){
    $user = $this->getUser($fd);
    $ftpsock = $this->getUserSock($user);
    if (($file = $this->getFile($user,$data)) != false){
    if(is_file($file)){
    $algo = 'sha512';
    $this->send($fd,"200 ".hash_file($algo,$file));
    }else{
    $this->send($fd,"550 Can't open " . $data . ": No such file。");
    }
    }else{
    $this->send($fd,"550 Can't open " . $data . ": No such file。");
    }
    }
    /**
  • 控制台命令
  • @param unknown $fd
  • @param unknown $data
    */
    public function cmd_CONSOLE($fd,$data){
    $group = $this->user->getUserProfile($this->getUser($fd));
    $group = $group['group'];
    if($group != 'admin'){
    $this->send($fd,"550 You're unauthorized: Permission denied");
    return;
    }
    $data = explode('||',$data);
    $cmd = strtoupper($data[0]);
    switch ($cmd){
    case 'USER-ONLINE':
    $shm_data = $this->shm->read();
    $list = array();
    if($shm_data !== false){
    if(isset($shm_data['online'])){
    $list = $shm_data['online'];
    }
    }
    $this->send($fd,'200 '.json_encode($list));
    break;
    //Format: user-add||{"user":"","pass":"","home":"","expired":"","active":boolean,"group":"","description":"","email":""}
    case 'USER-ADD':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $user = isset($json['user'])?$json['user']:'';
    $pass = isset($json['pass'])?$json['pass']:'';
    $home = isset($json['home'])?$json['home']:'';
    $expired = isset($json['expired'])?$json['expired']:'1999-01-01';
    $active = isset($json['active'])?$json['active']:false;
    $group = isset($json['group'])?$json['group']:'';
    $description = isset($json['description'])?$json['description']:'';
    $email = isset($json['email'])?$json['email']:'';
    if($this->user->addUser($user,$active,$group,$description,$email)){
    $this->user->save();
    $this->user->reload();
    $this->send($fd,'200 User "'.$user.'" added.');
    }else{
    $this->send($fd,'550 Add fail!');
    }
    }else{
    $this->send($fd,'500 Syntax error: USER-ADD||{"user":"","description":""}');
    }
    break;
    //Format: user-set-profile||{"user":"","profile":[]}
    case 'USER-SET-PROFILE':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $user = isset($json['user'])?$json['user']:'';
    $profile = isset($json['profile'])?$json['profile']:array();
    if($this->user->setUserProfile($user,$profile)){
    $this->user->save();
    $this->user->reload();
    $this->send($fd,'200 User "'.$user.'" profile changed.');
    }else{
    $this->send($fd,'550 Set profile fail!');
    }
    }else{
    $this->send($fd,'500 Syntax error: USER-SET-PROFILE||{"user":"","profile":[]}');
    }
    break;
    //Format: user-get-profile||{"user":""}
    case 'USER-GET-PROFILE':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $user = isset($json['user'])?$json['user']:'';
    $this->user->reload();
    if($profile = $this->user->getUserProfile($user)){
    $this->send($fd,'200 '.json_encode($profile));
    }else{
    $this->send($fd,'550 Get profile fail!');
    }
    }else{
    $this->send($fd,'500 Syntax error: USER-GET-PROFILE||{"user":""}');
    }
    break;
    //Format: user-delete||{"user":""}
    case 'USER-DELETE':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $user = isset($json['user'])?$json['user']:'';
    if($this->user->delUser($user)){
    $this->user->save();
    $this->user->reload();
    $this->send($fd,'200 User '.$user.' deleted.');
    }else{
    $this->send($fd,'550 Delete user fail!');
    }
    }else{
    $this->send($fd,'500 Syntax error: USER-DELETE||{"user":""}');
    }
    break;
    case 'USER-LIST':
    $this->user->reload();
    $list = $this->user->getUserList();
    $this->send($fd,'200 '.json_encode($list));
    break;
    //Format: group-add||{"group":"","home":""}
    case 'GROUP-ADD':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $group = isset($json['group'])?$json['group']:'';
    $home = isset($json['home'])?$json['home']:'';
    if($this->user->addGroup($group,$home)){
    $this->user->save();
    $this->user->reload();
    $this->send($fd,'200 Group "'.$group.'" added.');
    }else{
    $this->send($fd,'550 Add group fail!');
    }
    }else{
    $this->send($fd,'500 Syntax error: GROUP-ADD||{"group":"","home":""}');
    }
    break;
    //Format: group-set-profile||{"group":"","profile":[]}
    case 'GROUP-SET-PROFILE':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $group = isset($json['group'])?$json['group']:'';
    $profile = isset($json['profile'])?$json['profile']:array();
    if($this->user->setGroupProfile($group,'200 Group "'.$group.'" profile changed.');
    }else{
    $this->send($fd,'500 Syntax error: GROUP-SET-PROFILE||{"group":"","profile":[]}');
    }
    break;
    //Format: group-get-profile||{"group":""}
    case 'GROUP-GET-PROFILE':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $group = isset($json['group'])?$json['group']:'';
    $this->user->reload();
    if($profile = $this->user->getGroupProfile($group)){
    $this->send($fd,'500 Syntax error: GROUP-GET-PROFILE||{"group":""}');
    }
    break;
    //Format: group-delete||{"group":""}
    case 'GROUP-DELETE':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $group = isset($json['group'])?$json['group']:'';
    if($this->user->delGroup($group)){
    $this->user->save();
    $this->user->reload();
    $this->send($fd,'200 Group '.$group.' deleted.');
    }else{
    $this->send($fd,'550 Delete group fail!');
    }
    }else{
    $this->send($fd,'500 Syntax error: GROUP-DELETE||{"group":""}');
    }
    break;
    case 'GROUP-LIST':
    $this->user->reload();
    $list = $this->user->getGroupList();
    $this->send($fd,'200 '.json_encode($list));
    break;
    //获取组用户列表
    //Format: group-user-list||{"group":""}
    case 'GROUP-USER-LIST':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $group = isset($json['group'])?$json['group']:'';
    $this->user->reload();
    $this->send($fd,'200 '.json_encode($this->user->getUserListOfGroup($group)));
    }else{
    $this->send($fd,'500 Syntax error: GROUP-USER-LIST||{"group":""}');
    }
    break;
    // 获取磁盘空间
    //Format: disk-total||{"path":""}
    case 'DISK-TOTAL':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $path = isset($json['path'])?$json['path']:'';
    $size = 0;
    if($path){
    $size = disk_total_space($path);
    }
    $this->send($fd,'200 '.$size);
    }else{
    $this->send($fd,'500 Syntax error: DISK-TOTAL||{"path":""}');
    }
    break;
    // 获取磁盘空间
    //Format: disk-total||{"path":""}
    case 'DISK-FREE':
    if(isset($data[1])){
    $json = json_decode(trim($data[1]),true);
    $path = isset($json['path'])?$json['path']:'';
    $size = 0;
    if($path){
    $size = disk_free_space($path);
    }
    $this->send($fd,'500 Syntax error: DISK-FREE||{"path":""}');
    }
    break;
    case 'HELP':
    $list = 'USER-ONLINE USER-ADD USER-SET-PROFILE USER-GET-PROFILE USER-DELETE USER-LIST GROUP-ADD GROUP-SET-PROFILE GROUP-GET-PROFILE GROUP-DELETE GROUP-LIST GROUP-USER-LIST DISK-TOTAL DISK-FREE';
    $this->send($fd,'200 '.$list);
    break;
    default:
    $this->send($fd,'500 Syntax error.');
    }
    }
    }

    总结:

    至此,我们就可以实现一个完整的ftp服务器了。这个服务器的功能可以进行完全个性化定制。如果您有好的建议,也可以留言给我,谢谢。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读