{ lib, pkgs, config, ... }:
let
package = pkgs.pure-ftpd.override { ldapFtpId = "immaeFtp"; };
pure-ftpd-enabled = config.myServices.ftp.pure-ftpd.enable;
proftpd-enabled = config.myServices.ftp.proftpd.enable;
in
{
options = {
myServices.ftp.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable ftp.
'';
};
myServices.ftp.pure-ftpd.enable = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to enable pure-ftpd.
'';
};
myServices.ftp.proftpd.enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = ''
Whether to enable proftpd.
'';
};
};
config = lib.mkIf config.myServices.ftp.enable {
services.borgBackup.profiles.global.ignoredPaths = [
"ftp/test_ftp"
"proftpd/authorized_keys"
];
myServices.dns.zones."immae.eu".subdomains.ftp =
with config.myServices.dns.helpers; ips servers.eldiron.ips.main;
myServices.chatonsProperties.services.espace-de-stockage = {
file.datetime = "2022-08-22T01:00:00";
service = {
name = "Espace de stockage";
description = "Compte FTP/SFTP";
logo = if pure-ftpd-enabled
then "https://www.pureftpd.org/project/pure-ftpd/images/favicon.png"
else if proftpd-enabled
then "http://proftpd.org/proftpd.png"
else "";
website = "ftp.immae.eu";
status.level = "OK";
status.description = "OK";
registration."" = ["MEMBER" "CLIENT"];
registration.load = "OPEN";
install.type = "PACKAGE";
};
software = if pure-ftpd-enabled then {
name = "Pure-ftpd";
website = "https://www.pureftpd.org/project/pure-ftpd/";
license.url = "https://github.com/jedisct1/pure-ftpd/blob/master/COPYING";
license.name = "MIT Licence";
version = package.version;
source.url = "https://github.com/jedisct1/pure-ftpd/";
modules = "openssh";
} else if proftpd-enabled then {
name = "ProFTPD";
website = "http://proftpd.org/";
license.url = "https://github.com/proftpd/proftpd/blob/master/COPYING";
license.name = "GNU General Public License v2.0";
version = pkgs.proftpd.version;
source.url = "https://github.com/proftpd/proftpd/";
modules = "openssh";
} else {};
};
#myServices.chatonsProperties.services.ftp = {
# file.datetime = "2022-08-22T01:00:00";
# service = {
# name = "Comptes FTP";
# description = "Compte FTP/SFTP";
# logo = if pure-ftpd-enabled
# then "https://www.pureftpd.org/project/pure-ftpd/images/favicon.png"
# else if proftpd-enabled
# then "http://proftpd.org/proftpd.png"
# else "";
# website = "ftp.immae.eu";
# status.level = "OK";
# status.description = "OK";
# registration."" = ["MEMBER" "CLIENT"];
# registration.load = "OPEN";
# install.type = "PACKAGE";
# };
# software = if pure-ftpd-enabled then {
# name = "Pure-ftpd";
# website = "https://www.pureftpd.org/project/pure-ftpd/";
# license.url = "https://github.com/jedisct1/pure-ftpd/blob/master/COPYING";
# license.name = "MIT Licence";
# version = package.version;
# source.url = "https://github.com/jedisct1/pure-ftpd/";
# } else if proftpd-enabled then {
# name = "ProFTPD";
# website = "http://proftpd.org/";
# license.url = "https://github.com/proftpd/proftpd/blob/master/COPYING";
# license.name = "GNU General Public License v2.0";
# version = pkgs.proftpd.version;
# source.url = "https://github.com/proftpd/proftpd/";
# } else {};
#};
security.acme.certs."ftp" = {
domain = "eldiron.immae.eu";
# FIXME: make it global
extraLegoRunFlags = ["--preferred-chain" "ISRG Root X1"];
extraLegoRenewFlags = ["--preferred-chain" "ISRG Root X1"];
postRun = (lib.optionalString pure-ftpd-enabled ''
systemctl restart pure-ftpd.service
'') + (lib.optionalString proftpd-enabled ''
systemctl restart proftpd.service
'');
extraDomainNames = [ "ftp.immae.eu" ];
};
networking = {
firewall = {
allowedTCPPorts = [ 21 115 ];
allowedTCPPortRanges = [ { from = 40000; to = 50000; } ];
};
};
users.users.ftp = {
uid = config.ids.uids.ftp; # 8
group = "ftp";
description = "Anonymous FTP user";
home = "/homeless-shelter";
extraGroups = [ "keys" ];
};
users.groups.ftp.gid = config.ids.gids.ftp;
system.activationScripts.ftp = ''
install -m 0755 -o ftp -g ftp -d /var/lib/ftp
'' + (lib.optionalString proftpd-enabled ''
install -m 0755 -o nobody -g nogroup -d /var/lib/proftpd/authorized_keys
'');
secrets.keys."pure-ftpd-ldap" = lib.mkIf pure-ftpd-enabled {
permissions = "0400";
user = "ftp";
group = "ftp";
text = ''
LDAPServer ${config.myEnv.ftp.ldap.host}
LDAPPort 389
LDAPUseTLS True
LDAPBaseDN ${config.myEnv.ftp.ldap.base}
LDAPBindDN ${config.myEnv.ftp.ldap.dn}
LDAPBindPW ${config.myEnv.ftp.ldap.password}
LDAPDefaultUID 500
LDAPForceDefaultUID False
LDAPDefaultGID 100
LDAPForceDefaultGID False
LDAPFilter ${config.myEnv.ftp.ldap.pure-ftpd_filter}
LDAPAuthMethod BIND
# Pas de possibilite de donner l'Uid/Gid !
# Compile dans pure-ftpd directement avec immaeFtpUid / immaeFtpGid
LDAPHomeDir immaeFtpDirectory
'';
};
secrets.keys."proftpd-ldap.conf" = lib.mkIf proftpd-enabled {
permissions = "0400";
user = "ftp";
group = "ftp";
text = ''
LDAPServer ldaps://${config.myEnv.ftp.ldap.host}:636/??sub
LDAPUseTLS on
LDAPAuthBinds on
LDAPBindDN "${config.myEnv.ftp.ldap.dn}" "${config.myEnv.ftp.ldap.password}"
LDAPSearchScope subtree
LDAPAuthBinds on
LDAPDefaultGID 100
LDAPDefaultUID 500
LDAPForceDefaultUID off
LDAPForceDefaultGID off
LDAPAttr gidNumber immaeFtpGid
LDAPAttr uidNumber immaeFtpUid
LDAPAttr homeDirectory immaeFtpDirectory
LDAPUsers "${config.myEnv.ftp.ldap.base}" "${config.myEnv.ftp.ldap.proftpd_filter}"
LDAPGroups "${config.myEnv.ftp.ldap.base}"
'';
};
services.filesWatcher.pure-ftpd = lib.mkIf pure-ftpd-enabled {
restart = true;
paths = [ config.secrets.fullPaths."pure-ftpd-ldap" ];
};
services.filesWatcher.proftpd = lib.mkIf proftpd-enabled {
restart = true;
paths = [ config.secrets.fullPaths."proftpd-ldap.conf" ];
};
systemd.services.pure-ftpd = let
configFile = pkgs.writeText "pure-ftpd.conf" ''
PassivePortRange 40000 50000
Bind 42
ChrootEveryone yes
CreateHomeDir yes
BrokenClientsCompatibility yes
MaxClientsNumber 50
Daemonize yes
MaxClientsPerIP 8
VerboseLog no
DisplayDotFiles yes
AnonymousOnly no
NoAnonymous no
SyslogFacility ftp
DontResolve yes
MaxIdleTime 15
LDAPConfigFile ${config.secrets.fullPaths."pure-ftpd-ldap"}
LimitRecursion 10000 8
AnonymousCanCreateDirs no
MaxLoad 4
AntiWarez yes
Umask 133:022
# ftp
MinUID 8
AllowUserFXP no
AllowAnonymousFXP no
ProhibitDotFilesWrite no
ProhibitDotFilesRead no
AutoRename no
AnonymousCantUpload no
MaxDiskUsage 99
CustomerProof yes
TLS 1
CertFile ${config.security.acme.certs.ftp.directory}/full.pem
'';
in lib.mkIf pure-ftpd-enabled {
description = "Pure-FTPd server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig.ExecStart = "${package}/bin/pure-ftpd ${configFile}";
serviceConfig.Type = "forking";
serviceConfig.PIDFile = "/run/pure-ftpd.pid";
};
systemd.services.proftpd = let
configFile = pkgs.writeText "proftpd.conf" ''
ServerName "ProFTPD"
ServerType standalone
DefaultServer on
Port 21
UseIPv6 on
Umask 022
MaxInstances 30
MaxClients 50
MaxClientsPerHost 8
# Set the user and group under which the server will run.
User ftp
Group ftp
CreateHome on
DefaultRoot ~
AllowOverwrite on
TLSEngine on
TLSRequired off
TLSProtocol TLSv1.1 TLSv1.2 TLSv1.3
TLSCertificateChainFile ${config.security.acme.certs.ftp.directory}/fullchain.pem
TLSECCertificateFile ${config.security.acme.certs.ftp.directory}/cert.pem
TLSECCertificateKeyFile ${config.security.acme.certs.ftp.directory}/key.pem
TLSRenegotiate none
PidFile /run/proftpd/proftpd.pid
ScoreboardFile /run/proftpd/proftpd.scoreboard
PassivePorts 40000 50000
#DebugLevel 10
Include ${config.secrets.fullPaths."proftpd-ldap.conf"}
RequireValidShell off
# Bar use of SITE CHMOD by default
DenyAll
Umask 022
Port 115
SFTPEngine on
CreateHome on
DefaultRoot ~
AllowOverwrite on
SFTPHostKey /etc/ssh/ssh_host_ed25519_key
SFTPHostKey /etc/ssh/ssh_host_rsa_key
Include ${config.secrets.fullPaths."proftpd-ldap.conf"}
RequireValidShell off
SFTPAuthorizedUserKeys file:/var/lib/proftpd/authorized_keys/%u
SFTPAuthMethods password publickey
SFTPOptions IgnoreSFTPSetOwners
AllowChrootSymlinks off
'';
in lib.mkIf proftpd-enabled {
description = "ProFTPD server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig.ExecStart = "${pkgs.proftpd}/bin/proftpd -c ${configFile}";
serviceConfig.Type = "forking";
serviceConfig.PIDFile = "/run/proftpd/proftpd.pid";
serviceConfig.RuntimeDirectory = "proftpd";
};
services.cron.systemCronJobs = lib.mkIf proftpd-enabled [
"*/2 * * * * nobody ${./ftp_sync.sh}"
];
myServices.monitoring.fromMasterActivatedPlugins = [ "ftp" ];
myServices.monitoring.fromMasterObjects.service = [
{
service_description = "ftp has access to database for authentication";
host_name = config.hostEnv.fqdn;
use = "external-service";
check_command = "check_ftp_database";
servicegroups = "webstatus-remote-services";
_webstatus_name = "FTP";
_webstatus_url = "ftp.immae.eu";
}
];
};
}