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,
}