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
Clientprotokolldatei? Serverprotokolldatei? –
Client-Protokolldatei wäre besser, wir sehen nicht, welche Befehle der Client gesendet hat und welche Antworten er erhalten hat. –
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. –