2016-05-20 3 views
1

Ich schreibe von Grund auf einen FTP-Server hauptsächlich um zu verstehen, wie Client/Socket-FTP-Kommunikation funktioniert und um einige angepasste Funktionen zu entwickeln.Benutzerdefinierter PHP-FTP-Server: Der Client wird nach dem Senden des LIST-Befehls getrennt

Ich habe Zweifel, wie Server behandelt den PASV Befehl vom Client erhalten, als wenn ich versuche, einen neuen Port zu instanziieren, der Client trennt.

Dies ist der vollständige PHP-Code, an das ich arbeite:

<? 
//-- Server runs on port :2121 and (at the moment) accept any user with any password 
$server = new Ftpd(2121); 
class ftpd { 
    private $clients = array();     //Array of connected clients 
    private $server = "";      //Server connection handler 
    private $listen_address = "";    //Listen Address 
    private $listen_port = 0;     //Listen Port 
    private $min_pasv_port = 15000;    //Port range for PASSIVE connection 
    private $max_pasv_port = 16000; 
    private $eol = "\n";      //EndOfLine 
/* Show log on stdout */ 
    private function log($msg) { 
     $output = date("d-M-Y H:i:s") . " - " . $msg; 
     echo $output . "\n"; 
    } 
/* Display socket error and abort */ 
    function socket_error($command = "") { 
     $this->errorcode = socket_last_error($this->server); 
     $this->errormessage = socket_strerror($this->errorcode); 
     $this->log("[ ERROR ] on command " . $command . "() : " . $this->errorcode . " - " . $this->errormessage); 
     die(); 
    } 
/* Get list of connections currently alive */ 
    private function socketlist() { 
     $socketlist = array(
      'server' => $this->server 
     ); 
     reset($this->clients); 
     while (list($k,$c) = each($this->clients)) { 
      $socketlist[$k] = $c['conn']; 
     } 
     return($socketlist); 
    } 
/* Add new client */ 
    private function add_client($conn) { 
     $clientID = uniqid("client_"); 
     socket_getpeername($conn, $ip, $port); 
     $this->clients[$clientID] = array(
      'conn'  => $conn, 
      'ip'  => $ip, 
      'hostname' => gethostbyaddr($ip), 
      'port'  => $port, 
      'id'  => $clientID, 
      'user'  => '', 
      'password' => '' 
     ); 
     return($this->clients[$clientID]); 
    } 
/* Get connected client list */ 
    private function get_client($clientID) { 
     reset($this->clients); 
     while (list($id,$c) = each($this->clients)) { 
      if ($c['conn'] == $clientID) return($c); 
     } 
     return(false); 
    } 
/* Remove a connection with a client */ 
    private function remove_client($clientID) { 
     reset($this->clients); 
     while (list($k,$c) = each($this->clients)) { 
      if ($c['conn'] == $clientID) unset($this->clients[$k]); 
     } 
     return(true); 
    } 
/* Constructor */ 
    function ftpd($listen_port = 21) { 
     $listen_address = gethostbyname($_SERVER['HOSTNAME']); 
     /* Open socket */ 
     if (! ($server = @socket_create(AF_INET, SOCK_STREAM, 0)))   $this->socket_error('socket_create'); 
     else                $this->log("[ DONE ] socket_create"); 
     /* reuse listening socket address */ 
     if (! @socket_setopt($server, SOL_SOCKET, SO_REUSEADDR, 1))   $this->socket_error('socket_setopt'); 
     else                $this->log("[ DONE ] socket_setopt"); 
     /* set socket to non-blocking */ 
     if (! @socket_set_nonblock($server))        $this->socket_error('socket_set_nonblock'); 
     else                $this->log("[ DONE ] socket_set_nonblock"); 
     /* bind socket with address and port */ 
     if (! @socket_bind($server, $listen_address, $listen_port))   $this->socket_error('socket_bind'); 
     else                $this->log("[ DONE ] socket_bind on " . $listen_address . ":" . $listen_port); 
     /* start listening */ 
     if (! @socket_listen($server))          $this->socket_error('socket_listen'); 
     else                $this->log("[ DONE ] socket_listen"); 
     $this->server   = $server; 
     $this->listen_address = $listen_address; 
     $this->listen_port  = $listen_port; 
     /* Loop waiting connections */ 
     while (true) { 
      $this->log("[ WAIT ] Accept incoming connections (" . count($this->clients) . " clients currently connected)"); 
      $write = NULL; 
      $exeption = NULL; 
      /* Build list of active sockets */ 
      $slist = $this->socketlist(); 
      if (socket_select($slist, $write, $exeption, 1, 0) > 0) { 
       foreach($slist as $sock) { 
        if ($sock == $this->server) { 
         /* accept a connection on server */ 
         $this->log("New connection"); 
         if (! ($conn = socket_accept($this->server))) { 
          $this->socket_error('socket_accept'); 
         } else { 
          $lastclient = $this->add_client($conn); 
          $this->log("Client " . $lastclient['hostname'] . " (" . $lastclient['ip'] . ":" . $lastclient['port'] . ") connected"); 
          $this->write($lastclient['conn'], 220, "Welcome!"); 
         } 
        } else { 
         $this->log("ANOTHER MESSAGE"); 
         $this->read($sock); 
        } 
       } 
      } 
     } 
    } 
/* write data to socket connection */ 
    function write($clientID, $id, $message) { 
     $connected_client = $this->get_client($clientID); 
     $this->log("[ WRITE to " . $connected_client['hostname'] . " ] Message: " . $id . " " . $message); 
     if (! (socket_write($clientID, $id . " " . $message . "\r\n"))) $this->socket_error('socket_write'); 
    } 
/* receive data from socket connection */ 
    function read($clientID) { 
     $connected_client = $this->get_client($clientID); 
     $keyclient = $connected_client['id']; 
     $this->log("[ READ from " . $connected_client['hostname'] . " ] Ready"); 
     //$this->log("Client " . $connected_client['hostname'] . " (" . $connected_client['ip'] . ":" . $connected_client['port'] . ") ready to write"); 
     if (($msg = @socket_read($clientID, 1024)) === false || $msg == '') { 
      if ($msg != '') $this->socket_error('socket_read'); 
      $this->log("[ READ from " . $connected_client['hostname'] . " ] **** Message: " . $msg); 
      $this->remove_client($clientID); 
      $this->log("[ DISCONNECT ] " . $clientID); 
     } else { 
      $msg = trim($msg); 
      $this->log("[ READ from " . $connected_client['hostname'] . " ] Message: " . $msg); 
      list($cmd, $cmd_option) = explode(" ", $msg, 2); 
      if ($cmd == "USER") { //-- USER command received 
       //-- any user are allowed to login with any password 
       $this->clients[$keyclient]['user'] = $cmd_option; 
       $this->Write($clientID, 331, "Password required for " . $cmd_option); 
      } elseif ($cmd == "PASS") { //-- PASS command received 
       //-- any user are allowed to login with any password 
       $this->clients[$keyclient]['password'] = $cmd_option; 
       $this->Write($clientID, 230, "Welcome!"); 
      } elseif ($cmd == "PWD") { //-- PWD command received 
       $this->Write($clientID, 257, "/ is the current directory"); 
      } elseif ($cmd == "TYPE") { //-- TYPE command received 
       $this->eol = ($cmd_option == "A" ? "\r\n" : "\n"); 
       $this->Write($clientID, 200, "TYPE set to " . $cmd_option); 
      } elseif ($cmd == "SYST") { //-- SYST command received 
       $this->Write($clientID, 215, "UNIX Type: L8"); 
      } elseif ($cmd == "AUTH") { //-- AUTH command to be implemented 
       $this->Write($clientID, 500, $msg . " handled but not understood"); 
      } elseif ($cmd == "PASV") { //-- PASV command to be implemented 
       while (true) {    /* loop until a free port can be used */ 
        $port = rand($this->min_pasv_port, $this->max_pasv_port); 
        if (! ($conn = @socket_create(AF_INET, SOCK_STREAM, 0))) $this->socket_error('PASV.socket_create'); 
        else              $this->log("[ DONE ] PASV.socket_create"); 
        /* reuse listening socket address */ 
        if (! @socket_setopt($conn, SOL_SOCKET, SO_REUSEADDR, 1)) $this->socket_error('PASV.socket_setopt'); 
        else              $this->log("[ DONE ] PASV.socket_setopt"); 
        /* set socket to non-blocking */ 
        if (! @socket_set_nonblock($conn))       $this->socket_error('PASV.socket_set_nonblock'); 
        else              $this->log("[ DONE ] PASV.socket_set_nonblock"); 
        /* bind socket with address and port */ 
        if (! @socket_bind($conn, $this->listen_address, $port)) $this->socket_error('PASV.socket_bind'); 
        else              $this->log("[ DONE ] PASV.socket_bind on " . $this->listen_address . ":" . $port); 
        /* start listening */ 
        if (! @socket_listen($conn))        $this->socket_error('PASV.socket_listen'); 
        else              $this->log("[ DONE ] PASV.socket_listen"); 
        $this->clients[$keyclient]['conn'] = $conn; 
        $this->clients[$keyclient]['port'] = $port; 
        $p1 = $port >> 8; 
        $p2 = $port & 0xff; 
        $tmp = str_replace(".", ",", $this->listen_address); 
        $this->Write($clientID, 227, "Entering Passive Mode (" . $tmp . "," . $p1 . "," . $p2 . ")."); 
        print_r($this->clients); 
        break; 
       } 
      } elseif ($cmd == "LIST") { //-- LIST command to be developped 
       exec("ls /ews/tmp", $output); 
       $this->Write($clientID, "", implode("\n", $output)); 
       $this->Write($clientID, 226, "Transfer complete"); 
      } else { 
       $this->Write($clientID, 500, $msg . " unhandled"); 
      } 
     } 
    } 
} 
?> 

