Skip to main content

libunftp/server/controlchan/
ftps.rs

1use async_trait::async_trait;
2
3use crate::{
4    auth::UserDetail,
5    server::{
6        Command, ControlChanErrorKind, Event, Reply, ReplyCode, controlchan::error::ControlChanError, controlchan::middleware::ControlChanMiddleware,
7        ftpserver::options::FtpsRequired, session::SharedSession,
8    },
9    storage::{Metadata, StorageBackend},
10};
11
12// Middleware that enforces FTPS on the control channel according to the specified setting/requirement.
13pub struct FtpsControlChanEnforcerMiddleware<Storage, User, Next>
14where
15    User: UserDetail + 'static,
16    Storage: StorageBackend<User> + 'static,
17    Storage::Metadata: Metadata,
18    Next: ControlChanMiddleware,
19{
20    pub session: SharedSession<Storage, User>,
21    pub ftps_requirement: FtpsRequired,
22    pub next: Next,
23}
24
25#[async_trait]
26impl<Storage, User, Next> ControlChanMiddleware for FtpsControlChanEnforcerMiddleware<Storage, User, Next>
27where
28    User: UserDetail + 'static,
29    Storage: StorageBackend<User> + 'static,
30    Storage::Metadata: Metadata,
31    Next: ControlChanMiddleware,
32{
33    async fn handle(&mut self, event: Event) -> Result<Reply, ControlChanError> {
34        match (self.ftps_requirement, event) {
35            (FtpsRequired::None, event) => self.next.handle(event).await,
36            (FtpsRequired::All, event) => match event {
37                Event::Command(Command::Ccc) => Ok(Reply::new(ReplyCode::FtpsRequired, "Cannot downgrade connection, TLS enforced.")),
38                Event::Command(Command::User { .. }) | Event::Command(Command::Pass { .. }) => {
39                    let is_tls = async {
40                        let session = self.session.lock().await;
41                        session.cmd_tls
42                    }
43                    .await;
44                    match is_tls {
45                        true => self.next.handle(event).await,
46                        false => Ok(Reply::new(ReplyCode::FtpsRequired, "A TLS connection is required on the control channel")),
47                    }
48                }
49                _ => self.next.handle(event).await,
50            },
51            (FtpsRequired::Accounts, event) => {
52                let (is_tls, username) = async {
53                    let session = self.session.lock().await;
54                    (session.cmd_tls, session.username.clone())
55                }
56                .await;
57                match (is_tls, event) {
58                    (true, event) => self.next.handle(event).await,
59                    (false, Event::Command(Command::User { username })) => {
60                        if is_anonymous_user(&username[..])? {
61                            self.next.handle(Event::Command(Command::User { username })).await
62                        } else {
63                            Ok(Reply::new(ReplyCode::FtpsRequired, "A TLS connection is required on the control channel"))
64                        }
65                    }
66                    (false, Event::Command(Command::Pass { password })) => {
67                        match username {
68                            None => {
69                                // Should not happen, username should have already been provided.
70                                Err(ControlChanError::new(ControlChanErrorKind::IllegalState))
71                            }
72                            Some(username) => {
73                                if is_anonymous_user(username)? {
74                                    self.next.handle(Event::Command(Command::Pass { password })).await
75                                } else {
76                                    Ok(Reply::new(ReplyCode::FtpsRequired, "A TLS connection is required on the control channel"))
77                                }
78                            }
79                        }
80                    }
81                    (false, event) => self.next.handle(event).await,
82                }
83            }
84        }
85    }
86}
87
88// Middleware that enforces FTPS on the data channel according to the specified setting/requirement.
89pub struct FtpsDataChanEnforcerMiddleware<Storage, User, Next>
90where
91    User: UserDetail + 'static,
92    Storage: StorageBackend<User> + 'static,
93    Storage::Metadata: Metadata,
94    Next: ControlChanMiddleware,
95{
96    pub session: SharedSession<Storage, User>,
97    pub ftps_requirement: FtpsRequired,
98    pub next: Next,
99}
100
101#[async_trait]
102impl<Storage, User, Next> ControlChanMiddleware for FtpsDataChanEnforcerMiddleware<Storage, User, Next>
103where
104    User: UserDetail + 'static,
105    Storage: StorageBackend<User> + 'static,
106    Storage::Metadata: Metadata,
107    Next: ControlChanMiddleware,
108{
109    async fn handle(&mut self, event: Event) -> Result<Reply, ControlChanError> {
110        match (self.ftps_requirement, event) {
111            (FtpsRequired::None, event) => self.next.handle(event).await,
112            (FtpsRequired::All, event) => match event {
113                Event::Command(Command::Pasv) => {
114                    let is_tls = async {
115                        let session = self.session.lock().await;
116                        session.data_tls
117                    }
118                    .await;
119                    match is_tls {
120                        true => self.next.handle(event).await,
121                        false => Ok(Reply::new(ReplyCode::FtpsRequired, "A TLS connection is required on the data channel")),
122                    }
123                }
124                _ => self.next.handle(event).await,
125            },
126            (FtpsRequired::Accounts, event) => match event {
127                Event::Command(Command::Pasv) => {
128                    let (is_tls, username_opt) = async {
129                        let session = self.session.lock().await;
130                        (session.cmd_tls, session.username.clone())
131                    }
132                    .await;
133
134                    let username: String = username_opt.ok_or_else(|| ControlChanError::new(ControlChanErrorKind::IllegalState))?;
135                    let is_anonymous = is_anonymous_user(username)?;
136                    match (is_tls, is_anonymous) {
137                        (true, _) | (false, true) => self.next.handle(event).await,
138                        _ => Ok(Reply::new(ReplyCode::FtpsRequired, "A TLS connection is required on the data channel")),
139                    }
140                }
141                _ => self.next.handle(event).await,
142            },
143        }
144    }
145}
146
147fn is_anonymous_user(username: impl AsRef<[u8]>) -> Result<bool, std::str::Utf8Error> {
148    let username_str = std::str::from_utf8(username.as_ref())?;
149    Ok(username_str == "anonymous")
150}