at 23.11-pre 7.1 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 5 # cups calls its backends as user `lp` (which is good!), 6 # but cups-pdf wants to be called as `root`, so it can change ownership of files. 7 # We add a suid wrapper and a wrapper script to trick cups into calling the suid wrapper. 8 # Note that a symlink to the suid wrapper alone wouldn't suffice, cups would complain 9 # > File "/nix/store/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-cups-progs/lib/cups/backend/cups-pdf" has insecure permissions (0104554/uid=0/gid=20) 10 11 # wrapper script that redirects calls to the suid wrapper 12 cups-pdf-wrapper = pkgs.writeTextFile { 13 name = "${pkgs.cups-pdf-to-pdf.name}-wrapper.sh"; 14 executable = true; 15 destination = "/lib/cups/backend/cups-pdf"; 16 checkPhase = '' 17 ${pkgs.stdenv.shellDryRun} "$target" 18 ${lib.getExe pkgs.shellcheck} "$target" 19 ''; 20 text = '' 21 #! ${pkgs.runtimeShell} 22 exec "${config.security.wrapperDir}/cups-pdf" "$@" 23 ''; 24 }; 25 26 # wrapped cups-pdf package that uses the suid wrapper 27 cups-pdf-wrapped = pkgs.buildEnv { 28 name = "${pkgs.cups-pdf-to-pdf.name}-wrapped"; 29 # using the wrapper as first path ensures it is used 30 paths = [ cups-pdf-wrapper pkgs.cups-pdf-to-pdf ]; 31 ignoreCollisions = true; 32 }; 33 34 instanceSettings = name: { 35 freeformType = with lib.types; nullOr (oneOf [ int str path package ]); 36 # override defaults: 37 # inject instance name into paths, 38 # also avoid conflicts between user names and special dirs 39 options.Out = lib.mkOption { 40 type = with lib.types; nullOr singleLineStr; 41 default = "/var/spool/cups-pdf-${name}/users/\${USER}"; 42 defaultText = "/var/spool/cups-pdf-{instance-name}/users/\${USER}"; 43 example = "\${HOME}/cups-pdf"; 44 description = lib.mdDoc '' 45 output directory; 46 `''${HOME}` will be expanded to the user's home directory, 47 `''${USER}` will be expanded to the user name. 48 ''; 49 }; 50 options.AnonDirName = lib.mkOption { 51 type = with lib.types; nullOr singleLineStr; 52 default = "/var/spool/cups-pdf-${name}/anonymous"; 53 defaultText = "/var/spool/cups-pdf-{instance-name}/anonymous"; 54 example = "/var/lib/cups-pdf"; 55 description = lib.mdDoc "path for anonymously created PDF files"; 56 }; 57 options.Spool = lib.mkOption { 58 type = with lib.types; nullOr singleLineStr; 59 default = "/var/spool/cups-pdf-${name}/spool"; 60 defaultText = "/var/spool/cups-pdf-{instance-name}/spool"; 61 example = "/var/lib/cups-pdf"; 62 description = lib.mdDoc "spool directory"; 63 }; 64 options.Anonuser = lib.mkOption { 65 type = lib.types.singleLineStr; 66 default = "root"; 67 description = lib.mdDoc '' 68 User for anonymous PDF creation. 69 An empty string disables this feature. 70 ''; 71 }; 72 options.GhostScript = lib.mkOption { 73 type = with lib.types; nullOr path; 74 default = lib.getExe pkgs.ghostscript; 75 defaultText = lib.literalExpression "lib.getExe pkgs.ghostscript"; 76 example = lib.literalExpression ''''${pkgs.ghostscript}/bin/ps2pdf''; 77 description = lib.mdDoc "location of GhostScript binary"; 78 }; 79 }; 80 81 instanceConfig = { name, config, ... }: { 82 options = { 83 enable = (lib.mkEnableOption (lib.mdDoc "this cups-pdf instance")) // { default = true; }; 84 installPrinter = (lib.mkEnableOption (lib.mdDoc '' 85 a CUPS printer queue for this instance. 86 The queue will be named after the instance and will use the {file}`CUPS-PDF_opt.ppd` ppd file. 87 If this is disabled, you need to add the queue yourself to use the instance 88 '')) // { default = true; }; 89 confFileText = lib.mkOption { 90 type = lib.types.lines; 91 description = lib.mdDoc '' 92 This will contain the contents of {file}`cups-pdf.conf` for this instance, derived from {option}`settings`. 93 You can use this option to append text to the file. 94 ''; 95 }; 96 settings = lib.mkOption { 97 type = lib.types.submodule (instanceSettings name); 98 default = {}; 99 example = { 100 Out = "\${HOME}/cups-pdf"; 101 UserUMask = "0033"; 102 }; 103 description = lib.mdDoc '' 104 Settings for a cups-pdf instance, see the descriptions in the template config file in the cups-pdf package. 105 The key value pairs declared here will be translated into proper key value pairs for {file}`cups-pdf.conf`. 106 Setting a value to `null` disables the option and removes it from the file. 107 ''; 108 }; 109 }; 110 config.confFileText = lib.pipe config.settings [ 111 (lib.filterAttrs (key: value: value != null)) 112 (lib.mapAttrs (key: builtins.toString)) 113 (lib.mapAttrsToList (key: value: "${key} ${value}\n")) 114 lib.concatStrings 115 ]; 116 }; 117 118 cupsPdfCfg = config.services.printing.cups-pdf; 119 120 copyConfigFileCmds = lib.pipe cupsPdfCfg.instances [ 121 (lib.filterAttrs (name: lib.getAttr "enable")) 122 (lib.mapAttrs (name: lib.getAttr "confFileText")) 123 (lib.mapAttrs (name: pkgs.writeText "cups-pdf-${name}.conf")) 124 (lib.mapAttrsToList (name: confFile: "ln --symbolic --no-target-directory ${confFile} /var/lib/cups/cups-pdf-${name}.conf\n")) 125 lib.concatStrings 126 ]; 127 128 printerSettings = lib.pipe cupsPdfCfg.instances [ 129 (lib.filterAttrs (name: lib.getAttr "enable")) 130 (lib.filterAttrs (name: lib.getAttr "installPrinter")) 131 (lib.mapAttrsToList (name: instance: (lib.mapAttrs (key: lib.mkDefault) { 132 inherit name; 133 model = "CUPS-PDF_opt.ppd"; 134 deviceUri = "cups-pdf:/${name}"; 135 description = "virtual printer for cups-pdf instance ${name}"; 136 location = instance.settings.Out; 137 }))) 138 ]; 139 140in 141 142{ 143 144 options.services.printing.cups-pdf = { 145 enable = lib.mkEnableOption (lib.mdDoc '' 146 the cups-pdf virtual pdf printer backend. 147 By default, this will install a single printer `pdf`. 148 but this can be changed/extended with {option}`services.printing.cups-pdf.instances` 149 ''); 150 instances = lib.mkOption { 151 type = lib.types.attrsOf (lib.types.submodule instanceConfig); 152 default.pdf = {}; 153 example.pdf.settings = { 154 Out = "\${HOME}/cups-pdf"; 155 UserUMask = "0033"; 156 }; 157 description = lib.mdDoc '' 158 Permits to raise one or more cups-pdf instances. 159 Each instance is named by an attribute name, and the attribute's values control the instance' configuration. 160 ''; 161 }; 162 }; 163 164 config = lib.mkIf cupsPdfCfg.enable { 165 services.printing.enable = true; 166 services.printing.drivers = [ cups-pdf-wrapped ]; 167 hardware.printers.ensurePrinters = printerSettings; 168 # the cups module will install the default config file, 169 # but we don't need it and it would confuse cups-pdf 170 systemd.services.cups.preStart = lib.mkAfter '' 171 rm -f /var/lib/cups/cups-pdf.conf 172 ${copyConfigFileCmds} 173 ''; 174 security.wrappers.cups-pdf = { 175 group = "lp"; 176 owner = "root"; 177 permissions = "+r,ug+x"; 178 setuid = true; 179 source = "${pkgs.cups-pdf-to-pdf}/lib/cups/backend/cups-pdf"; 180 }; 181 }; 182 183 meta.maintainers = [ lib.maintainers.yarny ]; 184 185}