1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
//! Contains code pertaining to the setup options that can be given to the [`ServerBuilder`](crate::ServerBuilder)

use async_trait::async_trait;
use bitflags::bitflags;
use std::time::Duration;
use std::{
    fmt::Formatter,
    fmt::{self, Debug, Display},
    io,
    net::{IpAddr, Ipv4Addr},
    ops::Range,
};
use tokio::net::TcpSocket;

// Once we're sure about the types of these I think its good to expose it to the API user so that
// he/she can see what our server defaults are.
pub(crate) const DEFAULT_GREETING: &str = "Welcome to the libunftp FTP server";
pub(crate) const DEFAULT_IDLE_SESSION_TIMEOUT_SECS: u64 = 600;
pub(crate) const DEFAULT_PASSIVE_HOST: PassiveHost = PassiveHost::FromConnection;
pub(crate) const DEFAULT_PASSIVE_PORTS: Range<u16> = 49152..65535;
pub(crate) const DEFAULT_FTPS_REQUIRE: FtpsRequired = FtpsRequired::None;
pub(crate) const DEFAULT_FTPS_TRUST_STORE: &str = "./trusted.pem";

/// A helper trait to customize how the server binds to ports
#[async_trait]
pub trait Binder: Debug + Send {
    /// Create a [`tokio::net::TcpSocket`] and bind it to the given address, with a port in the
    /// given range.
    async fn bind(&mut self, local_addr: IpAddr, passive_ports: Range<u16>) -> io::Result<TcpSocket>;
}

/// The option to [ServerBuilder::passive_host](crate::ServerBuilder::passive_host). It allows the user to specify how the IP address
/// communicated in the _PASV_ response is determined.
#[derive(Debug, PartialEq, Clone, Default)]
pub enum PassiveHost {
    /// Use the IP address of the control connection
    #[default]
    FromConnection,
    /// Advertise this specific IP address
    Ip(Ipv4Addr),
    /// Resolve this DNS name into an IPv4 address.
    Dns(String),
    // We also be nice to have:
    // - PerUser(Box<dyn (Fn(Box<dyn UserDetail>) -> Ipv4Addr) + Send + Sync>) or something like
    //   that to allow a per user decision
}

impl Eq for PassiveHost {}

impl From<Ipv4Addr> for PassiveHost {
    fn from(ip: Ipv4Addr) -> Self {
        PassiveHost::Ip(ip)
    }
}

impl From<[u8; 4]> for PassiveHost {
    fn from(ip: [u8; 4]) -> Self {
        PassiveHost::Ip(ip.into())
    }
}

impl From<&str> for PassiveHost {
    fn from(dns_or_ip: &str) -> Self {
        match dns_or_ip.parse() {
            Ok(IpAddr::V4(ip)) => PassiveHost::Ip(ip),
            _ => PassiveHost::Dns(dns_or_ip.to_string()),
        }
    }
}

/// The option to [ServerBuilder::ftps_required](crate::ServerBuilder::ftps_required). It allows the user to specify whether clients are required
/// to upgrade a to secure TLS connection i.e. use FTPS.
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum FtpsRequired {
    /// All users, including anonymous must use FTPS
    All,
    /// All non-anonymous users requires FTPS.
    Accounts,
    /// FTPS not enforced.
    None, // would be nice to have a per-user setting also.
}

impl Eq for FtpsRequired {}

impl From<bool> for FtpsRequired {
    fn from(on: bool) -> Self {
        match on {
            true => FtpsRequired::All,
            false => FtpsRequired::None,
        }
    }
}

impl Display for FtpsRequired {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                FtpsRequired::All => "All users, including anonymous, requires FTPS",
                FtpsRequired::Accounts => "All non-anonymous users requires FTPS",
                FtpsRequired::None => "FTPS not enforced",
            }
        )
    }
}

bitflags! {
    /// Used to configure TLS options employed for FTPS
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct TlsFlags: u32 {
        /// Enables TLS version 1.2
        const V1_2               = 0b00000001;
        /// Enables TLS version 1.3
        const V1_3               = 0b00000010;
        /// Enables TLS session resumption via means of sever side session IDs.
        const RESUMPTION_SESS_ID = 0b00001000;
        /// Enables TLS session resumption via means tickets ([rfc5077](https://tools.ietf.org/html/rfc5077))
        const RESUMPTION_TICKETS = 0b00010000;
        /// Enables the latest safe TLS versions i.e. 1.2 and 1.3
        const LATEST_VERSIONS = Self::V1_2.bits() | Self::V1_3.bits();
    }
}

impl Default for TlsFlags {
    fn default() -> TlsFlags {
        // Switch TLS 1.3 off by default since we still see a PUT bug with lftp when switching
        // session resumption on along with TLS 1.3.
        TlsFlags::V1_2 | TlsFlags::RESUMPTION_SESS_ID | TlsFlags::RESUMPTION_TICKETS
    }
}

