diff --git a/maintainers/maintainer-list.nix b/maintainers/maintainer-list.nix index 5397845d728a..010c032cc097 100644 --- a/maintainers/maintainer-list.nix +++ b/maintainers/maintainer-list.nix @@ -4523,6 +4523,11 @@ github = "y0no"; name = "Yoann Ono"; }; + yarny = { + email = "41838844+Yarny0@users.noreply.github.com"; + github = "Yarny0"; + name = "Yarny"; + }; yarr = { email = "savraz@gmail.com"; github = "Eternity-Yarr"; diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index 8292cdc995e0..aafeb997c326 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -53,7 +53,7 @@ tomcat = 16; #audio = 17; # unused #floppy = 18; # unused - #uucp = 19; # unused + uucp = 19; #lp = 20; # unused #proc = 21; # unused pulseaudio = 22; # must match `pulseaudio' GID diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 3f3123798f59..f51a30aec2e9 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -517,6 +517,7 @@ ./services/networking/heyefi.nix ./services/networking/hostapd.nix ./services/networking/htpdate.nix + ./services/networking/hylafax/default.nix ./services/networking/i2pd.nix ./services/networking/i2p.nix ./services/networking/iodine.nix diff --git a/nixos/modules/services/networking/hylafax/default.nix b/nixos/modules/services/networking/hylafax/default.nix new file mode 100644 index 000000000000..4c63b822d165 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/default.nix @@ -0,0 +1,29 @@ +{ config, lib, pkgs, ... }: + +{ + + imports = [ + ./options.nix + ./systemd.nix + ]; + + config = lib.modules.mkIf config.services.hylafax.enable { + environment.systemPackages = [ pkgs.hylafaxplus ]; + users.users.uucp = { + uid = config.ids.uids.uucp; + group = "uucp"; + description = "Unix-to-Unix CoPy system"; + isSystemUser = true; + inherit (config.users.users.nobody) home; + }; + assertions = [{ + assertion = config.services.hylafax.modems != {}; + message = '' + HylaFAX cannot be used without modems. + Please define at least one modem with + . + ''; + }]; + }; + +} diff --git a/nixos/modules/services/networking/hylafax/faxq-default.nix b/nixos/modules/services/networking/hylafax/faxq-default.nix new file mode 100644 index 000000000000..a2630ce66b71 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/faxq-default.nix @@ -0,0 +1,12 @@ +{ ... }: + +# see man:hylafax-config(5) + +{ + + ModemGroup = [ ''"any:.*"'' ]; + ServerTracing = "0x78701"; + SessionTracing = "0x78701"; + UUCPLockDir = "/var/lock"; + +} diff --git a/nixos/modules/services/networking/hylafax/faxq-wait.sh b/nixos/modules/services/networking/hylafax/faxq-wait.sh new file mode 100755 index 000000000000..8c39e9d20c18 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/faxq-wait.sh @@ -0,0 +1,29 @@ +#! @shell@ -e + +# skip this if there are no modems at all +if ! stat -t "@spoolAreaPath@"/etc/config.* >/dev/null 2>&1 +then + exit 0 +fi + +echo "faxq started, waiting for modem(s) to initialize..." + +for i in `seq @timeoutSec@0 -1 0` # gracefully timeout +do + sleep 0.1 + # done if status files exist, but don't mention initialization + if \ + stat -t "@spoolAreaPath@"/status/* >/dev/null 2>&1 \ + && \ + ! grep --silent --ignore-case 'initializing server' \ + "@spoolAreaPath@"/status/* + then + echo "modem(s) apparently ready" + exit 0 + fi + # if i reached 0, modems probably failed to initialize + if test $i -eq 0 + then + echo "warning: modem initialization timed out" + fi +done diff --git a/nixos/modules/services/networking/hylafax/hfaxd-default.nix b/nixos/modules/services/networking/hylafax/hfaxd-default.nix new file mode 100644 index 000000000000..8999dae57f41 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/hfaxd-default.nix @@ -0,0 +1,10 @@ +{ ... }: + +# see man:hfaxd(8) + +{ + + ServerTracing = "0x91"; + XferLogFile = "/clientlog"; + +} diff --git a/nixos/modules/services/networking/hylafax/modem-default.nix b/nixos/modules/services/networking/hylafax/modem-default.nix new file mode 100644 index 000000000000..7529b5b0aafd --- /dev/null +++ b/nixos/modules/services/networking/hylafax/modem-default.nix @@ -0,0 +1,22 @@ +{ pkgs, ... }: + +# see man:hylafax-config(5) + +{ + + TagLineFont = "etc/LiberationSans-25.pcf"; + TagLineLocale = ''en_US.UTF-8''; + + AdminGroup = "root"; # groups that can change server config + AnswerRotary = "fax"; # don't accept anything else but faxes + LogFileMode = "0640"; + PriorityScheduling = true; + RecvFileMode = "0640"; + ServerTracing = "0x78701"; + SessionTracing = "0x78701"; + UUCPLockDir = "/var/lock"; + + SendPageCmd = ''${pkgs.coreutils}/bin/false''; # prevent pager transmit + SendUUCPCmd = ''${pkgs.coreutils}/bin/false''; # prevent UUCP transmit + +} diff --git a/nixos/modules/services/networking/hylafax/options.nix b/nixos/modules/services/networking/hylafax/options.nix new file mode 100644 index 000000000000..4ac6d3fa8432 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/options.nix @@ -0,0 +1,375 @@ +{ config, lib, pkgs, ... }: + +let + + inherit (lib.options) literalExample mkEnableOption mkOption; + inherit (lib.types) bool enum int lines loaOf nullOr path str submodule; + inherit (lib.modules) mkDefault mkIf mkMerge; + + commonDescr = '' + Values can be either strings or integers + (which will be added to the config file verbatimly) + or lists thereof + (which will be translated to multiple + lines with the same configuration key). + Boolean values are translated to "Yes" or "No". + The default contains some reasonable + configuration to yield an operational system. + ''; + + str1 = lib.types.addCheck str (s: s!=""); # non-empty string + int1 = lib.types.addCheck int (i: i>0); # positive integer + + configAttrType = + # Options in HylaFAX configuration files can be + # booleans, strings, integers, or list thereof + # representing multiple config directives with the same key. + # This type definition resolves all + # those types into a list of strings. + let + inherit (lib.types) attrsOf coercedTo listOf; + innerType = coercedTo bool (x: if x then "Yes" else "No") + (coercedTo int (toString) str); + in + attrsOf (coercedTo innerType lib.singleton (listOf innerType)); + + cfg = config.services.hylafax; + + modemConfigOptions = { name, config, ... }: { + options = { + name = mkOption { + type = str1; + example = "ttyS1"; + description = '' + Name of modem device, + will be searched for in /dev. + ''; + }; + type = mkOption { + type = str1; + example = "cirrus"; + description = '' + Name of modem configuration file, + will be searched for in config + in the spooling area directory. + ''; + }; + config = mkOption { + type = configAttrType; + example = { + AreaCode = "49"; + LocalCode = "30"; + FAXNumber = "123456"; + LocalIdentifier = "LostInBerlin"; + }; + description = '' + Attribute set of values for the given modem. + ${commonDescr} + Options defined here override options in + for this modem. + ''; + }; + }; + config.name = mkDefault name; + config.config.Include = [ "config/${config.type}" ]; + }; + + defaultConfig = + let + inherit (config.security) wrapperDir; + inherit (config.services.mail.sendmailSetuidWrapper) program; + mkIfDefault = cond: value: mkIf cond (mkDefault value); + noWrapper = config.services.mail.sendmailSetuidWrapper==null; + # If a sendmail setuid wrapper exists, + # we add the path to the default configuration file. + # Otherwise, we use `false` to provoke + # an error if hylafax tries to use it. + c.sendmailPath = mkMerge [ + (mkIfDefault noWrapper ''${pkgs.coreutils}/bin/false'') + (mkIfDefault (!noWrapper) ''${wrapperDir}/${program}'') + ]; + importDefaultConfig = file: + lib.attrsets.mapAttrs + (lib.trivial.const mkDefault) + (import file { inherit pkgs; }); + c.commonModemConfig = importDefaultConfig ./modem-default.nix; + c.faxqConfig = importDefaultConfig ./faxq-default.nix; + c.hfaxdConfig = importDefaultConfig ./hfaxd-default.nix; + in + c; + + localConfig = + let + c.hfaxdConfig.UserAccessFile = cfg.userAccessFile; + c.faxqConfig = lib.attrsets.mapAttrs + (lib.trivial.const (v: mkIf (v!=null) v)) + { + AreaCode = cfg.areaCode; + CountryCode = cfg.countryCode; + LongDistancePrefix = cfg.longDistancePrefix; + InternationalPrefix = cfg.internationalPrefix; + }; + c.commonModemConfig = c.faxqConfig; + in + c; + +in + + +{ + + + options.services.hylafax = { + + enable = mkEnableOption ''HylaFAX server''; + + autostart = mkOption { + type = bool; + default = true; + example = false; + description = '' + Autostart the HylaFAX queue manager at system start. + If this is false, the queue manager + will still be started if there are pending + jobs or if a user tries to connect to it. + ''; + }; + + countryCode = mkOption { + type = nullOr str1; + default = null; + example = "49"; + description = ''Country code for server and all modems.''; + }; + + areaCode = mkOption { + type = nullOr str1; + default = null; + example = "30"; + description = ''Area code for server and all modems.''; + }; + + longDistancePrefix = mkOption { + type = nullOr str; + default = null; + example = "0"; + description = ''Long distance prefix for server and all modems.''; + }; + + internationalPrefix = mkOption { + type = nullOr str; + default = null; + example = "00"; + description = ''International prefix for server and all modems.''; + }; + + spoolAreaPath = mkOption { + type = path; + default = "/var/spool/fax"; + description = '' + The spooling area will be created/maintained + at the location given here. + ''; + }; + + userAccessFile = mkOption { + type = path; + default = "/etc/hosts.hfaxd"; + description = '' + The hosts.hfaxd + file entry in the spooling area + will be symlinked to the location given here. + This file must exist and be + readable only by the uucp user. + See hosts.hfaxd(5) for details. + This configuration permits access for all users: + + environment.etc."hosts.hfaxd" = { + mode = "0600"; + user = "uucp"; + text = ".*"; + }; + + Note that host-based access can be controlled with + ; + by default, only 127.0.0.1 is permitted to connect. + ''; + }; + + sendmailPath = mkOption { + type = path; + example = literalExample "''${pkgs.postfix}/bin/sendmail"; + # '' ; # fix vim + description = '' + Path to sendmail program. + The default uses the local sendmail wrapper + (see ), + otherwise the false + binary to cause an error if used. + ''; + }; + + hfaxdConfig = mkOption { + type = configAttrType; + example.RecvqProtection = "0400"; + description = '' + Attribute set of lines for the global + hfaxd config file etc/hfaxd.conf. + ${commonDescr} + ''; + }; + + faxqConfig = mkOption { + type = configAttrType; + example = { + InternationalPrefix = "00"; + LongDistancePrefix = "0"; + }; + description = '' + Attribute set of lines for the global + faxq config file etc/config. + ${commonDescr} + ''; + }; + + commonModemConfig = mkOption { + type = configAttrType; + example = { + InternationalPrefix = "00"; + LongDistancePrefix = "0"; + }; + description = '' + Attribute set of default values for + modem config files etc/config.*. + ${commonDescr} + Think twice before changing + paths of fax-processing scripts. + ''; + }; + + modems = mkOption { + type = loaOf (submodule [ modemConfigOptions ]); + default = {}; + example.ttyS1 = { + type = "cirrus"; + config = { + FAXNumber = "123456"; + LocalIdentifier = "Smith"; + }; + }; + description = '' + Description of installed modems. + At least on modem must be defined + to enable the HylaFAX server. + ''; + }; + + spoolExtraInit = mkOption { + type = lines; + default = ""; + example = ''chmod 0755 . # everyone may read my faxes''; + description = '' + Additional shell code that is executed within the + spooling area directory right after its setup. + ''; + }; + + faxcron.enable.spoolInit = mkEnableOption '' + Purge old files from the spooling area with + faxcron + each time the spooling area is initialized. + ''; + faxcron.enable.frequency = mkOption { + type = nullOr str1; + default = null; + example = "daily"; + description = '' + Purge old files from the spooling area with + faxcron with the given frequency + (see systemd.time(7)). + ''; + }; + faxcron.infoDays = mkOption { + type = int1; + default = 30; + description = '' + Set the expiration time for data in the + remote machine information directory in days. + ''; + }; + faxcron.logDays = mkOption { + type = int1; + default = 30; + description = '' + Set the expiration time for + session trace log files in days. + ''; + }; + faxcron.rcvDays = mkOption { + type = int1; + default = 7; + description = '' + Set the expiration time for files in + the received facsimile queue in days. + ''; + }; + + faxqclean.enable.spoolInit = mkEnableOption '' + Purge old files from the spooling area with + faxqclean + each time the spooling area is initialized. + ''; + faxqclean.enable.frequency = mkOption { + type = nullOr str1; + default = null; + example = "daily"; + description = '' + Purge old files from the spooling area with + faxcron with the given frequency + (see systemd.time(7)). + ''; + }; + faxqclean.archiving = mkOption { + type = enum [ "never" "as-flagged" "always" ]; + default = "as-flagged"; + example = "always"; + description = '' + Enable or suppress job archiving: + never disables job archiving, + as-flagged archives jobs that + have been flagged for archiving by sendfax, + always forces archiving of all jobs. + See also sendfax(1) and faxqclean(8). + ''; + }; + faxqclean.doneqMinutes = mkOption { + type = int1; + default = 15; + example = literalExample ''24*60''; + description = '' + Set the job + age threshold (in minutes) that controls how long + jobs may reside in the doneq directory. + ''; + }; + faxqclean.docqMinutes = mkOption { + type = int1; + default = 60; + example = literalExample ''24*60''; + description = '' + Set the document + age threshold (in minutes) that controls how long + unreferenced files may reside in the docq directory. + ''; + }; + + }; + + + config.services.hylafax = + mkIf + (config.services.hylafax.enable) + (mkMerge [ defaultConfig localConfig ]) + ; + +} diff --git a/nixos/modules/services/networking/hylafax/spool.sh b/nixos/modules/services/networking/hylafax/spool.sh new file mode 100755 index 000000000000..31e930e8c597 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/spool.sh @@ -0,0 +1,111 @@ +#! @shell@ -e + +# The following lines create/update the HylaFAX spool directory: +# Subdirectories/files with persistent data are kept, +# other directories/files are removed/recreated, +# mostly from the template spool +# directory in the HylaFAX package. + +# This block explains how the spool area is +# derived from the spool template in the HylaFAX package: +# +# + capital letter: directory; file otherwise +# + P/p: persistent directory +# + F/f: directory with symlinks per entry +# + T/t: temporary data +# + S/s: single symlink into package +# | +# | + u: change ownership to uucp:uucp +# | + U: ..also change access mode to user-only +# | | +# archive P U +# bin S +# client T u (client connection info) +# config S +# COPYRIGHT s +# dev T u (maybe some FIFOs) +# docq P U +# doneq P U +# etc F contains customized config files! +# etc/hosts.hfaxd f +# etc/xferfaxlog f +# info P u (database of called devices) +# log P u (communication logs) +# pollq P U +# recvq P u +# sendq P U +# status T u (modem status info files) +# tmp T U + + +shopt -s dotglob # if bash sees "*", it also includes dot files +lnsym () { ln --symbol "$@" ; } +lnsymfrc () { ln --symbolic --force "$@" ; } +cprd () { cp --remove-destination "$@" ; } +update () { install --owner=@faxuser@ --group=@faxgroup@ "$@" ; } + + +## create/update spooling area + +update --mode=0750 -d "@spoolAreaPath@" +cd "@spoolAreaPath@" + +persist=(archive docq doneq info log pollq recvq sendq) + +# remove entries that don't belong here +touch dummy # ensure "*" resolves to something +for k in * +do + keep=0 + for j in "${persist[@]}" xferfaxlog clientlog faxcron.lastrun + do + if test "$k" == "$j" + then + keep=1 + break + fi + done + if test "$keep" == "0" + then + rm --recursive "$k" + fi +done + +# create persistent data directories (unless they exist already) +update --mode=0700 -d "${persist[@]}" +chmod 0755 info log recvq + +# create ``xferfaxlog``, ``faxcron.lastrun``, ``clientlog`` +touch clientlog faxcron.lastrun xferfaxlog +chown @faxuser@:@faxgroup@ clientlog faxcron.lastrun xferfaxlog + +# create symlinks for frozen directories/files +lnsym --target-directory=. "@hylafax@"/spool/{COPYRIGHT,bin,config} + +# create empty temporary directories +update --mode=0700 -d client dev status +update -d tmp + + +## create and fill etc + +install -d "@spoolAreaPath@/etc" +cd "@spoolAreaPath@/etc" + +# create symlinks to all files in template's etc +lnsym --target-directory=. "@hylafax@/spool/etc"/* + +# set LOCKDIR in setup.cache +sed --regexp-extended 's|^(UUCP_LOCKDIR=).*$|\1'"'@lockPath@'|g" --in-place setup.cache + +# etc/{xferfaxlog,lastrun} are stored in the spool root +lnsymfrc --target-directory=. ../xferfaxlog +lnsymfrc --no-target-directory ../faxcron.lastrun lastrun + +# etc/hosts.hfaxd is provided by the NixOS configuration +lnsymfrc --no-target-directory "@userAccessFile@" hosts.hfaxd + +# etc/config and etc/config.${DEVID} must be copied: +# hfaxd reads these file after locking itself up in a chroot +cprd --no-target-directory "@globalConfigPath@" config +cprd --target-directory=. "@modemConfigPath@"/* diff --git a/nixos/modules/services/networking/hylafax/systemd.nix b/nixos/modules/services/networking/hylafax/systemd.nix new file mode 100644 index 000000000000..91d9c1a37da6 --- /dev/null +++ b/nixos/modules/services/networking/hylafax/systemd.nix @@ -0,0 +1,249 @@ +{ config, lib, pkgs, ... }: + + +let + + inherit (lib) mkIf mkMerge; + inherit (lib) concatStringsSep optionalString; + + cfg = config.services.hylafax; + mapModems = lib.flip map (lib.attrValues cfg.modems); + + mkConfigFile = name: conf: + # creates hylafax config file, + # makes sure "Include" is listed *first* + let + mkLines = conf: + (lib.concatLists + (lib.flip lib.mapAttrsToList conf + (k: map (v: ''${k}: ${v}'') + ))); + include = mkLines { Include = conf.Include or []; }; + other = mkLines ( conf // { Include = []; } ); + in + pkgs.writeText ''hylafax-config${name}'' + (concatStringsSep "\n" (include ++ other)); + + globalConfigPath = mkConfigFile "" cfg.faxqConfig; + + modemConfigPath = + let + mkModemConfigFile = { config, name, ... }: + mkConfigFile ''.${name}'' + (cfg.commonModemConfig // config); + mkLine = { name, type, ... }@modem: '' + # check if modem config file exists: + test -f "${pkgs.hylafaxplus}/spool/config/${type}" + ln \ + --symbolic \ + --no-target-directory \ + "${mkModemConfigFile modem}" \ + "$out/config.${name}" + ''; + in + pkgs.runCommand "hylafax-config-modems" {} + ''mkdir --parents "$out/" ${concatStringsSep "\n" (mapModems mkLine)}''; + + setupSpoolScript = pkgs.substituteAll { + name = "hylafax-setup-spool.sh"; + src = ./spool.sh; + isExecutable = true; + inherit (pkgs.stdenv) shell; + hylafax = pkgs.hylafaxplus; + faxuser = "uucp"; + faxgroup = "uucp"; + lockPath = "/var/lock"; + inherit globalConfigPath modemConfigPath; + inherit (cfg) sendmailPath spoolAreaPath userAccessFile; + }; + + waitFaxqScript = pkgs.substituteAll { + # This script checks the modems status files + # and waits until all modems report readiness. + name = "hylafax-faxq-wait-start.sh"; + src = ./faxq-wait.sh; + isExecutable = true; + timeoutSec = toString 10; + inherit (pkgs.stdenv) shell; + inherit (cfg) spoolAreaPath; + }; + + sockets."hylafax-hfaxd" = { + description = "HylaFAX server socket"; + documentation = [ "man:hfaxd(8)" ]; + wantedBy = [ "multi-user.target" ]; + listenStreams = [ "127.0.0.1:4559" ]; + socketConfig.FreeBind = true; + socketConfig.Accept = true; + }; + + paths."hylafax-faxq" = { + description = "HylaFAX queue manager sendq watch"; + documentation = [ "man:faxq(8)" "man:sendq(5)" ]; + wantedBy = [ "multi-user.target" ]; + pathConfig.PathExistsGlob = [ ''${cfg.spoolAreaPath}/sendq/q*'' ]; + }; + + timers = mkMerge [ + ( + mkIf (cfg.faxcron.enable.frequency!=null) + { "hylafax-faxcron".timerConfig.Persistent = true; } + ) + ( + mkIf (cfg.faxqclean.enable.frequency!=null) + { "hylafax-faxqclean".timerConfig.Persistent = true; } + ) + ]; + + hardenService = + # Add some common systemd service hardening settings, + # but allow each service (here) to override + # settings by explicitely setting those to `null`. + # More hardening would be nice but makes + # customizing hylafax setups very difficult. + # If at all, it should only be added along + # with some options to customize it. + let + hardening = { + PrivateDevices = true; # breaks /dev/tty... + PrivateNetwork = true; + PrivateTmp = true; + ProtectControlGroups = true; + #ProtectHome = true; # breaks custom spool dirs + ProtectKernelModules = true; + ProtectKernelTunables = true; + #ProtectSystem = "strict"; # breaks custom spool dirs + RestrictNamespaces = true; + RestrictRealtime = true; + }; + filter = key: value: (value != null) || ! (lib.hasAttr key hardening); + apply = service: lib.filterAttrs filter (hardening // (service.serviceConfig or {})); + in + service: service // { serviceConfig = apply service; }; + + services."hylafax-spool" = { + description = "HylaFAX spool area preparation"; + documentation = [ "man:hylafax-server(4)" ]; + script = '' + ${setupSpoolScript} + cd "${cfg.spoolAreaPath}" + ${cfg.spoolExtraInit} + if ! test -f "${cfg.spoolAreaPath}/etc/hosts.hfaxd" + then + echo hosts.hfaxd is missing + exit 1 + fi + ''; + serviceConfig.ExecStop = ''${setupSpoolScript}''; + serviceConfig.RemainAfterExit = true; + serviceConfig.Type = "oneshot"; + unitConfig.RequiresMountsFor = [ cfg.spoolAreaPath ]; + }; + + services."hylafax-faxq" = { + description = "HylaFAX queue manager"; + documentation = [ "man:faxq(8)" ]; + requires = [ "hylafax-spool.service" ]; + after = [ "hylafax-spool.service" ]; + wants = mapModems ( { name, ... }: ''hylafax-faxgetty@${name}.service'' ); + wantedBy = mkIf cfg.autostart [ "multi-user.target" ]; + serviceConfig.Type = "forking"; + serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/faxq -q "${cfg.spoolAreaPath}"''; + # This delays the "readiness" of this service until + # all modems are initialized (or a timeout is reached). + # Otherwise, sending a fax with the fax service + # stopped will always yield a failed send attempt: + # The fax service is started when the job is created with + # `sendfax`, but modems need some time to initialize. + serviceConfig.ExecStartPost = [ ''${waitFaxqScript}'' ]; + # faxquit fails if the pipe is already gone + # (e.g. the service is already stopping) + serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}"''; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.RestrictRealtime = null; + }; + + services."hylafax-hfaxd@" = { + description = "HylaFAX server"; + documentation = [ "man:hfaxd(8)" ]; + after = [ "hylafax-faxq.service" ]; + requires = [ "hylafax-faxq.service" ]; + serviceConfig.StandardInput = "socket"; + serviceConfig.StandardOutput = "socket"; + serviceConfig.ExecStart = ''${pkgs.hylafaxplus}/spool/bin/hfaxd -q "${cfg.spoolAreaPath}" -d -I''; + unitConfig.RequiresMountsFor = [ cfg.userAccessFile ]; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.PrivateNetwork = null; + }; + + services."hylafax-faxcron" = rec { + description = "HylaFAX spool area maintenance"; + documentation = [ "man:faxcron(8)" ]; + after = [ "hylafax-spool.service" ]; + requires = [ "hylafax-spool.service" ]; + wantedBy = mkIf cfg.faxcron.enable.spoolInit requires; + startAt = mkIf (cfg.faxcron.enable.frequency!=null) cfg.faxcron.enable.frequency; + serviceConfig.ExecStart = concatStringsSep " " [ + ''${pkgs.hylafaxplus}/spool/bin/faxcron'' + ''-q "${cfg.spoolAreaPath}"'' + ''-info ${toString cfg.faxcron.infoDays}'' + ''-log ${toString cfg.faxcron.logDays}'' + ''-rcv ${toString cfg.faxcron.rcvDays}'' + ]; + }; + + services."hylafax-faxqclean" = rec { + description = "HylaFAX spool area queue cleaner"; + documentation = [ "man:faxqclean(8)" ]; + after = [ "hylafax-spool.service" ]; + requires = [ "hylafax-spool.service" ]; + wantedBy = mkIf cfg.faxqclean.enable.spoolInit requires; + startAt = mkIf (cfg.faxqclean.enable.frequency!=null) cfg.faxqclean.enable.frequency; + serviceConfig.ExecStart = concatStringsSep " " [ + ''${pkgs.hylafaxplus}/spool/bin/faxqclean'' + ''-q "${cfg.spoolAreaPath}"'' + ''-v'' + (optionalString (cfg.faxqclean.archiving!="never") ''-a'') + (optionalString (cfg.faxqclean.archiving=="always") ''-A'') + ''-j ${toString (cfg.faxqclean.doneqMinutes*60)}'' + ''-d ${toString (cfg.faxqclean.docqMinutes*60)}'' + ]; + }; + + mkFaxgettyService = { name, ... }: + lib.nameValuePair ''hylafax-faxgetty@${name}'' rec { + description = "HylaFAX faxgetty for %I"; + documentation = [ "man:faxgetty(8)" ]; + bindsTo = [ "dev-%i.device" ]; + requires = [ "hylafax-spool.service" ]; + after = bindsTo ++ requires; + before = [ "hylafax-faxq.service" "getty.target" ]; + unitConfig.StopWhenUnneeded = true; + unitConfig.AssertFileNotEmpty = ''${cfg.spoolAreaPath}/etc/config.%I''; + serviceConfig.UtmpIdentifier = "%I"; + serviceConfig.TTYPath = "/dev/%I"; + serviceConfig.Restart = "always"; + serviceConfig.KillMode = "process"; + serviceConfig.IgnoreSIGPIPE = false; + serviceConfig.ExecStart = ''-${pkgs.hylafaxplus}/spool/bin/faxgetty -q "${cfg.spoolAreaPath}" /dev/%I''; + # faxquit fails if the pipe is already gone + # (e.g. the service is already stopping) + serviceConfig.ExecStop = ''-${pkgs.hylafaxplus}/spool/bin/faxquit -q "${cfg.spoolAreaPath}" %I''; + # disable some systemd hardening settings + serviceConfig.PrivateDevices = null; + serviceConfig.RestrictRealtime = null; + }; + + modemServices = + lib.listToAttrs (mapModems mkFaxgettyService); + +in + +{ + config.systemd = mkIf cfg.enable { + inherit sockets timers paths; + services = lib.mapAttrs (lib.const hardenService) (services // modemServices); + }; +} diff --git a/pkgs/servers/hylafaxplus/config.site b/pkgs/servers/hylafaxplus/config.site new file mode 100644 index 000000000000..7c8014449216 --- /dev/null +++ b/pkgs/servers/hylafaxplus/config.site @@ -0,0 +1,20 @@ +@config_maxgid@ +DIR_BIN="@out_@/bin" +DIR_FONTMAP="@out_@/share/ghostscript/@ghostscript_version@" +DIR_LIB="@out_@/lib" +DIR_LIBDATA="@out_@/spool/etc" +DIR_LIBEXEC="@out_@/spool/bin" +DIR_LOCKS=/var/lock +DIR_MAN="@out_@/share/man" +DIR_SBIN="@out_@/spool/bin" +DIR_SPOOL="@out_@/spool" +FONTMAP="@ghostscript@/share/ghostscript/@ghostscript_version@" +PATH_AFM="@ghostscript@/share/ghostscript/fonts" +PATH_DPSRIP="@out_@/spool/bin/ps2fax" +PATH_EGETTY="@coreutils@/bin/false" +PATH_GSRIP="@ghostscript@/bin/gs" +PATH_IMPRIP="@coreutils@/bin/false" +PATH_SENDMAIL="@coreutils@/bin/false" +PATH_VGETTY="@coreutils@/bin/false" +SYSVINIT=no +TIFFBIN="@libtiff@/bin" diff --git a/pkgs/servers/hylafaxplus/default.nix b/pkgs/servers/hylafaxplus/default.nix new file mode 100644 index 000000000000..410d24974268 --- /dev/null +++ b/pkgs/servers/hylafaxplus/default.nix @@ -0,0 +1,95 @@ +{ stdenv +, lib +, fakeroot +, fetchurl +, libfaketime +, substituteAll +## runtime dependencies +, coreutils +, file +, findutils +, gawk +, ghostscript +, gnugrep +, gnused +, libtiff +, psmisc +, sharutils +, utillinux +, zlib +## optional packages (using `null` disables some functionality) +, jbigkit ? null +, lcms2 ? null # for colored faxes +, openldap ? null +, pam ? null +## system-dependent settings that have to be hardcoded +, maxgid ? 65534 # null -> try to auto-detect (bad on linux) +, maxuid ? 65534 # null -> hardcoded value 60002 +}: + +let + + name = "hylafaxplus-${version}"; + version = "5.6.0"; + sha256 = "128514kw9kb5cvznm87z7gis1mpyx4bcqrxx4xa7cbfj1v3v81fr"; + + configSite = substituteAll { + name = "hylafaxplus-config.site"; + src = ./config.site; + config_maxgid = lib.optionalString (maxgid!=null) ''CONFIG_MAXGID=${builtins.toString maxgid}''; + ghostscript_version = ghostscript.version; + out_ = "@out@"; # "out" will be resolved in post-install.sh + inherit coreutils ghostscript libtiff; + }; + + postPatch = substituteAll { + name = "hylafaxplus-post-patch.sh"; + src = ./post-patch.sh; + inherit configSite; + maxuid = lib.optionalString (maxuid!=null) (builtins.toString maxuid); + faxcover_binpath = lib.makeBinPath + [stdenv.shellPackage coreutils]; + faxsetup_binpath = lib.makeBinPath + [stdenv.shellPackage coreutils findutils gnused gnugrep gawk]; + }; + + postInstall = substituteAll { + name = "hylafaxplus-post-install.sh"; + src = ./post-install.sh; + inherit fakeroot libfaketime; + }; + +in + +stdenv.mkDerivation { + inherit name version; + src = fetchurl { + url = "mirror://sourceforge/hylafax/hylafax-${version}.tar.gz"; + inherit sha256; + }; + # Note that `configure` (and maybe `faxsetup`) are looking + # for a couple of standard binaries in the `PATH` and + # hardcode their absolute paths in the new package. + buildInputs = [ + file # for `file` command + ghostscript + libtiff + psmisc # for `fuser` command + sharutils # for `uuencode` command + utillinux # for `agetty` command + zlib + jbigkit # optional + lcms2 # optional + openldap # optional + pam # optional + ]; + postPatch = ''. ${postPatch}''; + dontAddPrefix = true; + postInstall = ''. ${postInstall}''; + postInstallCheck = ''. ${./post-install-check.sh}''; + meta.description = "enterprise-class system for sending and receiving facsimiles"; + meta.homepage = http://hylafax.sourceforge.net; + meta.license = lib.licenses.bsd3; + meta.maintainers = [ lib.maintainers.yarny ]; + meta.platforms = lib.platforms.linux; +} diff --git a/pkgs/servers/hylafaxplus/post-install-check.sh b/pkgs/servers/hylafaxplus/post-install-check.sh new file mode 100644 index 000000000000..2850738edccc --- /dev/null +++ b/pkgs/servers/hylafaxplus/post-install-check.sh @@ -0,0 +1,7 @@ +# check if the package contains all the files needed +for x in faxq faxquit hfaxd faxcron faxqclean faxgetty +do + test -x "$out/spool/bin/$x" +done +test -d "$out/spool/config" +test -f "$out/spool/etc/setup.cache" diff --git a/pkgs/servers/hylafaxplus/post-install.sh b/pkgs/servers/hylafaxplus/post-install.sh new file mode 100644 index 000000000000..ddc7c3f85eda --- /dev/null +++ b/pkgs/servers/hylafaxplus/post-install.sh @@ -0,0 +1,24 @@ +# Parts of the `install` make target don't +# dare to set file modes (or owners), but put the +# needed commands in a new file called `root.sh`. +# We execute the `chmod` commands of +# this script to set execute bits. +sed '/chown/d;/chgrp/d' --in-place root.sh +. root.sh + +# We run `faxsetup` to prepare some config files +# that the admin would have to create otherwise. +# Since `faxsetup` is quite picky about its environment, +# we have to prepare some dummy files. +# `faxsetup` stores today's date in the output files, +# so we employ faketime to simulate a deterministic date. +echo "uucp:x:0" >> "$TMPDIR/passwd.dummy" # dummy uucp user +touch "$out/spool/etc/config.dummy" # dummy modem config +mkdir "$TMPDIR/lock.dummy" # dummy lock dir +"@libfaketime@/bin/faketime" -f "$(date --utc --date=@$SOURCE_DATE_EPOCH '+%F %T')" \ + "@fakeroot@/bin/fakeroot" -- \ + "$out/spool/bin/faxsetup" -with-DIR_LOCKS="$TMPDIR/lock.dummy" -with-PASSWD="$TMPDIR/passwd.dummy" +rm "$out/spool/etc/config.dummy" + +# Ensure all binaries are reachable within the spooling area. +ln --symbolic --target-directory="$out/spool/bin/" "$out/bin/"* diff --git a/pkgs/servers/hylafaxplus/post-patch.sh b/pkgs/servers/hylafaxplus/post-patch.sh new file mode 100644 index 000000000000..6ec5937147e2 --- /dev/null +++ b/pkgs/servers/hylafaxplus/post-patch.sh @@ -0,0 +1,25 @@ +# `configure` (maybe others) set `POSIXLY_CORRECT`, which +# breaks the gcc wrapper script of nixpkgs (maybe others). +# We simply un-export `POSIXLY_CORRECT` after each export so +# its effects don't apply within nixpkgs wrapper scripts. +grep -rlF POSIXLY_CORRECT | xargs \ + sed '/export *POSIXLY_CORRECT/a export -n POSIXLY_CORRECT' -i + +# Replace strange default value for the nobody account. +if test -n "@maxuid@" +then + for f in util/faxadduser.c hfaxd/manifest.h + do + substituteInPlace "$f" --replace 60002 "@maxuid@" + done +fi + +# Replace hardcoded `PATH` variables with proper paths. +# Note: `findutils` is needed for `faxcron`. +substituteInPlace faxcover/edit-faxcover.sh.in \ + --replace 'PATH=/bin' 'PATH="@faxcover_binpath@"' +substituteInPlace etc/faxsetup.sh.in \ + --replace 'PATH=/bin' 'PATH="@faxsetup_binpath@"' + +# Create `config.site` +substitute "@configSite@" config.site --subst-var out diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index 487fcccb1928..80721d63914e 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -3200,6 +3200,8 @@ with pkgs; hwinfo = callPackage ../tools/system/hwinfo { }; + hylafaxplus = callPackage ../servers/hylafaxplus { }; + i2c-tools = callPackage ../os-specific/linux/i2c-tools { }; i2p = callPackage ../tools/networking/i2p {};