use std::io::{BufRead, BufReader, Write};
use std::net::TcpStream;
use std::ops::Deref;
use std::sync::{Arc, Mutex, MutexGuard};
use super::data_stream::DataStream;
use super::tls::TlsStream;
use crate::Status;
use crate::command::Command;
use crate::types::{FtpError, FtpResult, Response};
pub(super) const TRANSFER_COMPLETE: &[Status] =
&[Status::ClosingDataConnection, Status::RequestedFileActionOk];
pub(super) type SharedControl<T> = Arc<Mutex<ControlChannel<T>>>;
#[derive(Debug)]
pub(super) struct ControlChannel<T>
where
T: TlsStream,
{
pub(super) reader: BufReader<DataStream<T>>,
pub(super) data_connection_open: bool,
}
pub(super) fn lock<T>(control: &Mutex<ControlChannel<T>>) -> MutexGuard<'_, ControlChannel<T>>
where
T: TlsStream,
{
control
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[cfg(feature = "secure")]
pub(super) fn into_exclusive<T>(control: SharedControl<T>) -> FtpResult<ControlChannel<T>>
where
T: TlsStream,
{
Arc::try_unwrap(control)
.map(|mutex| {
mutex
.into_inner()
.unwrap_or_else(|poisoned| poisoned.into_inner())
})
.map_err(|_| FtpError::DataConnectionAlreadyOpen)
}
impl<T> ControlChannel<T>
where
T: TlsStream,
{
pub(super) fn new(stream: DataStream<T>) -> Self {
Self {
reader: BufReader::new(stream),
data_connection_open: false,
}
}
pub(super) fn shared(stream: DataStream<T>) -> SharedControl<T> {
Arc::new(Mutex::new(Self::new(stream)))
}
pub(super) fn socket(&self) -> &TcpStream {
self.reader.get_ref().get_ref()
}
pub(super) fn perform(&mut self, command: Command) -> FtpResult<()> {
let command = command.to_string();
crate::command::validate_command_line(&command)?;
trace!("CC OUT: {}", command.trim_end_matches("\r\n"));
self.reader
.get_mut()
.write_all(command.as_bytes())
.map_err(FtpError::ConnectionError)
}
pub(super) fn read_response(&mut self, expected_code: Status) -> FtpResult<Response> {
self.read_response_in(&[expected_code])
}
pub(super) fn read_response_in(&mut self, expected_code: &[Status]) -> FtpResult<Response> {
let mut line = Vec::new();
let mut body: Vec<u8> = Vec::new();
self.read_line(&mut line)?;
body.extend(line.iter());
trace!("CC IN: {:?}", line);
if line.len() < 5 {
return Err(FtpError::BadResponse);
}
let code_word: u32 = code_from_buffer(&line, 3)?;
let mut code = Status::from(code_word);
trace!("Code parsed from response: {} ({})", code, code_word);
let expected = [line[0], line[1], line[2], 0x20];
let feat_opener = [line[0], line[1], line[2], b'-'];
let is_terminal = |reply: &[u8]| {
reply.len() >= 4
&& reply[0].is_ascii_digit()
&& reply[1].is_ascii_digit()
&& reply[2].is_ascii_digit()
&& (reply[3] == b' '
|| (expected_code.contains(&Status::System) && reply[0..4] == feat_opener))
};
trace!("CC IN: {:?}", line);
while !is_terminal(&line) {
line.clear();
let bytes_read = self.read_line(&mut line)?;
if bytes_read == 0 {
return Err(FtpError::ConnectionError(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"connection closed during multiline response",
)));
}
body.extend(line.iter());
trace!("CC IN: {:?}", line);
}
if line[0..4] != expected {
code = Status::from(code_from_buffer(&line, 3)?);
trace!("Code updated from terminal response: {}", code);
}
let response: Response = Response::new(code, body);
if expected_code.contains(&code) {
Ok(response)
} else {
Err(FtpError::UnexpectedResponse(response))
}
}
pub(super) fn read_line(&mut self, line: &mut Vec<u8>) -> FtpResult<usize> {
self.reader
.read_until(0x0A, line.as_mut())
.map_err(FtpError::ConnectionError)?;
Ok(line.len())
}
pub(super) fn guard_multiple_data_connections(&self) -> FtpResult<()> {
if self.data_connection_open {
Err(FtpError::DataConnectionAlreadyOpen)
} else {
Ok(())
}
}
pub(super) fn complete_transfer(&mut self) -> FtpResult<()> {
self.data_connection_open = false;
trace!("data connection closed; reading transfer reply");
self.read_response_in(TRANSFER_COMPLETE).map(|_| ())
}
}
fn code_from_buffer(buf: &[u8], len: usize) -> FtpResult<u32> {
if buf.len() < len {
return Err(FtpError::BadResponse);
}
let buffer = buf[0..len].to_vec();
let as_string = String::from_utf8(buffer).map_err(|_| FtpError::BadResponse)?;
as_string.parse::<u32>().map_err(|_| FtpError::BadResponse)
}
#[derive(Debug)]
pub struct ControlSocket<'a, T>
where
T: TlsStream,
{
guard: MutexGuard<'a, ControlChannel<T>>,
}
impl<'a, T> ControlSocket<'a, T>
where
T: TlsStream,
{
pub(super) fn lock(control: &'a Mutex<ControlChannel<T>>) -> Self {
Self {
guard: lock(control),
}
}
}
impl<T> Deref for ControlSocket<'_, T>
where
T: TlsStream,
{
type Target = TcpStream;
fn deref(&self) -> &Self::Target {
self.guard.socket()
}
}
#[cfg(all(test, feature = "secure"))]
mod tls_transition_tests {
use std::io::{BufRead, BufReader, Read, Write};
use std::net::TcpListener;
use std::sync::Arc;
use std::thread;
use std::time::Duration;
use super::super::tls::{NoTlsStream, TlsConnector};
use crate::{FtpError, FtpResult, FtpStream};
#[derive(Debug)]
struct UnusedConnector;
impl TlsConnector for UnusedConnector {
type Stream = NoTlsStream;
fn connect(&self, _: &str, _: std::net::TcpStream) -> FtpResult<Self::Stream> {
panic!("TLS must not start while a transfer owns the control connection")
}
}
#[test]
fn should_reject_tls_changes_before_sending_commands() {
for secure in [false, true] {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let address = listener.local_addr().unwrap();
let server = thread::spawn(move || {
let (socket, _) = listener.accept().unwrap();
socket
.set_read_timeout(Some(Duration::from_secs(5)))
.unwrap();
let mut reader = BufReader::new(socket);
reader.get_mut().write_all(b"220 ready\r\n").unwrap();
let mut command = String::new();
reader.read_line(&mut command).unwrap();
if !command.is_empty() {
let reply: &[u8] = if secure {
b"234 start TLS\r\n"
} else {
b"200 cleared\r\n"
};
reader.get_mut().write_all(reply).unwrap();
reader.read_to_end(&mut Vec::new()).unwrap();
}
command
});
let ftp = FtpStream::connect(address).unwrap();
let transfer_control = Arc::clone(&ftp.control);
let result = if secure {
ftp.into_secure(UnusedConnector, "localhost")
} else {
ftp.clear_command_channel()
};
assert!(matches!(result, Err(FtpError::DataConnectionAlreadyOpen)));
drop(transfer_control);
assert_eq!(server.join().unwrap(), "");
}
}
}