Dies ist die Log-Server als Daemon

[/ews/tmp]# ./ftp.server 
20-May-2016 11:45:51 - [ DONE ] socket_create 
20-May-2016 11:45:51 - [ DONE ] socket_setopt 
20-May-2016 11:45:51 - [ DONE ] socket_set_nonblock 
20-May-2016 11:45:51 - [ DONE ] socket_bind on 164.130.21.98:2121 
20-May-2016 11:45:51 - [ DONE ] socket_listen 
20-May-2016 11:45:51 - [ WAIT ] Accept incoming connections (0 clients currently connected) 
20-May-2016 11:45:52 - [ WAIT ] Accept incoming connections (0 clients currently connected) 
//--message repeated till when client connects 
20-May-2016 11:46:06 - [ WAIT ] Accept incoming connections (0 clients currently connected) 
20-May-2016 11:46:06 - New connection 
20-May-2016 11:46:06 - Client ewsserver (164.130.21.98:45071) connected 
20-May-2016 11:46:06 - [ WRITE to ewsserver ] Message: 220 Welcome! 
20-May-2016 11:46:06 - [ WAIT ] Accept incoming connections (1 clients currently connected) 
20-May-2016 11:46:07 - [ WAIT ] Accept incoming connections (1 clients currently connected) 
20-May-2016 11:46:07 - ANOTHER MESSAGE 
20-May-2016 11:46:07 - [ READ from ewsserver ] Ready 
20-May-2016 11:46:07 - [ READ from ewsserver ] Message: USER dummy 
20-May-2016 11:46:07 - [ WRITE to ewsserver ] Message: 331 Password required for dummy 
20-May-2016 11:46:07 - [ WAIT ] Accept incoming connections (1 clients currently connected) 
20-May-2016 11:46:08 - ANOTHER MESSAGE 
20-May-2016 11:46:08 - [ READ from ewsserver ] Ready 
20-May-2016 11:46:08 - [ READ from ewsserver ] Message: PASS dummy 
20-May-2016 11:46:08 - [ WRITE to ewsserver ] Message: 230 Welcome! 
20-May-2016 11:46:08 - [ WAIT ] Accept incoming connections (1 clients currently connected) 
20-May-2016 11:46:08 - ANOTHER MESSAGE 
20-May-2016 11:46:08 - [ READ from ewsserver ] Ready 
20-May-2016 11:46:08 - [ READ from ewsserver ] Message: SYST 
20-May-2016 11:46:08 - [ WRITE to ewsserver ] Message: 215 UNIX Type: L8 
20-May-2016 11:46:08 - [ WAIT ] Accept incoming connections (1 clients currently connected) 
20-May-2016 11:46:09 - [ WAIT ] Accept incoming connections (1 clients currently connected) 
//-- client type the "dir" command and PASV command is received 
20-May-2016 11:46:13 - ANOTHER MESSAGE 
20-May-2016 11:46:13 - [ READ from ewsserver ] Ready 
20-May-2016 11:46:13 - [ READ from ewsserver ] Message: PASV 
20-May-2016 11:46:13 - [ DONE ] PASV.socket_create 
20-May-2016 11:46:13 - [ DONE ] PASV.socket_setopt 
20-May-2016 11:46:13 - [ DONE ] PASV.socket_set_nonblock 
20-May-2016 11:46:13 - [ DONE ] PASV.socket_bind on 164.130.21.98:15469 
20-May-2016 11:46:13 - [ DONE ] PASV.socket_listen 
20-May-2016 11:46:13 - [ WRITE to ] Message: 227 Entering Passive Mode (164,130,21,98,60,109). 
Array 
(
    [client_573edcde66f87] => Array 
     (
      [conn] => Resource id #7 
      [ip] => 164.130.21.98 
      [hostname] => ewsserver 
      [port] => 15469 
      [id] => client_573edcde66f87 
      [user] => vega 
      [password] => vega 
     ) 

) 
20-May-2016 11:46:13 - [ WAIT ] Accept incoming connections (1 clients currently connected) 
20-May-2016 11:46:13 - ANOTHER MESSAGE 
20-May-2016 11:46:13 - [ READ from ewsserver ] Ready 
20-May-2016 11:46:13 - [ READ from ewsserver ] **** Message: 
//-- Server disconnect 
20-May-2016 11:46:13 - [ DISCONNECT ] Resource id #7 
20-May-2016 11:46:13 - [ WAIT ] Accept incoming connections (0 clients currently connected) 
20-May-2016 11:46:14 - [ WAIT ] Accept incoming connections (0 clients currently connected) 
20-May-2016 11:46:15 - [ WAIT ] Accept incoming connections (0 clients currently connected) 
20-May-2016 11:46:16 - [ WAIT ] Accept incoming connections (0 clients currently connected) 
20-May-2016 11:46:17 - [ WAIT ] Accept incoming connections (0 clients currently connected) 

beginnt, während dies die Eingabeaufforderung Client-Seite :

Status: Resolving address of host.name.st.com 
Status: Connecting to xxx.xxx.21.98:2121... 
Status: Connection established, waiting for welcome message... 
Response: 220 Welcome! 
Command: AUTH TLS 
Response: 500 AUTH TLS handled but not understood 
Command: AUTH SSL 
Response: 500 AUTH SSL handled but not understood 
Status: Insecure server, it does not support FTP over TLS. 
Command: USER dummy 
Response: 331 Password required for dummy 
Command: PASS ***** 
Response: 230 Welcome! 
Command: SYST 
Response: 215 UNIX Type: L8 
Command: FEAT 
Response: 500 FEAT unhandled 
Status: Server does not support non-ASCII characters. 
Status: Logged in 
Status: Retrieving directory listing... 
Command: PWD 
Response: 257/is the current directory 
Command: TYPE I 
Response: 200 TYPE set to I 
Command: PASV 
Response: 227 Entering Passive Mode (xxx,xxx,21,98,60,172). 
Command: LIST 
Error: Disconnected from server: ECONNABORTED - Connection aborted 
Error: Failed to retrieve directory listing 
Status: Disconnected from server 
Status: Resolving address of host.name.st.com 
Status: Connecting to xxx.xxx.21.98:2121... 
Status: Connection established, waiting for welcome message... 
Response: 220 Welcome! 
Command: AUTH TLS 
Response: 500 AUTH TLS handled but not understood 
Command: AUTH SSL 
Response: 500 AUTH SSL handled but not understood 
Status: Insecure server, it does not support FTP over TLS. 
Command: USER dummy 
Response: 331 Password required for dummy 
Command: PASS ***** 
Response: 230 Welcome! 
Status: Server does not support non-ASCII characters. 
Status: Logged in 
Status: Retrieving directory listing... 
Command: PWD 
Response: 257/is the current directory 
Command: TYPE I 
Response: 200 TYPE set to I 
Command: PASV 
Response: 227 Entering Passive Mode (xxx,xxx,21,98,60,251). 
Command: LIST 
+1

