使用PHP如何实现高效安全的ftp服务器(二)
在上篇文章给大家介绍了使用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; // 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','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]\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){ $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]\taccess:$access ".' '.($isexactmatch?'1':'0')." $path\n"; 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,"\0")) !== 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)."\0"; 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 = "\r\n\r\n"; 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,$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("\r\n", $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 body if($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 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, "\r\n \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']."\r\n"; unset($response['head']['status']); }else{ $out .= "http/1.1 200 ok\r\n"; } //headers foreach($response['head'] as $k=>$v){ $out .= $k.': '.$v."\r\n"; } //cookies if($_cookie){ $arr = array(); foreach ($_cookie as $k => $v){ $arr[] = $k.'='.$v; } $out .= 'set-cookie: '.implode(';', $arr)."\r\n"; } //end $out .= "\r\n"; $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 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"> <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 = "\r\n"; 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; //tls public $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.\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 running\n"); } posix_kill($server_pid, sigusr1); exit; }elseif ($argv[1] == 'stop'){ if (empty($server_pid)){ exit("ftpserver is not running\n"); } posix_kill($server_pid, sigterm); exit; }elseif ($argv[1] == 'start'){ //已存在serverpid,并且进程存在 if (!empty($server_pid) and posix_kill($server_pid,(int) 0)){ exit("ftpserver is already running.\n"); } //启动服务器 $startfunc(); }else{ usage: exit("usage: php {$argv[0]} start|stop|reload\n"); } } /*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + 方法 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ 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."]\t" .$msg."\n"; if(!empty(self::$log_file)){ $debug_file = dirname(self::$log_file).'/debug.log'; file_put_contents($debug_file, $log,file_append); if(filesize($debug_file) > 10485760){//10m unlink($debug_file); } } echo $log; } if($level != 'debug'){ //日志记录 $this->queue[] = date('y-m-d h:i:s')."\t[".$level."]\t".$msg; } if(count($this->queue)>10 && !empty(self::$log_file) || $flush){ if (filesize(self::$log_file) > 209715200){ //200m rename(self::$log_file,self::$log_file.'.'.date('his')); } $logs = ''; foreach ($this->queue as $q){ $logs .= $q."\n"; } file_put_contents(self::$log_file, $logs,file_append); $this->queue = array(); } } public function shutdown(){ return $this->server->shutdown(); } public function close($fd){ return $this->server->close($fd); } public function send($fd,$data){ $data = strtr($data,array("\n" => "", "\0" => "", "\r" => "")); $this->log("[-->]\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,$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("[<--]\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,$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)."\r\n"; } 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."\r\n"; }else{ $filelist.= 'type=file;size='.$stats['size'].';modify='.gmdate('ymdhis',$stats['mtime']).';unix.mode='.$mode.'; '.$file."\r\n"; } }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 . "\r\n"; } } 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 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, $user, $ip)){ $this->send($fd, "530 login authentication failed: too many login attempts. blocked in 10 minutes."); return; } if ($this->user->checkuser($user, $pass, $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, $user, $fd, $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, $user, $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, $data){ $user = $this->getuser($fd); $ftpsock = $this->getusersock($user); if (!$ftpsock){ $this->send($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",'info'); }else{ $this->send($fd, "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,$data){ $user = $this->getuser($fd); $ftpsock = $this->getusersock($user); if (!$ftpsock){ $this->send($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))){ $fp = fopen($file, "rb+"); 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("appe at offset ".ftell($fp)); }else{ $this->log("appe 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."\tappe: $file",'info'); }else{ $this->send($fd, "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_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){ $user = $this->getuser($fd); $file = $this->filldirname($user, $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",'warn'); }else{ $this->send($fd, "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",'info'); }else{ $this->send($fd, "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",'warn'); }else{ $this->send($fd, "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, $data){ $this->send($fd, "215 unix type: l8"); } /** * 权限控制 * @param $fd * @param $data */ public function cmd_site($fd, $data){ if (substr($data, 0, 6) == "chmod "){ $user = $this->getuser($fd); $chmod = explode(" ", $data, 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]}",'info'); }else{ $this->send($fd, "550 could not change perms on " . $chmod[2] . ": permission denied"); } }else{ $this->send($fd, "550 you're unauthorized: 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, $data){ $user = $this->getuser($fd); $ftpsock = $this->getusersock($user); if (!$ftpsock){ $this->send($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, "501 sorry, 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, 'ssl', 'local_pk', $this->setting['ssl_key_file']); stream_context_set_option($context, 'ssl', 'allow_self_signed', true); stream_context_set_option($context, 'ssl', 'verify_peer', false); stream_context_set_option($context, 'ssl', 'verify_peer_name', false); stream_context_set_option($context, 'ssl', '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, $errno, $errstr, stream_server_bind | stream_server_listen); } if ($sock){ $addr = stream_socket_get_name($sock, false); list($ip, $port) = explode(':', $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,$data){ $this->send($fd, "200 ok"); } //================== //rfc2228 //================== public function cmd_pbsz($fd,$data){ $this->send($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,$data){ $this->send($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,$data){ $this->send($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, $data)) != false){ $this->send($fd, '213 '.date('ymdhis.u',filemtime($file))); }else{ $this->send($fd, '550 no file named "'.$data.'"'); } } /** * 获取文件大小 * @param $fd * @param $data */ public function cmd_size($fd,$data){ $user = $this->getuser($fd); if (($file = $this->getfile($user, $data)) != false){ $this->send($fd, '213 '.filesize($file)); }else{ $this->send($fd, '550 no file named "'.$data.'"'); } } /** * 获取文件列表 * @param unknown $fd * @param unknown $data */ public function cmd_mlsd($fd,$data){ $user = $this->getuser($fd); $ftpsock = $this->getusersock($user); if (!$ftpsock){ $this->send($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,'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,$pass,$home,$expired,$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":"","pass":"","home":"","expired":"","active":boolean,"group":"","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, $profile)){ $this->user->save(); $this->user->reload(); $this->send($fd, '200 group "'.$group.'" profile changed.'); }else{ $this->send($fd, '550 set profile fail!'); } }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, '200 '.json_encode($profile)); }else{ $this->send($fd, '550 get profile fail!'); } }else{ $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, '200 '.$size); }else{ $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服务器了。这个服务器的功能可以进行完全个性化定制。如果您有好的建议,也可以留言给我,谢谢。
上一篇: 2套4000左右六核独显吃鸡配置推荐 2019科学装机
下一篇: 聪明的农民