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

1.实现用户类CUser。

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

用户文件格式:

* array(* "user1" => 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,ip2,...)* )* ) * )* * 组文件格式:* array(* "group1"=>array(* "home"=>"/home/ftp/dept1/",* "folder"=>array(* * ),* "ip"=>array(* "allow"=>array(ip1,ip2,...),* "deny"=>array(ip1,ip2,...)* )* )* ) 

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

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

  实现代码如下:  

class User{const I = 1; // inheritconst FD = 2; // folder deleteconst FN = 4; // folder renameconst FC = 8; // folder createconst FL = 16; // folder listconst D = 32; // file deleteconst N = 64; // file renameconst A = 128; // file appendconst 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","N","D","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,$pass,$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("*","d+",str_replace(".", ".", $addr))."/";if(preg_match($pattern, $ip) && !empty($addr)){$pass = false;break;}}}}if(isset($this->users[$user]["ip"]["allow"])){ foreach ($this->users[$user]["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->users[$user]["ip"]["deny"])){foreach ($this->users[$user]["ip"]["deny"] as $addr){$pattern = "/".str_replace("*","d+",str_replace(".", ".", $addr))."/";if(preg_match($pattern, $ip) && !empty($addr)){$pass = false;break;}}}}echo date("Y-m-d H:i:s")." [debug]	IP ACCESS:"." ".($pass?"true":"false")."";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){ $result = $this->getPathAccess($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){$result = $this->getPathAccess($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){ $result = $this->getPathAccess($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){$result = $this->getPathAccess($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){$result = $this->getPathAccess($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){$result = $this->getPathAccess($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){$result = $this->getPathAccess($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","GB18030",$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]	ACCESS:$access "." ".($isExactMatch?"1":"0")." $path";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:, boolean, mixed>*/public function addAttempt(ShareMemory $shm ,$user,$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,$user,$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扩展即可。

class ShareMemory{private $mode = 0644;private $shm_key;private $shm_size;/*** 构造函数 */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,0,$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,"c",$this->mode,$this->shm_size)) !== false){ $count = shmop_write($shm_id,$str,1);shmop_close($shm_id);return $count;}return false;}/*** 删除内存块,下次使用时将重新开辟内存块* @return boolean*/public function delete(){if(($shm_id = shmop_open($this->shm_key,"c",$this->mode,$this->shm_size)) !== false){$result = shmop_delete($shm_id);shmop_close($shm_id);return $result;}return false;}} 

3.内置的web服务器类

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

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 = "";const HTTP_HEAD_MAXLEN = 8192; //http头最大长度不得超过2kconst HTTP_POST_MAXLEN = 1048576;//1mconst 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."]	" .$msg."";}public function __construct($config = array()){$this->config = array("wwwroot" => __DIR__."/wwwroot/","index" => "index.php","path_deny" => array("/protected/"), ); }public function onReceive($serv,$fd,$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("", $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, mixed, 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 bodyif($request["head"]["method"] == "POST"){//目前只处理表单提交 if (isset($request["head"]["Content-Type"]) && substr($request["head"]["Content-Type"], 0, 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 cookiesif(!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, " 	"");}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"]."";unset($response["head"]["Status"]);}else{$out .= "HTTP/1.1 200 OK";}//headersforeach($response["head"] as $k=>$v){$out .= $k.": ".$v."";}//cookiesif($_COOKIE){ $arr = array();foreach ($_COOKIE as $k => $v){$arr[] = $k."=".$v; }$out .= "Set-Cookie: ".implode(";", $arr)."";}//End$out .= "";$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(str_replace("-", "_", $key));$_SERVER[$_key] = $value;}$_SERVER["REMOTE_ADDR"] = $request["remote_ip"];$_SERVER["REQUEST_URI"] = $request["head"]["uri"]; //进行http authif(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"><head><meta charset="utf-8"> <title>FTP后台管理 </title></head><body><p>{$content}</p><div style="text-align:center"><hr>{$version} Copyright © 2015 by <a target="_new" href="http://www.realdatamed.com">Real Data</a> All Rights Reserved.</div></body></html>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配置。 

defined("DEBUG_ON") or define("DEBUG_ON", false);//主目录defined("BASE_PATH") or define("BASE_PATH", __DIR__);require_once BASE_PATH."/inc/User.php";require_once BASE_PATH."/inc/ShareMemory.php";require_once BASE_PATH."/web/CWebServer.php";require_once BASE_PATH."/inc/CSmtp.php";class CFtpServer{//软件版本const VERSION = "2.0"; const EOF = ""; public static $software "FTP-Server";private static $server_mode = SWOOLE_PROCESS; private static $pid_file;private static $log_file; //待写入文件的日志队列(缓冲区)private $queue = array();private $pasv_port_range = array(55000,60000);public $host = "0.0.0.0";public $port = 21;public $setting = array();//最大连接数public $max_connection = 50; //web管理端口public $manager_port = 8080;//tlspublic $ftps_port = 990;/*** @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."); }if(!extension_loaded("posix")){ exit("Require extension `posix`."); }if(!extension_loaded("swoole")){ exit("Require extension `swoole`."); }if(!extension_loaded("shmop")){exit("Require extension `shmop`.");}if(!extension_loaded("openssl")){exit("Require extension `openssl`.");}$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 running");}posix_kill($server_pid, SIGUSR1);exit;}elseif ($argv[1] == "stop"){if (empty($server_pid)){exit("FtpServer is not running");}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.");}//启动服务器$startFunc(); }else{usage:exit("Usage: php {$argv[0]} start|stop|reload");}}/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 方法+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/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",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->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."]	" .$msg."";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){//10Munlink($debug_file);}}echo $log; }if($level != "debug"){//日志记录 $this->queue[] = date("Y-m-d H:i:s")."	[".$level."]	".$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."";}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("" => "", "" => "", "" => ""));$this->log("[-->]	" . $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,$fd,$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,$fd,$from_id,$recv_data){$info = $this->getConnectionInfo($fd);if($info["server_port"] == $this->manager_port){//web请求$this->webserver->onReceive($this->server, $fd, $recv_data);}else{$read = trim($recv_data);$this->log("[<--]	" . $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,$fd,$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)."";}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."";}else{$filelist.= "Type=file;Size=".$stats["size"].";Modify=".gmdate("YmdHis",$stats["mtime"]).";UNIX.mode=".$mode."; ".$file."";}}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 . "";}}closedir($handle);}return $filelist;}/*** 将文件的全新从数字转换为字符串* @param int $int*/public function mode2char($int){$mode = "";$moded = sprintf("%o", ($int & 000777));$mode1 = substr($moded, 0, 1);$mode2 = substr($moded, 1, 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, 0, 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"], false);list($ip, $port) = explode(":", $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 socketfclose($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, $user, $ip)){$this->send($fd, "530 Login authentication failed: Too many login attempts. Blocked