Clientprotokolldatei? Serverprotokolldatei? –

+1

Client-Protokolldatei wäre besser, wir sehen nicht, welche Befehle der Client gesendet hat und welche Antworten er erhalten hat. –

+0

Das unmittelbare Problem ist, dass Sie versuchen, von der akzeptierten Datenverbindung zu lesen. Aber es ist der Client, der die Verzeichnisliste "herunterlädt". Nachdem Sie das Lesen des Zeitlimits beendet haben (weil der Client zu Recht nichts sendet), brechen Sie die Verbindung ab. Siehe meine aktualisierte Antwort. –

Antwort

1

Ich sehe diese Probleme im Code:

  • Das unmittelbare Problem ist, dass Sie versuchen, von der akzeptierten Datenverbindung zu lesen. Aber es ist der Client, der die Verzeichnisliste "herunterlädt". Nachdem Sie das Lesen des Zeitlimits beendet haben (weil der Client zu Recht nichts sendet), brechen Sie die Verbindung ab.
  • Sie bestätigen nicht die Annahme der Datenverbindung mit 150 Opening data channel for directory -ähnliche Antwort.
  • Sie schreiben die Auflistung auf die Steuerverbindung, nicht auf die Datenverbindung.
  • Sie beenden die Zeilen in der Liste mit LF 's, während die FTP - Spezifikation CRLF' s vorschreibt. Siehe "bare linefeeds received in ASCII mode" warning when listing directory on my FTP server
+0

Das stimmt! Die PASV-Verbindung funktioniert ordnungsgemäß, aber es fehlte der '150 Eröffnende Datenkanal für das Verzeichnis'. Einmal instanziiert versuche ich, die Liste der Dateien zu senden, aber Client hat es nicht erhalten. Wo finde ich die Spezifikation der LIST-Befehlsausgabe, die serverseitig implementiert ist? –

+1

Es gibt keine Spezifikation. Moderne FTP-Server und -Clients sollten stattdessen 'MLSD' verwenden, für die es eine Spezifikation gibt [RFC 3659] (https://tools.ietf.org/html/rfc3659). –

Verwandte Themen