/// The option to [ServerBuilder::ftps_client_auth](crate::ServerBuilder::ftps_client_auth). Tells if and how mutual TLS (client certificate
/// authentication) should be handled.
#[derive(Debug, PartialEq, Clone, Copy, Default)]
pub enum FtpsClientAuth {
    /// Mutual TLS is switched off and the server won't ask the client for a certificate in the TLS
    /// protocol. This is the default.
    #[default]
    Off,
    /// Mutual TLS is on and whilst the server will request a certificate it will still proceed
    /// without one. If a certificate is sent by the client it will be validated against the
    /// configured trust anchors (see [ServerBuilder::ftps_trust_store](crate::ServerBuilder::ftps_trust_store)).
    Request,
    /// Mutual TLS is on, the server will request a certificate and it won't proceed without a
    /// client certificate that validates against the configured trust anchors (see
    /// [ServerBuilder::ftps_trust_store](crate::ServerBuilder::ftps_trust_store)).
    Require,
}

impl Eq for FtpsClientAuth {}

impl From<bool> for FtpsClientAuth {
    fn from(on: bool) -> Self {
        match on {
            true => FtpsClientAuth::Require,
            false => FtpsClientAuth::Off,
        }
    }
}

/// The options for [ServerBuilder::sitemd5](crate::ServerBuilder::sitemd5).
/// Allow MD5 either to be used by all, logged in users only or no one.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum SiteMd5 {
    /// Enabled for all users, including anonymous
    All,
    /// Enabled for all non-anonymous users.
    #[default]
    Accounts,
    /// Disabled
    None, // would be nice to have a per-user setting also.
}

/// Tells how graceful shutdown should happen. An instance of this struct should be returned from
/// the future passed to
/// [ServerBuilder::shutdown_indicator](crate::ServerBuilder::shutdown_indicator).
pub struct Shutdown {
    pub(crate) grace_period: Duration,
    //pub(crate) handle_new_connections: bool,
}

impl Shutdown {
    /// Creates a Shutdown instance with default values
    pub fn new() -> Self {
        Shutdown::default()
    }

    /// Defines how much time to allow for components to shut down before shutdown is forceful.
    pub fn grace_period(mut self, d: impl Into<Duration>) -> Self {
        self.grace_period = d.into();
        self
    }

    // /// Control channel connections will still be accepted for a while as connections
    // /// are drained. Clients connecting during this phase will receive an FTP error code.
    // pub fn handle_new_connections(mut self) -> Self {
    //     self.handle_new_connections = true;
    //     self
    // }
    //
    // /// Control channel connections will not be allowed during the shutdown phase.
    // pub fn block_new_connections(mut self) -> Self {
    //     self.handle_new_connections = false;
    //     self
    // }
}

impl Default for Shutdown {
    fn default() -> Shutdown {
        Shutdown {
            grace_period: Duration::from_secs(10),
            //handle_new_connections: false,
        }
    }
}

#[derive(Debug, Clone)]
/// Variants for failed logins protection policy
pub enum FailedLoginsBlock {
    /// User plus source IP address blocking
    UserAndIP,
    /// Block a source IP regardless of user
    IP,
    /// Block the user regardless of source IP
    User,
}

impl FailedLoginsPolicy {
    /// Create a new FailedLoginsPenalty instance
    pub fn new(max_attempts: u32, expires_after: Duration, block_by: FailedLoginsBlock) -> FailedLoginsPolicy {
        FailedLoginsPolicy {
            max_attempts,
            expires_after,
            block_by,
        }
    }
}

#[derive(Debug, Clone)]
/// Describes the exact penalty
pub struct FailedLoginsPolicy {
    /// The maximum number of consecutive failed login attempts before the account gets locked
    pub(crate) max_attempts: u32,
    /// The expiration time since the last failed login attempt that the account gets unlocked
    pub(crate) expires_after: Duration,
    // The variant in which this is blocked
    pub(crate) block_by: FailedLoginsBlock,
}

impl Default for FailedLoginsPolicy {
    fn default() -> FailedLoginsPolicy {
        FailedLoginsPolicy::new(3, Duration::from_secs(120), FailedLoginsBlock::UserAndIP)
    }
}

/// The options for
/// [ServerBuilder::active_passive_mode](crate::ServerBuilder::active_passive_mode).  This allows
/// to switch active / passive mode on or off.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum ActivePassiveMode {
    /// Only passive mode is enabled
    #[default]
    PassiveOnly,
    /// Only Active mode is enabled
    ActiveOnly,
    /// Both is enabled
    ActiveAndPassive,
}