vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / printing / cupsd.nix
bloba1fb0b3951e45371aa81e332269e6497b6aa78f0
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
7   inherit (pkgs) cups-pk-helper cups-filters xdg-utils;
9   cfg = config.services.printing;
10   cups = cfg.package;
12   polkitEnabled = config.security.polkit.enable;
14   additionalBackends = pkgs.runCommand "additional-cups-backends" {
15       preferLocalBuild = true;
16     } ''
17       mkdir -p $out
18       if [ ! -e ${cups.out}/lib/cups/backend/smb ]; then
19         mkdir -p $out/lib/cups/backend
20         ln -sv ${pkgs.samba}/bin/smbspool $out/lib/cups/backend/smb
21       fi
23       # Provide support for printing via HTTPS.
24       if [ ! -e ${cups.out}/lib/cups/backend/https ]; then
25         mkdir -p $out/lib/cups/backend
26         ln -sv ${cups.out}/lib/cups/backend/ipp $out/lib/cups/backend/https
27       fi
28     '';
30   # Here we can enable additional backends, filters, etc. that are not
31   # part of CUPS itself, e.g. the SMB backend is part of Samba.  Since
32   # we can't update ${cups.out}/lib/cups itself, we create a symlink tree
33   # here and add the additional programs.  The ServerBin directive in
34   # cups-files.conf tells cupsd to use this tree.
35   bindir = pkgs.buildEnv {
36     name = "cups-progs";
37     paths =
38       [ cups.out additionalBackends cups-filters pkgs.ghostscript ]
39       ++ cfg.drivers;
40     pathsToLink = [ "/lib" "/share/cups" "/bin" ];
41     postBuild = cfg.bindirCmds;
42     ignoreCollisions = true;
43   };
45   writeConf = name: text: pkgs.writeTextFile {
46     inherit name text;
47     destination = "/etc/cups/${name}";
48   };
50   cupsFilesFile = writeConf "cups-files.conf" ''
51     SystemGroup root wheel
53     ServerBin ${bindir}/lib/cups
54     DataDir ${bindir}/share/cups
55     DocumentRoot ${cups.out}/share/doc/cups
57     AccessLog syslog
58     ErrorLog syslog
59     PageLog syslog
61     TempDir ${cfg.tempDir}
63     SetEnv PATH /var/lib/cups/path/lib/cups/filter:/var/lib/cups/path/bin
65     # User and group used to run external programs, including
66     # those that actually send the job to the printer.  Note that
67     # Udev sets the group of printer devices to `lp', so we want
68     # these programs to run as `lp' as well.
69     User cups
70     Group lp
72     ${cfg.extraFilesConf}
73   '';
75   cupsdFile = writeConf "cupsd.conf" ''
76     ${concatMapStrings (addr: ''
77       Listen ${addr}
78     '') cfg.listenAddresses}
79     Listen /run/cups/cups.sock
81     DefaultShared ${if cfg.defaultShared then "Yes" else "No"}
83     Browsing ${if cfg.browsing then "Yes" else "No"}
85     WebInterface ${if cfg.webInterface then "Yes" else "No"}
87     LogLevel ${cfg.logLevel}
89     ${cfg.extraConf}
90   '';
92   browsedFile = writeConf "cups-browsed.conf" cfg.browsedConf;
94   rootdir = pkgs.buildEnv {
95     name = "cups-progs";
96     paths = [
97       cupsFilesFile
98       cupsdFile
99       (writeConf "client.conf" cfg.clientConf)
100       (writeConf "snmp.conf" cfg.snmpConf)
101     ] ++ optional cfg.browsed.enable browsedFile
102       ++ cfg.drivers;
103     pathsToLink = [ "/etc/cups" ];
104     ignoreCollisions = true;
105   };
107   filterGutenprint = filter (pkg: pkg.meta.isGutenprint or false == true);
108   containsGutenprint = pkgs: length (filterGutenprint pkgs) > 0;
109   getGutenprint = pkgs: head (filterGutenprint pkgs);
111   parsePorts = addresses: let
112     splitAddress = addr: strings.splitString ":" addr;
113     extractPort = addr: builtins.foldl' (a: b: b) "" (splitAddress addr);
114   in
115     builtins.map (address: strings.toInt (extractPort address)) addresses;
121   imports = [
122     (mkChangedOptionModule [ "services" "printing" "gutenprint" ] [ "services" "printing" "drivers" ]
123       (config:
124         let enabled = getAttrFromPath [ "services" "printing" "gutenprint" ] config;
125         in if enabled then [ pkgs.gutenprint ] else [ ]))
126     (mkRemovedOptionModule [ "services" "printing" "cupsFilesConf" ] "")
127     (mkRemovedOptionModule [ "services" "printing" "cupsdConf" ] "")
128   ];
130   ###### interface
132   options = {
133     services.printing = {
135       enable = mkOption {
136         type = types.bool;
137         default = false;
138         description = ''
139           Whether to enable printing support through the CUPS daemon.
140         '';
141       };
143       package = lib.mkPackageOption pkgs "cups" {};
145       stateless = mkOption {
146         type = types.bool;
147         default = false;
148         description = ''
149           If set, all state directories relating to CUPS will be removed on
150           startup of the service.
151         '';
152       };
154       startWhenNeeded = mkOption {
155         type = types.bool;
156         default = true;
157         description = ''
158           If set, CUPS is socket-activated; that is,
159           instead of having it permanently running as a daemon,
160           systemd will start it on the first incoming connection.
161         '';
162       };
164       listenAddresses = mkOption {
165         type = types.listOf types.str;
166         default = [ "localhost:631" ];
167         example = [ "*:631" ];
168         description = ''
169           A list of addresses and ports on which to listen.
170         '';
171       };
173       allowFrom = mkOption {
174         type = types.listOf types.str;
175         default = [ "localhost" ];
176         example = [ "all" ];
177         apply = concatMapStringsSep "\n" (x: "Allow ${x}");
178         description = ''
179           From which hosts to allow unconditional access.
180         '';
181       };
183       openFirewall = mkOption {
184         type = types.bool;
185         default = false;
186         description = ''
187           Whether to open the firewall for TCP ports specified in
188           listenAddresses option.
189         '';
190       };
192       bindirCmds = mkOption {
193         type = types.lines;
194         internal = true;
195         default = "";
196         description = ''
197           Additional commands executed while creating the directory
198           containing the CUPS server binaries.
199         '';
200       };
202       defaultShared = mkOption {
203         type = types.bool;
204         default = false;
205         description = ''
206           Specifies whether local printers are shared by default.
207         '';
208       };
210       browsing = mkOption {
211         type = types.bool;
212         default = false;
213         description = ''
214           Specifies whether shared printers are advertised.
215         '';
216       };
218       webInterface = mkOption {
219         type = types.bool;
220         default = true;
221         description = ''
222           Specifies whether the web interface is enabled.
223         '';
224       };
226       logLevel = mkOption {
227         type = types.str;
228         default = "info";
229         example = "debug";
230         description = ''
231           Specifies the cupsd logging verbosity.
232         '';
233       };
235       extraFilesConf = mkOption {
236         type = types.lines;
237         default = "";
238         description = ''
239           Extra contents of the configuration file of the CUPS daemon
240           ({file}`cups-files.conf`).
241         '';
242       };
244       extraConf = mkOption {
245         type = types.lines;
246         default = "";
247         example =
248           ''
249             BrowsePoll cups.example.com
250             MaxCopies 42
251           '';
252         description = ''
253           Extra contents of the configuration file of the CUPS daemon
254           ({file}`cupsd.conf`).
255         '';
256       };
258       clientConf = mkOption {
259         type = types.lines;
260         default = "";
261         example =
262           ''
263             ServerName server.example.com
264             Encryption Never
265           '';
266         description = ''
267           The contents of the client configuration.
268           ({file}`client.conf`)
269         '';
270       };
272       browsed.enable = mkOption {
273         type = types.bool;
274         default = config.services.avahi.enable;
275         defaultText = literalExpression "config.services.avahi.enable";
276         description = ''
277           Whether to enable the CUPS Remote Printer Discovery (browsed) daemon.
278         '';
279       };
281       browsedConf = mkOption {
282         type = types.lines;
283         default = "";
284         example =
285           ''
286             BrowsePoll cups.example.com
287           '';
288         description = ''
289           The contents of the configuration. file of the CUPS Browsed daemon
290           ({file}`cups-browsed.conf`)
291         '';
292       };
294       snmpConf = mkOption {
295         type = types.lines;
296         default = ''
297           Address @LOCAL
298         '';
299         description = ''
300           The contents of {file}`/etc/cups/snmp.conf`. See "man
301           cups-snmp.conf" for a complete description.
302         '';
303       };
305       drivers = mkOption {
306         type = types.listOf types.path;
307         default = [];
308         example = literalExpression "with pkgs; [ gutenprint hplip splix ]";
309         description = ''
310           CUPS drivers to use. Drivers provided by CUPS, cups-filters,
311           Ghostscript and Samba are added unconditionally. If this list contains
312           Gutenprint (i.e. a derivation with
313           `meta.isGutenprint = true`) the PPD files in
314           {file}`/var/lib/cups/ppd` will be updated automatically
315           to avoid errors due to incompatible versions.
316         '';
317       };
319       tempDir = mkOption {
320         type = types.path;
321         default = "/tmp";
322         example = "/tmp/cups";
323         description = ''
324           CUPSd temporary directory.
325         '';
326       };
327     };
329   };
332   ###### implementation
334   config = mkIf config.services.printing.enable {
336     users.users.cups =
337       { uid = config.ids.uids.cups;
338         group = "lp";
339         description = "CUPS printing services";
340       };
342     # We need xdg-open (part of xdg-utils) for the desktop-file to proper open the users default-browser when opening "Manage Printing"
343     # https://github.com/NixOS/nixpkgs/pull/237994#issuecomment-1597510969
344     environment.systemPackages = [ cups.out xdg-utils ] ++ optional polkitEnabled cups-pk-helper;
345     environment.etc.cups.source = "/var/lib/cups";
347     services.dbus.packages = [ cups.out ] ++ optional polkitEnabled cups-pk-helper;
348     services.udev.packages = cfg.drivers;
350     # Allow passwordless printer admin for members of wheel group
351     security.polkit.extraConfig = mkIf polkitEnabled ''
352       polkit.addRule(function(action, subject) {
353           if (action.id == "org.opensuse.cupspkhelper.mechanism.all-edit" &&
354               subject.isInGroup("wheel")){
355               return polkit.Result.YES;
356           }
357       });
358     '';
360     # Cups uses libusb to talk to printers, and does not use the
361     # linux kernel driver. If the driver is not in a black list, it
362     # gets loaded, and then cups cannot access the printers.
363     boot.blacklistedKernelModules = [ "usblp" ];
365     # Some programs like print-manager rely on this value to get
366     # printer test pages.
367     environment.sessionVariables.CUPS_DATADIR = "${bindir}/share/cups";
369     systemd.packages = [ cups.out ];
371     systemd.sockets.cups = mkIf cfg.startWhenNeeded {
372       wantedBy = [ "sockets.target" ];
373       listenStreams = [ "" "/run/cups/cups.sock" ]
374         ++ map (x: replaceStrings ["localhost"] ["127.0.0.1"] (removePrefix "*:" x)) cfg.listenAddresses;
375     };
377     systemd.services.cups =
378       { wantedBy = optionals (!cfg.startWhenNeeded) [ "multi-user.target" ];
379         wants = [ "network.target" ];
380         after = [ "network.target" ];
382         path = [ cups.out ];
384         preStart = lib.optionalString cfg.stateless ''
385           rm -rf /var/cache/cups /var/lib/cups /var/spool/cups
386         '' + ''
387             mkdir -m 0700 -p /var/cache/cups
388             mkdir -m 0700 -p /var/spool/cups
389             mkdir -m 0755 -p ${cfg.tempDir}
391             mkdir -m 0755 -p /var/lib/cups
392             # While cups will automatically create self-signed certificates if accessed via TLS,
393             # this directory to store the certificates needs to be created manually.
394             mkdir -m 0700 -p /var/lib/cups/ssl
396             # Backwards compatibility
397             if [ ! -L /etc/cups ]; then
398               mv /etc/cups/* /var/lib/cups
399               rmdir /etc/cups
400               ln -s /var/lib/cups /etc/cups
401             fi
402             # First, clean existing symlinks
403             if [ -n "$(ls /var/lib/cups)" ]; then
404               for i in /var/lib/cups/*; do
405                 [ -L "$i" ] && rm "$i"
406               done
407             fi
408             # Then, populate it with static files
409             cd ${rootdir}/etc/cups
410             for i in *; do
411               [ ! -e "/var/lib/cups/$i" ] && ln -s "${rootdir}/etc/cups/$i" "/var/lib/cups/$i"
412             done
414             #update path reference
415             [ -L /var/lib/cups/path ] && \
416               rm /var/lib/cups/path
417             [ ! -e /var/lib/cups/path ] && \
418               ln -s ${bindir} /var/lib/cups/path
420             ${optionalString (containsGutenprint cfg.drivers) ''
421               if [ -d /var/lib/cups/ppd ]; then
422                 ${getGutenprint cfg.drivers}/bin/cups-genppdupdate -p /var/lib/cups/ppd
423               fi
424             ''}
425           '';
427           serviceConfig.PrivateTmp = true;
428       };
430     systemd.services.cups-browsed = mkIf cfg.browsed.enable
431       { description = "CUPS Remote Printer Discovery";
433         wantedBy = [ "multi-user.target" ];
434         wants = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
435         bindsTo = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
436         partOf = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
437         after = [ "avahi-daemon.service" ] ++ optional (!cfg.startWhenNeeded) "cups.service";
439         path = [ cups ];
441         serviceConfig.ExecStart = "${cups-filters}/bin/cups-browsed";
443         restartTriggers = [ browsedFile ];
444       };
446     services.printing.extraConf =
447       ''
448         DefaultAuthType Basic
450         <Location />
451           Order allow,deny
452           ${cfg.allowFrom}
453         </Location>
455         <Location /admin>
456           Order allow,deny
457           ${cfg.allowFrom}
458         </Location>
460         <Location /admin/conf>
461           AuthType Basic
462           Require user @SYSTEM
463           Order allow,deny
464           ${cfg.allowFrom}
465         </Location>
467         <Policy default>
468           <Limit Send-Document Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs Set-Job-Attributes Create-Job-Subscription Renew-Subscription Cancel-Subscription Get-Notifications Reprocess-Job Cancel-Current-Job Suspend-Current-Job Resume-Job CUPS-Move-Job>
469             Require user @OWNER @SYSTEM
470             Order deny,allow
471           </Limit>
473           <Limit Pause-Printer Resume-Printer Set-Printer-Attributes Enable-Printer Disable-Printer Pause-Printer-After-Current-Job Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer Activate-Printer Restart-Printer Shutdown-Printer Startup-Printer Promote-Job Schedule-Job-After CUPS-Add-Printer CUPS-Delete-Printer CUPS-Add-Class CUPS-Delete-Class CUPS-Accept-Jobs CUPS-Reject-Jobs CUPS-Set-Default>
474             AuthType Basic
475             Require user @SYSTEM
476             Order deny,allow
477           </Limit>
479           <Limit Cancel-Job CUPS-Authenticate-Job>
480             Require user @OWNER @SYSTEM
481             Order deny,allow
482           </Limit>
484           <Limit All>
485             Order deny,allow
486           </Limit>
487         </Policy>
488       '';
490     security.pam.services.cups = {};
492     networking.firewall = let
493       listenPorts = parsePorts cfg.listenAddresses;
494     in mkIf cfg.openFirewall {
495       allowedTCPPorts = listenPorts;
496     };
498   };
500   meta.maintainers = with lib.maintainers; [ matthewbauer ];