Skip to content

FTP (RFC 959) Compliance Issues in ftpd #215

@X1AOxiang

Description

@X1AOxiang

Hi, while reviewing ftpd's FTP implementation against RFC 959, we noticed several
places where the current behavior appears to differ from the specification. Each item below
cites the relevant RFC text alongside the corresponding source code for your reference.
We hope this is helpful for improving RFC conformance.


1. Control Connection Closed Immediately on QUIT During Active Transfer

RFC Reference: RFC 959 Section 4.1.1

"This command terminates a USER and if file transfer is not in progress, the server closes the control connection. If file transfer is in progress, the connection will remain open for result response and the server will then close it."

Analysis:
RFC 959 specifies that when QUIT is received while a file transfer is in progress, the control connection should remain open until the transfer completes and the final result response (e.g., 226 OK) is delivered. In the current implementation, FtpSession::QUIT() at lines 3001–3007 unconditionally sends 221 Disconnecting and immediately calls closeCommand(), with no check against m_state and no awareness of any ongoing transfer. Because the control connection socket is closed at line 3006 before the transfer thread has a chance to complete, the transfer-completion response at line 2234–2238 (sendResponse("226 OK\r\n")) can no longer be delivered over the already-closed socket. The RFC's requirement that the control connection remain open "for result response" until the transfer completes does not appear to be enforced in this code path.

Source Code Evidence (ftpSession.cpp):

// ftpSession.cpp:3001-3007
void FtpSession::QUIT (char const *args_)
{
	(void)args_;

	sendResponse ("221 Disconnecting\r\n");
	closeCommand ();
}

2. PASS Command Accepted Without a Prior USER Command

RFC Reference: RFC 959 Section 4.1.1

"This command must be immediately preceded by the user name command, and, for some sites, completes the user's identification for access control."

Analysis:
RFC 959 states that PASS must be immediately preceded by a USER command, and reply code 503 Bad sequence of commands is specified as the appropriate response when commands arrive out of sequence. In the current implementation, the session constructor at lines 406–409 sets m_authorizedUser = true when the server is configured with an empty username (m_config.user().empty() == true). When a PASS command subsequently arrives, the handler's guard condition at line 2801 — !user.empty() && !m_authorizedUser — evaluates to false because user is empty, and the check is silently skipped. As a result, password authentication proceeds at line 2807 without any prior USER command having been received during the current login sequence. Additionally, the PASS handler does not send a 503 response in any code path; it responds only with 430 variants, which differs from the reply code specified in RFC 959 for bad command sequencing.

Source Code Evidence (ftpSession.cpp):

// ftpSession.cpp:2802-2815
	{
		sendResponse ("430 User not authorized\r\n");
		return;
	}

	if (pass.empty () || pass == args_)
	{
		m_authorizedPass = true;
		sendResponse ("230 OK\r\n");
		return;
	}

	sendResponse ("430 Invalid password\r\n");
}

3. ABOR Reply Codes and Response Sequence Differ from RFC Specification

RFC Reference: RFC 959 Section 4.1.3

"In the first case, the server closes the data connection (if it is open) and responds with a 226 reply, indicating that the abort command was successfully processed. In the second case, the server aborts the FTP service in progress and closes the data connection, returning a 426 reply to indicate that the service request terminated abnormally. The server then sends a 226 reply, indicating that the abort command was successfully processed."

Analysis:
RFC 959 defines a precise two-case reply protocol for ABOR. When no transfer is in progress (case 1), the server should reply with 226. When a transfer is in progress (case 2), the server should first send 426 (transfer aborted abnormally) and then follow with 226 (abort successfully processed). The current implementation at lines 2374–2388 diverges from this specification in several respects. For case 1, the handler sends 225 No transfer to abort (line 2380) rather than the specified 226. For case 2, the handler sends 225 Aborted followed by 425 Transfer aborted (lines 2385–2386) rather than 426 followed by 226. Furthermore, the order of the two responses in case 2 is inverted relative to the RFC: the implementation sends what amounts to a success acknowledgement (225 Aborted) before the abnormal-termination notice (425 Transfer aborted), whereas the RFC specifies the abnormal-termination reply (426) must precede the success reply (226). Reply code 225 means "Data connection open; no transfer in progress" per RFC 959, and 425 means "Can't open data connection" — both carry semantics unrelated to the abort-completion acknowledgements that 226 and 426 are intended to convey.

Source Code Evidence (ftpSession.cpp):

// ftpSession.cpp:2374-2388
void FtpSession::ABOR (char const *args_)
{
	(void)args_;

	if (m_state == State::COMMAND)
	{
		sendResponse ("225 No transfer to abort\r\n");  // RFC expects 226
		return;
	}

	// abort the transfer
	sendResponse ("225 Aborted\r\n");           // RFC expects 426 first
	sendResponse ("425 Transfer aborted\r\n");  // RFC expects 226 second
	setState (State::COMMAND, true, true);
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions