Commit | Line | Data |
---|---|---|
f2ec23d1 AW |
1 | ;;; GNU Guix --- Functional package management for GNU |
2 | ;;; Copyright © 2016 Andy Wingo <wingo@pobox.com> | |
e57bd0be | 3 | ;;; Copyright © 2017 Clément Lassieur <clement@lassieur.org> |
f2ec23d1 AW |
4 | ;;; |
5 | ;;; This file is part of GNU Guix. | |
6 | ;;; | |
7 | ;;; GNU Guix is free software; you can redistribute it and/or modify it | |
8 | ;;; under the terms of the GNU General Public License as published by | |
9 | ;;; the Free Software Foundation; either version 3 of the License, or (at | |
10 | ;;; your option) any later version. | |
11 | ;;; | |
12 | ;;; GNU Guix is distributed in the hope that it will be useful, but | |
13 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | ;;; GNU General Public License for more details. | |
16 | ;;; | |
17 | ;;; You should have received a copy of the GNU General Public License | |
18 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | |
19 | ||
20 | (define-module (gnu services cups) | |
21 | #:use-module (gnu services) | |
22 | #:use-module (gnu services shepherd) | |
5305ed20 | 23 | #:use-module (gnu services configuration) |
f2ec23d1 AW |
24 | #:use-module (gnu system shadow) |
25 | #:use-module (gnu packages admin) | |
26 | #:use-module (gnu packages cups) | |
27 | #:use-module (gnu packages tls) | |
28 | #:use-module (guix packages) | |
29 | #:use-module (guix records) | |
30 | #:use-module (guix gexp) | |
f2ec23d1 AW |
31 | #:use-module (ice-9 match) |
32 | #:use-module ((srfi srfi-1) #:select (append-map)) | |
5305ed20 | 33 | #:export (cups-service-type |
f2ec23d1 AW |
34 | cups-configuration |
35 | opaque-cups-configuration | |
36 | ||
37 | files-configuration | |
38 | policy-configuration | |
39 | location-access-control | |
40 | operation-access-control | |
41 | method-access-control)) | |
42 | ||
43 | ;;; Commentary: | |
44 | ;;; | |
45 | ;;; Service defininition for the CUPS printing system. | |
46 | ;;; | |
47 | ;;; Code: | |
48 | ||
f2ec23d1 AW |
49 | (define %cups-accounts |
50 | (list (user-group (name "lp") (system? #t)) | |
51 | (user-group (name "lpadmin") (system? #t)) | |
52 | (user-account | |
53 | (name "lp") | |
54 | (group "lp") | |
55 | (system? #t) | |
56 | (comment "System user for invoking printing helper programs") | |
57 | (home-directory "/var/empty") | |
58 | (shell (file-append shadow "/sbin/nologin"))))) | |
59 | ||
720cb10c CL |
60 | (define (uglify-field-name field-name) |
61 | (let ((str (symbol->string field-name))) | |
62 | (string-concatenate | |
63 | (map string-titlecase | |
64 | (string-split (if (string-suffix? "?" str) | |
65 | (substring str 0 (1- (string-length str))) | |
66 | str) | |
67 | #\-))))) | |
68 | ||
69 | (define (serialize-field field-name val) | |
70 | (format #t "~a ~a\n" (uglify-field-name field-name) val)) | |
71 | ||
72 | (define (serialize-string field-name val) | |
73 | (serialize-field field-name val)) | |
74 | ||
f2ec23d1 AW |
75 | (define (multiline-string-list? val) |
76 | (and (list? val) | |
77 | (and-map (lambda (x) | |
78 | (and (string? x) (not (string-index x #\space)))) | |
79 | val))) | |
80 | (define (serialize-multiline-string-list field-name val) | |
81 | (for-each (lambda (str) (serialize-field field-name str)) val)) | |
82 | ||
720cb10c CL |
83 | (define (space-separated-string-list? val) |
84 | (and (list? val) | |
85 | (and-map (lambda (x) | |
86 | (and (string? x) (not (string-index x #\space)))) | |
87 | val))) | |
88 | (define (serialize-space-separated-string-list field-name val) | |
89 | (serialize-field field-name (string-join val " "))) | |
90 | ||
f2ec23d1 AW |
91 | (define (space-separated-symbol-list? val) |
92 | (and (list? val) (and-map symbol? val))) | |
93 | (define (serialize-space-separated-symbol-list field-name val) | |
94 | (serialize-field field-name (string-join (map symbol->string val) " "))) | |
95 | ||
720cb10c CL |
96 | (define (file-name? val) |
97 | (and (string? val) | |
98 | (string-prefix? "/" val))) | |
99 | (define (serialize-file-name field-name val) | |
100 | (serialize-string field-name val)) | |
101 | ||
102 | (define (serialize-boolean field-name val) | |
103 | (serialize-string field-name (if val "yes" "no"))) | |
104 | ||
f2ec23d1 AW |
105 | (define (non-negative-integer? val) |
106 | (and (exact-integer? val) (not (negative? val)))) | |
107 | (define (serialize-non-negative-integer field-name val) | |
108 | (serialize-field field-name val)) | |
109 | ||
110 | (define-syntax define-enumerated-field-type | |
111 | (lambda (x) | |
112 | (define (id-append ctx . parts) | |
113 | (datum->syntax ctx (apply symbol-append (map syntax->datum parts)))) | |
114 | (syntax-case x () | |
115 | ((_ name (option ...)) | |
116 | #`(begin | |
117 | (define (#,(id-append #'name #'name #'?) x) | |
118 | (memq x '(option ...))) | |
119 | (define (#,(id-append #'name #'serialize- #'name) field-name val) | |
120 | (serialize-field field-name val))))))) | |
121 | ||
122 | (define-enumerated-field-type access-log-level | |
123 | (config actions all)) | |
124 | (define-enumerated-field-type browse-local-protocols | |
125 | (all dnssd none)) | |
126 | (define-enumerated-field-type default-auth-type | |
127 | (Basic Negotiate)) | |
128 | (define-enumerated-field-type default-encryption | |
129 | (Never IfRequested Required)) | |
130 | (define-enumerated-field-type error-policy | |
131 | (abort-job retry-job retry-this-job stop-printer)) | |
132 | (define-enumerated-field-type log-level | |
133 | (none emerg alert crit error warn notice info debug debug2)) | |
134 | (define-enumerated-field-type log-time-format | |
135 | (standard usecs)) | |
136 | (define-enumerated-field-type server-tokens | |
137 | (None ProductOnly Major Minor Minimal OS Full)) | |
138 | (define-enumerated-field-type method | |
139 | (DELETE GET HEAD OPTIONS POST PUT TRACE)) | |
140 | (define-enumerated-field-type sandboxing | |
141 | (relaxed strict)) | |
142 | ||
143 | (define (method-list? val) | |
144 | (and (list? val) (and-map method? val))) | |
145 | (define (serialize-method-list field-name val) | |
146 | (serialize-field field-name (string-join (map symbol->string val) " "))) | |
147 | ||
148 | (define (host-name-lookups? val) | |
149 | (memq val '(#f #t 'double))) | |
150 | (define (serialize-host-name-lookups field-name val) | |
151 | (serialize-field field-name | |
152 | (match val (#f "No") (#t "Yes") ('double "Double")))) | |
153 | ||
154 | (define (host-name-list-or-*? x) | |
155 | (or (eq? x '*) | |
156 | (and (list? x) (and-map string? x)))) | |
157 | (define (serialize-host-name-list-or-* field-name val) | |
158 | (serialize-field field-name (match val | |
159 | ('* '*) | |
160 | (names (string-join names " "))))) | |
161 | ||
162 | (define (boolean-or-non-negative-integer? x) | |
163 | (or (boolean? x) (non-negative-integer? x))) | |
164 | (define (serialize-boolean-or-non-negative-integer field-name x) | |
165 | (if (boolean? x) | |
166 | (serialize-boolean field-name x) | |
167 | (serialize-non-negative-integer field-name x))) | |
168 | ||
169 | (define (ssl-options? x) | |
170 | (and (list? x) | |
171 | (and-map (lambda (elt) (memq elt '(AllowRC4 AllowSSL3))) x))) | |
172 | (define (serialize-ssl-options field-name val) | |
173 | (serialize-field field-name | |
174 | (match val | |
175 | (() "None") | |
176 | (opts (string-join (map symbol->string opts) " "))))) | |
177 | ||
178 | (define (serialize-access-control x) | |
179 | (display x) | |
180 | (newline)) | |
181 | (define (serialize-access-control-list field-name val) | |
182 | (for-each serialize-access-control val)) | |
183 | (define (access-control-list? val) | |
184 | (and (list? val) (and-map string? val))) | |
185 | ||
186 | (define-configuration operation-access-control | |
187 | (operations | |
188 | (space-separated-symbol-list '()) | |
189 | "IPP operations to which this access control applies.") | |
190 | (access-controls | |
191 | (access-control-list '()) | |
192 | "Access control directives, as a list of strings. Each string should be one directive, such as \"Order allow,deny\".")) | |
193 | ||
194 | (define-configuration method-access-control | |
195 | (reverse? | |
196 | (boolean #f) | |
197 | "If @code{#t}, apply access controls to all methods except the listed | |
198 | methods. Otherwise apply to only the listed methods.") | |
199 | (methods | |
200 | (method-list '()) | |
201 | "Methods to which this access control applies.") | |
202 | (access-controls | |
203 | (access-control-list '()) | |
204 | "Access control directives, as a list of strings. Each string should be one directive, such as \"Order allow,deny\".")) | |
205 | ||
206 | (define (serialize-operation-access-control x) | |
207 | (format #t "<Limit ~a>\n" | |
208 | (string-join (map symbol->string | |
209 | (operation-access-control-operations x)) " ")) | |
210 | (serialize-configuration | |
211 | x | |
212 | (filter (lambda (field) | |
213 | (not (eq? (configuration-field-name field) 'operations))) | |
214 | operation-access-control-fields)) | |
215 | (format #t "</Limit>\n")) | |
216 | ||
217 | (define (serialize-method-access-control x) | |
218 | (let ((limit (if (method-access-control-reverse? x) "LimitExcept" "Limit"))) | |
219 | (format #t "<~a ~a>\n" limit | |
220 | (string-join (map symbol->string | |
221 | (method-access-control-methods x)) " ")) | |
222 | (serialize-configuration | |
223 | x | |
224 | (filter (lambda (field) | |
225 | (case (configuration-field-name field) | |
226 | ((reverse? methods) #f) | |
227 | (else #t))) | |
228 | method-access-control-fields)) | |
229 | (format #t "</~a>\n" limit))) | |
230 | ||
231 | (define (operation-access-control-list? val) | |
232 | (and (list? val) (and-map operation-access-control? val))) | |
233 | (define (serialize-operation-access-control-list field-name val) | |
234 | (for-each serialize-operation-access-control val)) | |
235 | ||
236 | (define (method-access-control-list? val) | |
237 | (and (list? val) (and-map method-access-control? val))) | |
238 | (define (serialize-method-access-control-list field-name val) | |
239 | (for-each serialize-method-access-control val)) | |
240 | ||
241 | (define-configuration location-access-control | |
242 | (path | |
5305ed20 | 243 | (file-name (configuration-missing-field 'location-access-control 'path)) |
f2ec23d1 AW |
244 | "Specifies the URI path to which the access control applies.") |
245 | (access-controls | |
246 | (access-control-list '()) | |
247 | "Access controls for all access to this path, in the same format as the | |
248 | @code{access-controls} of @code{operation-access-control}.") | |
249 | (method-access-controls | |
250 | (method-access-control-list '()) | |
251 | "Access controls for method-specific access to this path.")) | |
252 | ||
253 | (define (serialize-location-access-control x) | |
254 | (format #t "<Location ~a>\n" (location-access-control-path x)) | |
255 | (serialize-configuration | |
256 | x | |
257 | (filter (lambda (field) | |
258 | (not (eq? (configuration-field-name field) 'path))) | |
259 | location-access-control-fields)) | |
260 | (format #t "</Location>\n")) | |
261 | ||
262 | (define (location-access-control-list? val) | |
263 | (and (list? val) (and-map location-access-control? val))) | |
264 | (define (serialize-location-access-control-list field-name val) | |
265 | (for-each serialize-location-access-control val)) | |
266 | ||
267 | (define-configuration policy-configuration | |
268 | (name | |
5305ed20 | 269 | (string (configuration-missing-field 'policy-configuration 'name)) |
f2ec23d1 AW |
270 | "Name of the policy.") |
271 | (job-private-access | |
272 | (string "@OWNER @SYSTEM") | |
273 | "Specifies an access list for a job's private values. @code{@@ACL} maps to | |
274 | the printer's requesting-user-name-allowed or requesting-user-name-denied | |
275 | values. @code{@@OWNER} maps to the job's owner. @code{@@SYSTEM} maps to the | |
276 | groups listed for the @code{system-group} field of the @code{files-config} | |
277 | configuration, which is reified into the @code{cups-files.conf(5)} file. | |
278 | Other possible elements of the access list include specific user names, and | |
279 | @code{@@@var{group}} to indicate members of a specific group. The access list | |
280 | may also be simply @code{all} or @code{default}.") | |
281 | (job-private-values | |
282 | (string (string-join '("job-name" "job-originating-host-name" | |
283 | "job-originating-user-name" "phone"))) | |
284 | "Specifies the list of job values to make private, or @code{all}, | |
285 | @code{default}, or @code{none}.") | |
286 | ||
287 | (subscription-private-access | |
288 | (string "@OWNER @SYSTEM") | |
289 | "Specifies an access list for a subscription's private values. | |
290 | @code{@@ACL} maps to the printer's requesting-user-name-allowed or | |
291 | requesting-user-name-denied values. @code{@@OWNER} maps to the job's owner. | |
292 | @code{@@SYSTEM} maps to the groups listed for the @code{system-group} field of | |
293 | the @code{files-config} configuration, which is reified into the | |
294 | @code{cups-files.conf(5)} file. Other possible elements of the access list | |
295 | include specific user names, and @code{@@@var{group}} to indicate members of a | |
296 | specific group. The access list may also be simply @code{all} or | |
297 | @code{default}.") | |
298 | (subscription-private-values | |
299 | (string (string-join '("notify-events" "notify-pull-method" | |
300 | "notify-recipient-uri" "notify-subscriber-user-name" | |
301 | "notify-user-data") | |
302 | " ")) | |
303 | "Specifies the list of job values to make private, or @code{all}, | |
304 | @code{default}, or @code{none}.") | |
305 | ||
306 | (access-controls | |
307 | (operation-access-control-list '()) | |
308 | "Access control by IPP operation.")) | |
309 | ||
310 | (define (serialize-policy-configuration x) | |
311 | (format #t "<Policy ~a>\n" (policy-configuration-name x)) | |
312 | (serialize-configuration | |
313 | x | |
314 | (filter (lambda (field) | |
315 | (not (eq? (configuration-field-name field) 'name))) | |
316 | policy-configuration-fields)) | |
317 | (format #t "</Policy>\n")) | |
318 | ||
319 | (define (policy-configuration-list? x) | |
320 | (and (list? x) (and-map policy-configuration? x))) | |
321 | (define (serialize-policy-configuration-list field-name x) | |
322 | (for-each serialize-policy-configuration x)) | |
323 | ||
324 | (define (log-location? x) | |
325 | (or (file-name? x) | |
326 | (eq? x 'stderr) | |
327 | (eq? x 'syslog))) | |
328 | (define (serialize-log-location field-name x) | |
329 | (if (string? x) | |
330 | (serialize-file-name field-name x) | |
331 | (serialize-field field-name x))) | |
332 | ||
333 | (define-configuration files-configuration | |
334 | (access-log | |
335 | (log-location "/var/log/cups/access_log") | |
336 | "Defines the access log filename. Specifying a blank filename disables | |
337 | access log generation. The value @code{stderr} causes log entries to be sent | |
338 | to the standard error file when the scheduler is running in the foreground, or | |
339 | to the system log daemon when run in the background. The value @code{syslog} | |
340 | causes log entries to be sent to the system log daemon. The server name may | |
341 | be included in filenames using the string @code{%s}, as in | |
342 | @code{/var/log/cups/%s-access_log}.") | |
343 | (cache-dir | |
344 | (file-name "/var/cache/cups") | |
345 | "Where CUPS should cache data.") | |
346 | (config-file-perm | |
347 | (string "0640") | |
348 | "Specifies the permissions for all configuration files that the scheduler | |
349 | writes. | |
350 | ||
351 | Note that the permissions for the printers.conf file are currently masked to | |
352 | only allow access from the scheduler user (typically root). This is done | |
353 | because printer device URIs sometimes contain sensitive authentication | |
354 | information that should not be generally known on the system. There is no way | |
355 | to disable this security feature.") | |
356 | ;; Not specifying data-dir and server-bin options as we handle these | |
357 | ;; manually. For document-root, the CUPS package has that path | |
358 | ;; preconfigured. | |
359 | (error-log | |
360 | (log-location "/var/log/cups/error_log") | |
361 | "Defines the error log filename. Specifying a blank filename disables | |
362 | access log generation. The value @code{stderr} causes log entries to be sent | |
363 | to the standard error file when the scheduler is running in the foreground, or | |
364 | to the system log daemon when run in the background. The value @code{syslog} | |
365 | causes log entries to be sent to the system log daemon. The server name may | |
366 | be included in filenames using the string @code{%s}, as in | |
367 | @code{/var/log/cups/%s-error_log}.") | |
368 | (fatal-errors | |
369 | (string "all -browse") | |
370 | "Specifies which errors are fatal, causing the scheduler to exit. The kind | |
371 | strings are: | |
372 | @table @code | |
373 | @item none | |
374 | No errors are fatal. | |
375 | @item all | |
376 | All of the errors below are fatal. | |
377 | @item browse | |
378 | Browsing initialization errors are fatal, for example failed connections to | |
379 | the DNS-SD daemon. | |
380 | @item config | |
381 | Configuration file syntax errors are fatal. | |
382 | @item listen | |
383 | Listen or Port errors are fatal, except for IPv6 failures on the loopback or | |
384 | @code{any} addresses. | |
385 | @item log | |
386 | Log file creation or write errors are fatal. | |
387 | @item permissions | |
388 | Bad startup file permissions are fatal, for example shared TLS certificate and | |
389 | key files with world-read permissions. | |
390 | @end table") | |
391 | (file-device? | |
392 | (boolean #f) | |
393 | "Specifies whether the file pseudo-device can be used for new printer | |
394 | queues. The URI @url{file:///dev/null} is always allowed.") | |
395 | (group | |
396 | (string "lp") | |
397 | "Specifies the group name or ID that will be used when executing external | |
398 | programs.") | |
399 | (log-file-perm | |
400 | (string "0644") | |
401 | "Specifies the permissions for all log files that the scheduler writes.") | |
402 | (page-log | |
403 | (log-location "/var/log/cups/page_log") | |
404 | "Defines the page log filename. Specifying a blank filename disables | |
405 | access log generation. The value @code{stderr} causes log entries to be sent | |
406 | to the standard error file when the scheduler is running in the foreground, or | |
407 | to the system log daemon when run in the background. The value @code{syslog} | |
408 | causes log entries to be sent to the system log daemon. The server name may | |
409 | be included in filenames using the string @code{%s}, as in | |
410 | @code{/var/log/cups/%s-page_log}.") | |
411 | (remote-root | |
412 | (string "remroot") | |
413 | "Specifies the username that is associated with unauthenticated accesses by | |
414 | clients claiming to be the root user. The default is @code{remroot}.") | |
415 | (request-root | |
416 | (file-name "/var/spool/cups") | |
417 | "Specifies the directory that contains print jobs and other HTTP request | |
418 | data.") | |
419 | (sandboxing | |
420 | (sandboxing 'strict) | |
421 | "Specifies the level of security sandboxing that is applied to print | |
422 | filters, backends, and other child processes of the scheduler; either | |
423 | @code{relaxed} or @code{strict}. This directive is currently only | |
424 | used/supported on macOS.") | |
425 | (server-keychain | |
426 | (file-name "/etc/cups/ssl") | |
427 | "Specifies the location of TLS certificates and private keys. CUPS will | |
428 | look for public and private keys in this directory: a @code{.crt} files for | |
429 | PEM-encoded certificates and corresponding @code{.key} files for PEM-encoded | |
430 | private keys.") | |
431 | (server-root | |
432 | (file-name "/etc/cups") | |
433 | "Specifies the directory containing the server configuration files.") | |
434 | (sync-on-close? | |
435 | (boolean #f) | |
436 | "Specifies whether the scheduler calls fsync(2) after writing configuration | |
437 | or state files.") | |
438 | (system-group | |
439 | (space-separated-string-list '("lpadmin" "wheel" "root")) | |
440 | "Specifies the group(s) to use for @code{@@SYSTEM} group authentication.") | |
441 | (temp-dir | |
442 | (file-name "/var/spool/cups/tmp") | |
443 | "Specifies the directory where temporary files are stored.") | |
444 | (user | |
445 | (string "lp") | |
446 | "Specifies the user name or ID that is used when running external | |
447 | programs.")) | |
448 | ||
449 | (define (serialize-files-configuration field-name val) | |
450 | #f) | |
451 | ||
452 | (define (environment-variables? vars) | |
453 | (space-separated-string-list? vars)) | |
454 | (define (serialize-environment-variables field-name vars) | |
455 | (unless (null? vars) | |
456 | (serialize-space-separated-string-list field-name vars))) | |
457 | ||
458 | (define (package-list? val) | |
459 | (and (list? val) (and-map package? val))) | |
460 | (define (serialize-package-list field-name val) | |
461 | #f) | |
462 | ||
463 | (define-configuration cups-configuration | |
464 | (cups | |
465 | (package cups) | |
466 | "The CUPS package.") | |
467 | (extensions | |
468 | (package-list (list cups-filters)) | |
469 | "Drivers and other extensions to the CUPS package.") | |
470 | (files-configuration | |
471 | (files-configuration (files-configuration)) | |
472 | "Configuration of where to write logs, what directories to use for print | |
473 | spools, and related privileged configuration parameters.") | |
474 | (access-log-level | |
475 | (access-log-level 'actions) | |
476 | "Specifies the logging level for the AccessLog file. The @code{config} | |
477 | level logs when printers and classes are added, deleted, or modified and when | |
478 | configuration files are accessed or updated. The @code{actions} level logs | |
479 | when print jobs are submitted, held, released, modified, or canceled, and any | |
480 | of the conditions for @code{config}. The @code{all} level logs all | |
481 | requests.") | |
482 | (auto-purge-jobs? | |
483 | (boolean #f) | |
484 | "Specifies whether to purge job history data automatically when it is no | |
485 | longer required for quotas.") | |
486 | (browse-local-protocols | |
487 | (browse-local-protocols 'dnssd) | |
488 | "Specifies which protocols to use for local printer sharing.") | |
489 | (browse-web-if? | |
490 | (boolean #f) | |
491 | "Specifies whether the CUPS web interface is advertised.") | |
492 | (browsing? | |
493 | (boolean #f) | |
494 | "Specifies whether shared printers are advertised.") | |
495 | (classification | |
496 | (string "") | |
497 | "Specifies the security classification of the server. | |
498 | Any valid banner name can be used, including \"classified\", \"confidential\", | |
499 | \"secret\", \"topsecret\", and \"unclassified\", or the banner can be omitted | |
500 | to disable secure printing functions.") | |
501 | (classify-override? | |
502 | (boolean #f) | |
503 | "Specifies whether users may override the classification (cover page) of | |
504 | individual print jobs using the @code{job-sheets} option.") | |
505 | (default-auth-type | |
506 | (default-auth-type 'Basic) | |
507 | "Specifies the default type of authentication to use.") | |
508 | (default-encryption | |
509 | (default-encryption 'Required) | |
510 | "Specifies whether encryption will be used for authenticated requests.") | |
511 | (default-language | |
512 | (string "en") | |
513 | "Specifies the default language to use for text and web content.") | |
514 | (default-paper-size | |
515 | (string "Auto") | |
516 | "Specifies the default paper size for new print queues. @samp{\"Auto\"} | |
517 | uses a locale-specific default, while @samp{\"None\"} specifies there is no | |
518 | default paper size. Specific size names are typically @samp{\"Letter\"} or | |
519 | @samp{\"A4\"}.") | |
520 | (default-policy | |
521 | (string "default") | |
522 | "Specifies the default access policy to use.") | |
523 | (default-shared? | |
524 | (boolean #t) | |
525 | "Specifies whether local printers are shared by default.") | |
526 | (dirty-clean-interval | |
527 | (non-negative-integer 30) | |
528 | "Specifies the delay for updating of configuration and state files, in | |
529 | seconds. A value of 0 causes the update to happen as soon as possible, | |
530 | typically within a few milliseconds.") | |
531 | (error-policy | |
532 | (error-policy 'stop-printer) | |
533 | "Specifies what to do when an error occurs. Possible values are | |
534 | @code{abort-job}, which will discard the failed print job; @code{retry-job}, | |
535 | which will retry the job at a later time; @code{retry-this-job}, which retries | |
536 | the failed job immediately; and @code{stop-printer}, which stops the | |
537 | printer.") | |
538 | (filter-limit | |
539 | (non-negative-integer 0) | |
540 | "Specifies the maximum cost of filters that are run concurrently, which can | |
541 | be used to minimize disk, memory, and CPU resource problems. A limit of 0 | |
542 | disables filter limiting. An average print to a non-PostScript printer needs | |
543 | a filter limit of about 200. A PostScript printer needs about half | |
544 | that (100). Setting the limit below these thresholds will effectively limit | |
545 | the scheduler to printing a single job at any time.") | |
546 | (filter-nice | |
547 | (non-negative-integer 0) | |
548 | "Specifies the scheduling priority of filters that are run to print a job. | |
549 | The nice value ranges from 0, the highest priority, to 19, the lowest | |
550 | priority.") | |
551 | ;; Add this option if the package is built with Kerberos support. | |
552 | ;; (gss-service-name | |
553 | ;; (string "http") | |
554 | ;; "Specifies the service name when using Kerberos authentication.") | |
555 | (host-name-lookups | |
556 | (host-name-lookups #f) | |
557 | "Specifies whether to do reverse lookups on connecting clients. | |
558 | The @code{double} setting causes @code{cupsd} to verify that the hostname | |
559 | resolved from the address matches one of the addresses returned for that | |
560 | hostname. Double lookups also prevent clients with unregistered addresses | |
561 | from connecting to your server. Only set this option to @code{#t} or | |
562 | @code{double} if absolutely required.") | |
563 | ;; Add this option if the package is built with launchd/systemd support. | |
564 | ;; (idle-exit-timeout | |
565 | ;; (non-negative-integer 60) | |
566 | ;; "Specifies the length of time to wait before shutting down due to | |
567 | ;; inactivity. Note: Only applicable when @code{cupsd} is run on-demand | |
568 | ;; (e.g., with @code{-l}).") | |
569 | (job-kill-delay | |
570 | (non-negative-integer 30) | |
571 | "Specifies the number of seconds to wait before killing the filters and | |
572 | backend associated with a canceled or held job.") | |
573 | (job-retry-interval | |
574 | (non-negative-integer 30) | |
575 | "Specifies the interval between retries of jobs in seconds. This is | |
576 | typically used for fax queues but can also be used with normal print queues | |
577 | whose error policy is @code{retry-job} or @code{retry-current-job}.") | |
578 | (job-retry-limit | |
579 | (non-negative-integer 5) | |
580 | "Specifies the number of retries that are done for jobs. This is typically | |
581 | used for fax queues but can also be used with normal print queues whose error | |
582 | policy is @code{retry-job} or @code{retry-current-job}.") | |
583 | (keep-alive? | |
584 | (boolean #t) | |
585 | "Specifies whether to support HTTP keep-alive connections.") | |
586 | (keep-alive-timeout | |
587 | (non-negative-integer 30) | |
588 | "Specifies how long an idle client connection remains open, in seconds.") | |
589 | (limit-request-body | |
590 | (non-negative-integer 0) | |
591 | "Specifies the maximum size of print files, IPP requests, and HTML form | |
592 | data. A limit of 0 disables the limit check.") | |
593 | (listen | |
594 | (multiline-string-list '("localhost:631" "/var/run/cups/cups.sock")) | |
595 | "Listens on the specified interfaces for connections. Valid values are of | |
596 | the form @var{address}:@var{port}, where @var{address} is either an IPv6 | |
597 | address enclosed in brackets, an IPv4 address, or @code{*} to indicate all | |
598 | addresses. Values can also be file names of local UNIX domain sockets. The | |
599 | Listen directive is similar to the Port directive but allows you to restrict | |
600 | access to specific interfaces or networks.") | |
601 | (listen-back-log | |
602 | (non-negative-integer 128) | |
603 | "Specifies the number of pending connections that will be allowed. This | |
604 | normally only affects very busy servers that have reached the MaxClients | |
605 | limit, but can also be triggered by large numbers of simultaneous connections. | |
606 | When the limit is reached, the operating system will refuse additional | |
607 | connections until the scheduler can accept the pending ones.") | |
608 | (location-access-controls | |
609 | (location-access-control-list | |
610 | (list (location-access-control | |
611 | (path "/") | |
612 | (access-controls '("Order allow,deny" | |
613 | "Allow localhost"))) | |
614 | (location-access-control | |
615 | (path "/admin") | |
616 | (access-controls '("Order allow,deny" | |
617 | "Allow localhost"))) | |
618 | (location-access-control | |
619 | (path "/admin/conf") | |
620 | (access-controls '("Order allow,deny" | |
621 | "AuthType Basic" | |
622 | "Require user @SYSTEM" | |
623 | "Allow localhost"))))) | |
624 | "Specifies a set of additional access controls.") | |
625 | (log-debug-history | |
626 | (non-negative-integer 100) | |
627 | "Specifies the number of debugging messages that are retained for logging | |
628 | if an error occurs in a print job. Debug messages are logged regardless of | |
629 | the LogLevel setting.") | |
630 | (log-level | |
631 | (log-level 'info) | |
632 | "Specifies the level of logging for the ErrorLog file. The value | |
633 | @code{none} stops all logging while @code{debug2} logs everything.") | |
634 | (log-time-format | |
635 | (log-time-format 'standard) | |
636 | "Specifies the format of the date and time in the log files. The value | |
637 | @code{standard} logs whole seconds while @code{usecs} logs microseconds.") | |
638 | (max-clients | |
639 | (non-negative-integer 100) | |
640 | "Specifies the maximum number of simultaneous clients that are allowed by | |
641 | the scheduler.") | |
642 | (max-clients-per-host | |
643 | (non-negative-integer 100) | |
644 | "Specifies the maximum number of simultaneous clients that are allowed from | |
645 | a single address.") | |
646 | (max-copies | |
647 | (non-negative-integer 9999) | |
648 | "Specifies the maximum number of copies that a user can print of each | |
649 | job.") | |
650 | (max-hold-time | |
651 | (non-negative-integer 0) | |
652 | "Specifies the maximum time a job may remain in the @code{indefinite} hold | |
653 | state before it is canceled. A value of 0 disables cancellation of held | |
654 | jobs.") | |
655 | (max-jobs | |
656 | (non-negative-integer 500) | |
657 | "Specifies the maximum number of simultaneous jobs that are allowed. Set | |
658 | to 0 to allow an unlimited number of jobs.") | |
659 | (max-jobs-per-printer | |
660 | (non-negative-integer 0) | |
661 | "Specifies the maximum number of simultaneous jobs that are allowed per | |
662 | printer. A value of 0 allows up to MaxJobs jobs per printer.") | |
663 | (max-jobs-per-user | |
664 | (non-negative-integer 0) | |
665 | "Specifies the maximum number of simultaneous jobs that are allowed per | |
666 | user. A value of 0 allows up to MaxJobs jobs per user.") | |
667 | (max-job-time | |
668 | (non-negative-integer 10800) | |
669 | "Specifies the maximum time a job may take to print before it is canceled, | |
670 | in seconds. Set to 0 to disable cancellation of \"stuck\" jobs.") | |
671 | (max-log-size | |
672 | (non-negative-integer 1048576) | |
673 | "Specifies the maximum size of the log files before they are rotated, in | |
674 | bytes. The value 0 disables log rotation.") | |
675 | (multiple-operation-timeout | |
676 | (non-negative-integer 300) | |
677 | "Specifies the maximum amount of time to allow between files in a multiple | |
678 | file print job, in seconds.") | |
679 | (page-log-format | |
680 | (string "") | |
681 | "Specifies the format of PageLog lines. Sequences beginning with | |
682 | percent (@samp{%}) characters are replaced with the corresponding information, | |
683 | while all other characters are copied literally. The following percent | |
684 | sequences are recognized: | |
685 | ||
686 | @table @samp | |
687 | @item %% | |
688 | insert a single percent character | |
689 | @item %@{name@} | |
690 | insert the value of the specified IPP attribute | |
691 | @item %C | |
692 | insert the number of copies for the current page | |
693 | @item %P | |
694 | insert the current page number | |
695 | @item %T | |
696 | insert the current date and time in common log format | |
697 | @item %j | |
698 | insert the job ID | |
699 | @item %p | |
700 | insert the printer name | |
701 | @item %u | |
702 | insert the username | |
703 | @end table | |
704 | ||
705 | A value of the empty string disables page logging. The string @code{%p %u %j | |
706 | %T %P %C %@{job-billing@} %@{job-originating-host-name@} %@{job-name@} | |
707 | %@{media@} %@{sides@}} creates a page log with the standard items.") | |
708 | (environment-variables | |
709 | (environment-variables '()) | |
710 | "Passes the specified environment variable(s) to child processes; a list of | |
711 | strings.") | |
712 | (policies | |
713 | (policy-configuration-list | |
714 | (list (policy-configuration | |
715 | (name "default") | |
716 | (access-controls | |
717 | (list | |
718 | (operation-access-control | |
719 | (operations | |
720 | '(Send-Document | |
721 | Send-URI Hold-Job Release-Job Restart-Job Purge-Jobs | |
722 | Cancel-Job Close-Job Cancel-My-Jobs Set-Job-Attributes | |
723 | Create-Job-Subscription Renew-Subscription | |
724 | Cancel-Subscription Get-Notifications | |
725 | Reprocess-Job Cancel-Current-Job Suspend-Current-Job | |
726 | Resume-Job CUPS-Move-Job Validate-Job | |
727 | CUPS-Get-Document)) | |
728 | (access-controls '("Require user @OWNER @SYSTEM" | |
729 | "Order deny,allow"))) | |
730 | (operation-access-control | |
731 | (operations | |
732 | '(Pause-Printer | |
733 | Cancel-Jobs | |
734 | Resume-Printer Set-Printer-Attributes Enable-Printer | |
735 | Disable-Printer Pause-Printer-After-Current-Job | |
736 | Hold-New-Jobs Release-Held-New-Jobs Deactivate-Printer | |
737 | Activate-Printer Restart-Printer Shutdown-Printer | |
738 | Startup-Printer Promote-Job Schedule-Job-After | |
739 | CUPS-Authenticate-Job CUPS-Add-Printer | |
740 | CUPS-Delete-Printer CUPS-Add-Class CUPS-Delete-Class | |
741 | CUPS-Accept-Jobs CUPS-Reject-Jobs CUPS-Set-Default)) | |
742 | (access-controls '("AuthType Basic" | |
743 | "Require user @SYSTEM" | |
744 | "Order deny,allow"))) | |
745 | (operation-access-control | |
746 | (operations '(All)) | |
747 | (access-controls '("Order deny,allow")))))))) | |
748 | "Specifies named access control policies.") | |
749 | #; | |
750 | (port | |
751 | (non-negative-integer 631) | |
752 | "Listens to the specified port number for connections.") | |
753 | (preserve-job-files | |
754 | (boolean-or-non-negative-integer 86400) | |
755 | "Specifies whether job files (documents) are preserved after a job is | |
756 | printed. If a numeric value is specified, job files are preserved for the | |
757 | indicated number of seconds after printing. Otherwise a boolean value applies | |
758 | indefinitely.") | |
759 | (preserve-job-history | |
760 | (boolean-or-non-negative-integer #t) | |
761 | "Specifies whether the job history is preserved after a job is printed. | |
762 | If a numeric value is specified, the job history is preserved for the | |
763 | indicated number of seconds after printing. If @code{#t}, the job history is | |
764 | preserved until the MaxJobs limit is reached.") | |
765 | (reload-timeout | |
766 | (non-negative-integer 30) | |
767 | "Specifies the amount of time to wait for job completion before restarting | |
768 | the scheduler.") | |
769 | (rip-cache | |
770 | (string "128m") | |
771 | "Specifies the maximum amount of memory to use when converting documents into bitmaps for a printer.") | |
772 | (server-admin | |
773 | (string "root@localhost.localdomain") | |
774 | "Specifies the email address of the server administrator.") | |
775 | (server-alias | |
776 | (host-name-list-or-* '*) | |
777 | "The ServerAlias directive is used for HTTP Host header validation when | |
778 | clients connect to the scheduler from external interfaces. Using the special | |
779 | name @code{*} can expose your system to known browser-based DNS rebinding | |
780 | attacks, even when accessing sites through a firewall. If the auto-discovery | |
781 | of alternate names does not work, we recommend listing each alternate name | |
782 | with a ServerAlias directive instead of using @code{*}.") | |
783 | (server-name | |
784 | (string "localhost") | |
785 | "Specifies the fully-qualified host name of the server.") | |
786 | (server-tokens | |
787 | (server-tokens 'Minimal) | |
788 | "Specifies what information is included in the Server header of HTTP | |
789 | responses. @code{None} disables the Server header. @code{ProductOnly} | |
790 | reports @code{CUPS}. @code{Major} reports @code{CUPS 2}. @code{Minor} | |
791 | reports @code{CUPS 2.0}. @code{Minimal} reports @code{CUPS 2.0.0}. @code{OS} | |
792 | reports @code{CUPS 2.0.0 (@var{uname})} where @var{uname} is the output of the | |
793 | @code{uname} command. @code{Full} reports @code{CUPS 2.0.0 (@var{uname}) | |
794 | IPP/2.0}.") | |
795 | (set-env | |
796 | (string "variable value") | |
797 | "Set the specified environment variable to be passed to child processes.") | |
798 | (ssl-listen | |
799 | (multiline-string-list '()) | |
800 | "Listens on the specified interfaces for encrypted connections. Valid | |
801 | values are of the form @var{address}:@var{port}, where @var{address} is either | |
802 | an IPv6 address enclosed in brackets, an IPv4 address, or @code{*} to indicate | |
803 | all addresses.") | |
804 | (ssl-options | |
805 | (ssl-options '()) | |
806 | "Sets encryption options. | |
807 | By default, CUPS only supports encryption using TLS v1.0 or higher using known | |
808 | secure cipher suites. The @code{AllowRC4} option enables the 128-bit RC4 | |
809 | cipher suites, which are required for some older clients that do not implement | |
810 | newer ones. The @code{AllowSSL3} option enables SSL v3.0, which is required | |
811 | for some older clients that do not support TLS v1.0.") | |
812 | #; | |
813 | (ssl-port | |
814 | (non-negative-integer 631) | |
815 | "Listens on the specified port for encrypted connections.") | |
816 | (strict-conformance? | |
817 | (boolean #f) | |
818 | "Specifies whether the scheduler requires clients to strictly adhere to the | |
819 | IPP specifications.") | |
820 | (timeout | |
821 | (non-negative-integer 300) | |
822 | "Specifies the HTTP request timeout, in seconds.") | |
823 | (web-interface? | |
824 | (boolean #f) | |
825 | "Specifies whether the web interface is enabled.")) | |
826 | ||
827 | (define-configuration opaque-cups-configuration | |
828 | (cups | |
829 | (package cups) | |
830 | "The CUPS package.") | |
831 | (extensions | |
832 | (package-list '()) | |
833 | "Drivers and other extensions to the CUPS package.") | |
834 | (cupsd.conf | |
5305ed20 JL |
835 | (string (configuration-missing-field 'opaque-cups-configuration |
836 | 'cupsd.conf)) | |
f2ec23d1 AW |
837 | "The contents of the @code{cupsd.conf} to use.") |
838 | (cups-files.conf | |
5305ed20 JL |
839 | (string (configuration-missing-field 'opaque-cups-configuration |
840 | 'cups-files.conf)) | |
f2ec23d1 AW |
841 | "The contents of the @code{cups-files.conf} to use.")) |
842 | ||
843 | (define %cups-activation | |
844 | ;; Activation gexp. | |
845 | (with-imported-modules '((guix build utils)) | |
846 | #~(begin | |
e57bd0be | 847 | (use-modules (guix build utils)) |
f2ec23d1 AW |
848 | (define (mkdir-p/perms directory owner perms) |
849 | (mkdir-p directory) | |
850 | (chown "/var/run/cups" (passwd:uid owner) (passwd:gid owner)) | |
851 | (chmod directory perms)) | |
852 | (define (build-subject parameters) | |
853 | (string-concatenate | |
854 | (map (lambda (pair) | |
855 | (let ((k (car pair)) (v (cdr pair))) | |
856 | (define (escape-char str chr) | |
857 | (string-join (string-split str chr) (string #\\ chr))) | |
858 | (string-append "/" k "=" | |
859 | (escape-char (escape-char v #\=) #\/)))) | |
860 | (filter (lambda (pair) (cdr pair)) parameters)))) | |
861 | (define* (create-self-signed-certificate-if-absent | |
862 | #:key private-key public-key (owner (getpwnam "root")) | |
863 | (common-name (gethostname)) | |
864 | (organization-name "GuixSD") | |
865 | (organization-unit-name "Default Self-Signed Certificate") | |
866 | (subject-parameters `(("CN" . ,common-name) | |
867 | ("O" . ,organization-name) | |
868 | ("OU" . ,organization-unit-name))) | |
869 | (subject (build-subject subject-parameters))) | |
870 | ;; Note that by default, OpenSSL outputs keys in PEM format. This | |
871 | ;; is what we want. | |
872 | (unless (file-exists? private-key) | |
873 | (cond | |
874 | ((zero? (system* (string-append #$openssl "/bin/openssl") | |
875 | "genrsa" "-out" private-key "2048")) | |
876 | (chown private-key (passwd:uid owner) (passwd:gid owner)) | |
877 | (chmod private-key #o400)) | |
878 | (else | |
879 | (format (current-error-port) | |
880 | "Failed to create private key at ~a.\n" private-key)))) | |
881 | (unless (file-exists? public-key) | |
882 | (cond | |
883 | ((zero? (system* (string-append #$openssl "/bin/openssl") | |
884 | "req" "-new" "-x509" "-key" private-key | |
885 | "-out" public-key "-days" "3650" | |
886 | "-batch" "-subj" subject)) | |
887 | (chown public-key (passwd:uid owner) (passwd:gid owner)) | |
888 | (chmod public-key #o444)) | |
889 | (else | |
890 | (format (current-error-port) | |
891 | "Failed to create public key at ~a.\n" public-key))))) | |
892 | (let ((user (getpwnam "lp"))) | |
893 | (mkdir-p/perms "/var/run/cups" user #o755) | |
894 | (mkdir-p/perms "/var/spool/cups" user #o755) | |
895 | (mkdir-p/perms "/var/spool/cups/tmp" user #o755) | |
896 | (mkdir-p/perms "/var/log/cups" user #o755) | |
897 | (mkdir-p/perms "/etc/cups" user #o755) | |
898 | (mkdir-p/perms "/etc/cups/ssl" user #o700) | |
899 | ;; This certificate is used for HTTPS connections to the CUPS web | |
900 | ;; interface. | |
901 | (create-self-signed-certificate-if-absent | |
902 | #:private-key "/etc/cups/ssl/localhost.key" | |
903 | #:public-key "/etc/cups/ssl/localhost.crt" | |
904 | #:owner (getpwnam "root") | |
905 | #:common-name (format #f "CUPS service on ~a" (gethostname))))))) | |
906 | ||
907 | (define (union-directory name packages paths) | |
908 | (computed-file | |
909 | name | |
910 | (with-imported-modules '((guix build utils)) | |
911 | #~(begin | |
912 | (use-modules (guix build utils) | |
913 | (srfi srfi-1)) | |
914 | (mkdir #$output) | |
915 | (for-each | |
916 | (lambda (package) | |
917 | (for-each | |
918 | (lambda (path) | |
919 | (for-each | |
920 | (lambda (src) | |
921 | (let* ((tail (substring src (string-length package))) | |
922 | (dst (string-append #$output tail))) | |
923 | (mkdir-p (dirname dst)) | |
924 | ;; CUPS currently symlinks in some data from cups-filters | |
925 | ;; to its output dir. Probably we should stop doing this | |
926 | ;; and instead rely only on the CUPS service to union the | |
927 | ;; relevant set of CUPS packages. | |
928 | (if (file-exists? dst) | |
929 | (format (current-error-port) "warning: ~a exists\n" dst) | |
930 | (symlink src dst)))) | |
4ce8860d | 931 | (find-files (string-append package path) #:stat stat))) |
f2ec23d1 AW |
932 | (list #$@paths))) |
933 | (list #$@packages)) | |
934 | #t)))) | |
935 | ||
936 | (define (cups-server-bin-directory extensions) | |
937 | "Return the CUPS ServerBin directory, containing binaries for CUPS and all | |
938 | extensions that it uses." | |
939 | (union-directory "cups-server-bin" extensions | |
940 | ;; /bin | |
941 | '("/lib/cups" "/share/ppd" "/share/cups"))) | |
942 | ||
943 | (define (cups-shepherd-service config) | |
944 | "Return a list of <shepherd-service> for CONFIG." | |
945 | (let* ((cupsd.conf-str | |
946 | (cond | |
947 | ((opaque-cups-configuration? config) | |
948 | (opaque-cups-configuration-cupsd.conf config)) | |
949 | (else | |
950 | (with-output-to-string | |
951 | (lambda () | |
952 | (serialize-configuration config | |
953 | cups-configuration-fields)))))) | |
954 | (cups-files.conf-str | |
955 | (cond | |
956 | ((opaque-cups-configuration? config) | |
957 | (opaque-cups-configuration-cups-files.conf config)) | |
958 | (else | |
959 | (with-output-to-string | |
960 | (lambda () | |
961 | (serialize-configuration | |
962 | (cups-configuration-files-configuration config) | |
963 | files-configuration-fields)))))) | |
964 | (cups (if (opaque-cups-configuration? config) | |
965 | (opaque-cups-configuration-cups config) | |
966 | (cups-configuration-cups config))) | |
967 | (server-bin | |
968 | (cups-server-bin-directory | |
969 | (cons cups | |
970 | (cond | |
971 | ((opaque-cups-configuration? config) | |
972 | (opaque-cups-configuration-extensions config)) | |
973 | (else | |
974 | (cups-configuration-extensions config)))))) | |
975 | ;;"SetEnv PATH " server-bin "/bin" "\n" | |
976 | (cupsd.conf | |
977 | (plain-file "cupsd.conf" cupsd.conf-str)) | |
978 | (cups-files.conf | |
979 | (mixed-text-file | |
980 | "cups-files.conf" | |
981 | cups-files.conf-str | |
982 | "CacheDir /var/cache/cups\n" | |
983 | "StateDir /var/run/cups\n" | |
984 | "DataDir " server-bin "/share/cups" "\n" | |
985 | "ServerBin " server-bin "/lib/cups" "\n"))) | |
986 | (list (shepherd-service | |
987 | (documentation "Run the CUPS print server.") | |
988 | (provision '(cups)) | |
989 | (requirement '(networking)) | |
990 | (start #~(make-forkexec-constructor | |
991 | (list (string-append #$cups "/sbin/cupsd") | |
992 | "-f" "-c" #$cupsd.conf "-s" #$cups-files.conf))) | |
993 | (stop #~(make-kill-destructor)))))) | |
994 | ||
995 | (define cups-service-type | |
996 | (service-type (name 'cups) | |
997 | (extensions | |
998 | (list (service-extension shepherd-root-service-type | |
999 | cups-shepherd-service) | |
1000 | (service-extension activation-service-type | |
1001 | (const %cups-activation)) | |
1002 | (service-extension account-service-type | |
1003 | (const %cups-accounts)))) | |
1004 | ||
1005 | ;; Extensions consist of lists of packages (representing CUPS | |
1006 | ;; drivers, etc) that we just concatenate. | |
1007 | (compose append) | |
1008 | ||
1009 | ;; Add extension packages by augmenting the cups-configuration | |
1010 | ;; 'extensions' field. | |
1011 | (extend | |
1012 | (lambda (config extensions) | |
1013 | (cond | |
1014 | ((cups-configuration? config) | |
1015 | (cups-configuration | |
1016 | (inherit config) | |
1017 | (extensions | |
1018 | (append (cups-configuration-extensions config) | |
1019 | extensions)))) | |
1020 | (else | |
1021 | (opaque-cups-configuration | |
1022 | (inherit config) | |
1023 | (extensions | |
1024 | (append (opaque-cups-configuration-extensions config) | |
3d3c5650 LC |
1025 | extensions))))))) |
1026 | ||
1027 | (default-value (cups-configuration)))) | |
f2ec23d1 AW |
1028 | |
1029 | ;; A little helper to make it easier to document all those fields. | |
5305ed20 JL |
1030 | (define (generate-cups-documentation) |
1031 | (generate-documentation | |
f2ec23d1 AW |
1032 | `((cups-configuration |
1033 | ,cups-configuration-fields | |
1034 | (files-configuration files-configuration) | |
1035 | (policies policy-configuration) | |
1036 | (location-access-controls location-access-controls)) | |
1037 | (files-configuration ,files-configuration-fields) | |
1038 | (policy-configuration | |
1039 | ,policy-configuration-fields | |
1040 | (operation-access-controls operation-access-controls)) | |
1041 | (location-access-controls | |
1042 | ,location-access-control-fields | |
1043 | (method-access-controls method-access-controls)) | |
1044 | (operation-access-controls ,operation-access-control-fields) | |
5305ed20 JL |
1045 | (method-access-controls ,method-access-control-fields)) |
1046 | 'cups-configuration)) |