libunftp/server/controlchan/
ftps.rs1use 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
12pub 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 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
88pub 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}