| 1 | ;;; GNU Guix --- Functional package management for GNU |
| 2 | ;;; Copyright © 2017 Julien Lepiller <julien@lepiller.eu> |
| 3 | ;;; Copyright © 2018 Oleg Pykhalov <go.wigust@gmail.com> |
| 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 dns) |
| 21 | #:use-module (gnu services) |
| 22 | #:use-module (gnu services configuration) |
| 23 | #:use-module (gnu services shepherd) |
| 24 | #:use-module (gnu system shadow) |
| 25 | #:use-module (gnu packages admin) |
| 26 | #:use-module (gnu packages dns) |
| 27 | #:use-module (guix packages) |
| 28 | #:use-module (guix records) |
| 29 | #:use-module (guix gexp) |
| 30 | #:use-module (srfi srfi-1) |
| 31 | #:use-module (srfi srfi-26) |
| 32 | #:use-module (srfi srfi-34) |
| 33 | #:use-module (srfi srfi-35) |
| 34 | #:use-module (ice-9 match) |
| 35 | #:use-module (ice-9 regex) |
| 36 | #:export (knot-service-type |
| 37 | knot-acl-configuration |
| 38 | knot-key-configuration |
| 39 | knot-keystore-configuration |
| 40 | knot-zone-configuration |
| 41 | knot-remote-configuration |
| 42 | knot-policy-configuration |
| 43 | knot-configuration |
| 44 | define-zone-entries |
| 45 | zone-file |
| 46 | zone-entry |
| 47 | |
| 48 | knot-resolver-service-type |
| 49 | knot-resolver-configuration |
| 50 | |
| 51 | dnsmasq-service-type |
| 52 | dnsmasq-configuration |
| 53 | |
| 54 | ddclient-service-type |
| 55 | ddclient-configuration)) |
| 56 | |
| 57 | ;;; |
| 58 | ;;; Knot DNS. |
| 59 | ;;; |
| 60 | |
| 61 | (define-record-type* <knot-key-configuration> |
| 62 | knot-key-configuration make-knot-key-configuration |
| 63 | knot-key-configuration? |
| 64 | (id knot-key-configuration-id |
| 65 | (default "")) |
| 66 | (algorithm knot-key-configuration-algorithm |
| 67 | (default #f)); one of #f, or an algorithm name |
| 68 | (secret knot-key-configuration-secret |
| 69 | (default ""))) |
| 70 | |
| 71 | (define-record-type* <knot-acl-configuration> |
| 72 | knot-acl-configuration make-knot-acl-configuration |
| 73 | knot-acl-configuration? |
| 74 | (id knot-acl-configuration-id |
| 75 | (default "")) |
| 76 | (address knot-acl-configuration-address |
| 77 | (default '())) |
| 78 | (key knot-acl-configuration-key |
| 79 | (default '())) |
| 80 | (action knot-acl-configuration-action |
| 81 | (default '())) |
| 82 | (deny? knot-acl-configuration-deny? |
| 83 | (default #f))) |
| 84 | |
| 85 | (define-record-type* <zone-entry> |
| 86 | zone-entry make-zone-entry |
| 87 | zone-entry? |
| 88 | (name zone-entry-name |
| 89 | (default "@")) |
| 90 | (ttl zone-entry-ttl |
| 91 | (default "")) |
| 92 | (class zone-entry-class |
| 93 | (default "IN")) |
| 94 | (type zone-entry-type |
| 95 | (default "A")) |
| 96 | (data zone-entry-data |
| 97 | (default ""))) |
| 98 | |
| 99 | (define-record-type* <zone-file> |
| 100 | zone-file make-zone-file |
| 101 | zone-file? |
| 102 | (entries zone-file-entries |
| 103 | (default '())) |
| 104 | (origin zone-file-origin |
| 105 | (default "")) |
| 106 | (ns zone-file-ns |
| 107 | (default "ns")) |
| 108 | (mail zone-file-mail |
| 109 | (default "hostmaster")) |
| 110 | (serial zone-file-serial |
| 111 | (default 1)) |
| 112 | (refresh zone-file-refresh |
| 113 | (default (* 2 24 3600))) |
| 114 | (retry zone-file-retry |
| 115 | (default (* 15 60))) |
| 116 | (expiry zone-file-expiry |
| 117 | (default (* 2 7 24 3600))) |
| 118 | (nx zone-file-nx |
| 119 | (default 3600))) |
| 120 | (define-record-type* <knot-keystore-configuration> |
| 121 | knot-keystore-configuration make-knot-keystore-configuration |
| 122 | knot-keystore-configuration? |
| 123 | (id knot-keystore-configuration-id |
| 124 | (default "")) |
| 125 | (backend knot-keystore-configuration-backend |
| 126 | (default 'pem)) |
| 127 | (config knot-keystore-configuration-config |
| 128 | (default "/var/lib/knot/keys/keys"))) |
| 129 | |
| 130 | (define-record-type* <knot-policy-configuration> |
| 131 | knot-policy-configuration make-knot-policy-configuration |
| 132 | knot-policy-configuration? |
| 133 | (id knot-policy-configuration-id |
| 134 | (default "")) |
| 135 | (keystore knot-policy-configuration-keystore |
| 136 | (default "default")) |
| 137 | (manual? knot-policy-configuration-manual? |
| 138 | (default #f)) |
| 139 | (single-type-signing? knot-policy-configuration-single-type-signing? |
| 140 | (default #f)) |
| 141 | (algorithm knot-policy-configuration-algorithm |
| 142 | (default "ecdsap256sha256")) |
| 143 | (ksk-size knot-policy-configuration-ksk-size |
| 144 | (default 256)) |
| 145 | (zsk-size knot-policy-configuration-zsk-size |
| 146 | (default 256)) |
| 147 | (dnskey-ttl knot-policy-configuration-dnskey-ttl |
| 148 | (default 'default)) |
| 149 | (zsk-lifetime knot-policy-configuration-zsk-lifetime |
| 150 | (default (* 30 24 3600))) |
| 151 | (propagation-delay knot-policy-configuration-propagation-delay |
| 152 | (default (* 24 3600))) |
| 153 | (rrsig-lifetime knot-policy-configuration-rrsig-lifetime |
| 154 | (default (* 14 24 3600))) |
| 155 | (rrsig-refresh knot-policy-configuration-rrsig-refresh |
| 156 | (default (* 7 24 3600))) |
| 157 | (nsec3? knot-policy-configuration-nsec3? |
| 158 | (default #f)) |
| 159 | (nsec3-iterations knot-policy-configuration-nsec3-iterations |
| 160 | (default 5)) |
| 161 | (nsec3-salt-length knot-policy-configuration-nsec3-salt-length |
| 162 | (default 8)) |
| 163 | (nsec3-salt-lifetime knot-policy-configuration-nsec3-salt-lifetime |
| 164 | (default (* 30 24 3600)))) |
| 165 | |
| 166 | (define-record-type* <knot-zone-configuration> |
| 167 | knot-zone-configuration make-knot-zone-configuration |
| 168 | knot-zone-configuration? |
| 169 | (domain knot-zone-configuration-domain |
| 170 | (default "")) |
| 171 | (file knot-zone-configuration-file |
| 172 | (default "")) ; the file where this zone is saved. |
| 173 | (zone knot-zone-configuration-zone |
| 174 | (default (zone-file))) ; initial content of the zone file |
| 175 | (master knot-zone-configuration-master |
| 176 | (default '())) |
| 177 | (ddns-master knot-zone-configuration-ddns-master |
| 178 | (default #f)) |
| 179 | (notify knot-zone-configuration-notify |
| 180 | (default '())) |
| 181 | (acl knot-zone-configuration-acl |
| 182 | (default '())) |
| 183 | (semantic-checks? knot-zone-configuration-semantic-checks? |
| 184 | (default #f)) |
| 185 | (disable-any? knot-zone-configuration-disable-any? |
| 186 | (default #f)) |
| 187 | (zonefile-sync knot-zone-configuration-zonefile-sync |
| 188 | (default 0)) |
| 189 | (zonefile-load knot-zone-configuration-zonefile-load |
| 190 | (default #f)) |
| 191 | (journal-content knot-zone-configuration-journal-content |
| 192 | (default #f)) |
| 193 | (max-journal-usage knot-zone-configuration-max-journal-usage |
| 194 | (default #f)) |
| 195 | (max-journal-depth knot-zone-configuration-max-journal-depth |
| 196 | (default #f)) |
| 197 | (max-zone-size knot-zone-configuration-max-zone-size |
| 198 | (default #f)) |
| 199 | (dnssec-policy knot-zone-configuration-dnssec-policy |
| 200 | (default #f)) |
| 201 | (serial-policy knot-zone-configuration-serial-policy |
| 202 | (default 'increment))) |
| 203 | |
| 204 | (define-record-type* <knot-remote-configuration> |
| 205 | knot-remote-configuration make-knot-remote-configuration |
| 206 | knot-remote-configuration? |
| 207 | (id knot-remote-configuration-id |
| 208 | (default "")) |
| 209 | (address knot-remote-configuration-address |
| 210 | (default '())) |
| 211 | (via knot-remote-configuration-via |
| 212 | (default '())) |
| 213 | (key knot-remote-configuration-key |
| 214 | (default #f))) |
| 215 | |
| 216 | (define-record-type* <knot-configuration> |
| 217 | knot-configuration make-knot-configuration |
| 218 | knot-configuration? |
| 219 | (knot knot-configuration-knot |
| 220 | (default knot)) |
| 221 | (run-directory knot-configuration-run-directory |
| 222 | (default "/var/run/knot")) |
| 223 | (includes knot-configuration-includes |
| 224 | (default '())) |
| 225 | (listen-v4 knot-configuration-listen-v4 |
| 226 | (default "0.0.0.0")) |
| 227 | (listen-v6 knot-configuration-listen-v6 |
| 228 | (default "::")) |
| 229 | (listen-port knot-configuration-listen-port |
| 230 | (default 53)) |
| 231 | (keys knot-configuration-keys |
| 232 | (default '())) |
| 233 | (keystores knot-configuration-keystores |
| 234 | (default '())) |
| 235 | (acls knot-configuration-acls |
| 236 | (default '())) |
| 237 | (remotes knot-configuration-remotes |
| 238 | (default '())) |
| 239 | (policies knot-configuration-policies |
| 240 | (default '())) |
| 241 | (zones knot-configuration-zones |
| 242 | (default '()))) |
| 243 | |
| 244 | (define-syntax define-zone-entries |
| 245 | (syntax-rules () |
| 246 | ((_ id (name ttl class type data) ...) |
| 247 | (define id (list (make-zone-entry name ttl class type data) ...))))) |
| 248 | |
| 249 | (define (error-out msg) |
| 250 | (raise (condition (&message (message msg))))) |
| 251 | |
| 252 | (define (verify-knot-key-configuration key) |
| 253 | (unless (knot-key-configuration? key) |
| 254 | (error-out "keys must be a list of only knot-key-configuration.")) |
| 255 | (let ((id (knot-key-configuration-id key))) |
| 256 | (unless (and (string? id) (not (equal? id ""))) |
| 257 | (error-out "key id must be a non empty string."))) |
| 258 | (unless (memq '(#f hmac-md5 hmac-sha1 hmac-sha224 hmac-sha256 hmac-sha384 hmac-sha512) |
| 259 | (knot-key-configuration-algorithm key)) |
| 260 | (error-out "algorithm must be one of: #f, 'hmac-md5, 'hmac-sha1, |
| 261 | 'hmac-sha224, 'hmac-sha256, 'hmac-sha384 or 'hmac-sha512"))) |
| 262 | |
| 263 | (define (verify-knot-keystore-configuration keystore) |
| 264 | (unless (knot-keystore-configuration? keystore) |
| 265 | (error-out "keystores must be a list of only knot-keystore-configuration.")) |
| 266 | (let ((id (knot-keystore-configuration-id keystore))) |
| 267 | (unless (and (string? id) (not (equal? id ""))) |
| 268 | (error-out "keystore id must be a non empty string."))) |
| 269 | (unless (memq '(pem pkcs11) |
| 270 | (knot-keystore-configuration-backend keystore)) |
| 271 | (error-out "backend must be one of: 'pem or 'pkcs11"))) |
| 272 | |
| 273 | (define (verify-knot-policy-configuration policy) |
| 274 | (unless (knot-policy-configuration? policy) |
| 275 | (error-out "policies must be a list of only knot-policy-configuration.")) |
| 276 | (let ((id (knot-policy-configuration-id policy))) |
| 277 | (unless (and (string? id) (not (equal? id ""))) |
| 278 | (error-out "policy id must be a non empty string.")))) |
| 279 | |
| 280 | (define (verify-knot-acl-configuration acl) |
| 281 | (unless (knot-acl-configuration? acl) |
| 282 | (error-out "acls must be a list of only knot-acl-configuration.")) |
| 283 | (let ((id (knot-acl-configuration-id acl)) |
| 284 | (address (knot-acl-configuration-address acl)) |
| 285 | (key (knot-acl-configuration-key acl)) |
| 286 | (action (knot-acl-configuration-action acl))) |
| 287 | (unless (and (string? id) (not (equal? id ""))) |
| 288 | (error-out "acl id must be a non empty string.")) |
| 289 | (unless (and (list? address) |
| 290 | (fold (lambda (x1 x2) (and (string? x1) (string? x2))) "" address)) |
| 291 | (error-out "acl address must be a list of strings."))) |
| 292 | (unless (boolean? (knot-acl-configuration-deny? acl)) |
| 293 | (error-out "deny? must be #t or #f."))) |
| 294 | |
| 295 | (define (verify-knot-zone-configuration zone) |
| 296 | (unless (knot-zone-configuration? zone) |
| 297 | (error-out "zones must be a list of only knot-zone-configuration.")) |
| 298 | (let ((domain (knot-zone-configuration-domain zone))) |
| 299 | (unless (and (string? domain) (not (equal? domain ""))) |
| 300 | (error-out "zone domain must be a non empty string.")))) |
| 301 | |
| 302 | (define (verify-knot-remote-configuration remote) |
| 303 | (unless (knot-remote-configuration? remote) |
| 304 | (error-out "remotes must be a list of only knot-remote-configuration.")) |
| 305 | (let ((id (knot-remote-configuration-id remote))) |
| 306 | (unless (and (string? id) (not (equal? id ""))) |
| 307 | (error-out "remote id must be a non empty string.")))) |
| 308 | |
| 309 | (define (verify-knot-configuration config) |
| 310 | (unless (package? (knot-configuration-knot config)) |
| 311 | (error-out "knot configuration field must be a package.")) |
| 312 | (unless (string? (knot-configuration-run-directory config)) |
| 313 | (error-out "run-directory must be a string.")) |
| 314 | (unless (list? (knot-configuration-includes config)) |
| 315 | (error-out "includes must be a list of strings or file-like objects.")) |
| 316 | (unless (list? (knot-configuration-keys config)) |
| 317 | (error-out "keys must be a list of knot-key-configuration.")) |
| 318 | (for-each (lambda (key) (verify-knot-key-configuration key)) |
| 319 | (knot-configuration-keys config)) |
| 320 | (unless (list? (knot-configuration-keystores config)) |
| 321 | (error-out "keystores must be a list of knot-keystore-configuration.")) |
| 322 | (for-each (lambda (keystore) (verify-knot-keystore-configuration keystore)) |
| 323 | (knot-configuration-keystores config)) |
| 324 | (unless (list? (knot-configuration-acls config)) |
| 325 | (error-out "acls must be a list of knot-acl-configuration.")) |
| 326 | (for-each (lambda (acl) (verify-knot-acl-configuration acl)) |
| 327 | (knot-configuration-acls config)) |
| 328 | (unless (list? (knot-configuration-zones config)) |
| 329 | (error-out "zones must be a list of knot-zone-configuration.")) |
| 330 | (for-each (lambda (zone) (verify-knot-zone-configuration zone)) |
| 331 | (knot-configuration-zones config)) |
| 332 | (unless (list? (knot-configuration-policies config)) |
| 333 | (error-out "policies must be a list of knot-policy-configuration.")) |
| 334 | (for-each (lambda (policy) (verify-knot-policy-configuration policy)) |
| 335 | (knot-configuration-policies config)) |
| 336 | (unless (list? (knot-configuration-remotes config)) |
| 337 | (error-out "remotes must be a list of knot-remote-configuration.")) |
| 338 | (for-each (lambda (remote) (verify-knot-remote-configuration remote)) |
| 339 | (knot-configuration-remotes config)) |
| 340 | #t) |
| 341 | |
| 342 | (define (format-string-list l) |
| 343 | "Formats a list of string in YAML" |
| 344 | (if (eq? l '()) |
| 345 | "" |
| 346 | (let ((l (reverse l))) |
| 347 | (string-append |
| 348 | "[" |
| 349 | (fold (lambda (x1 x2) |
| 350 | (string-append (if (symbol? x1) (symbol->string x1) x1) ", " |
| 351 | (if (symbol? x2) (symbol->string x2) x2))) |
| 352 | (if (symbol? (car l)) (symbol->string (car l)) (car l)) (cdr l)) |
| 353 | "]")))) |
| 354 | |
| 355 | (define (knot-acl-config acls) |
| 356 | (with-output-to-string |
| 357 | (lambda () |
| 358 | (for-each |
| 359 | (lambda (acl-config) |
| 360 | (let ((id (knot-acl-configuration-id acl-config)) |
| 361 | (address (knot-acl-configuration-address acl-config)) |
| 362 | (key (knot-acl-configuration-key acl-config)) |
| 363 | (action (knot-acl-configuration-action acl-config)) |
| 364 | (deny? (knot-acl-configuration-deny? acl-config))) |
| 365 | (format #t " - id: ~a\n" id) |
| 366 | (unless (eq? address '()) |
| 367 | (format #t " address: ~a\n" (format-string-list address))) |
| 368 | (unless (eq? key '()) |
| 369 | (format #t " key: ~a\n" (format-string-list key))) |
| 370 | (unless (eq? action '()) |
| 371 | (format #t " action: ~a\n" (format-string-list action))) |
| 372 | (format #t " deny: ~a\n" (if deny? "on" "off")))) |
| 373 | acls)))) |
| 374 | |
| 375 | (define (knot-key-config keys) |
| 376 | (with-output-to-string |
| 377 | (lambda () |
| 378 | (for-each |
| 379 | (lambda (key-config) |
| 380 | (let ((id (knot-key-configuration-id key-config)) |
| 381 | (algorithm (knot-key-configuration-algorithm key-config)) |
| 382 | (secret (knot-key-configuration-secret key-config))) |
| 383 | (format #t " - id: ~a\n" id) |
| 384 | (if algorithm |
| 385 | (format #t " algorithm: ~a\n" (symbol->string algorithm))) |
| 386 | (format #t " secret: ~a\n" secret))) |
| 387 | keys)))) |
| 388 | |
| 389 | (define (knot-keystore-config keystores) |
| 390 | (with-output-to-string |
| 391 | (lambda () |
| 392 | (for-each |
| 393 | (lambda (keystore-config) |
| 394 | (let ((id (knot-keystore-configuration-id keystore-config)) |
| 395 | (backend (knot-keystore-configuration-backend keystore-config)) |
| 396 | (config (knot-keystore-configuration-config keystore-config))) |
| 397 | (format #t " - id: ~a\n" id) |
| 398 | (format #t " backend: ~a\n" (symbol->string backend)) |
| 399 | (format #t " config: \"~a\"\n" config))) |
| 400 | keystores)))) |
| 401 | |
| 402 | (define (knot-policy-config policies) |
| 403 | (with-output-to-string |
| 404 | (lambda () |
| 405 | (for-each |
| 406 | (lambda (policy-config) |
| 407 | (let ((id (knot-policy-configuration-id policy-config)) |
| 408 | (keystore (knot-policy-configuration-keystore policy-config)) |
| 409 | (manual? (knot-policy-configuration-manual? policy-config)) |
| 410 | (single-type-signing? (knot-policy-configuration-single-type-signing? |
| 411 | policy-config)) |
| 412 | (algorithm (knot-policy-configuration-algorithm policy-config)) |
| 413 | (ksk-size (knot-policy-configuration-ksk-size policy-config)) |
| 414 | (zsk-size (knot-policy-configuration-zsk-size policy-config)) |
| 415 | (dnskey-ttl (knot-policy-configuration-dnskey-ttl policy-config)) |
| 416 | (zsk-lifetime (knot-policy-configuration-zsk-lifetime policy-config)) |
| 417 | (propagation-delay (knot-policy-configuration-propagation-delay |
| 418 | policy-config)) |
| 419 | (rrsig-lifetime (knot-policy-configuration-rrsig-lifetime |
| 420 | policy-config)) |
| 421 | (nsec3? (knot-policy-configuration-nsec3? policy-config)) |
| 422 | (nsec3-iterations (knot-policy-configuration-nsec3-iterations |
| 423 | policy-config)) |
| 424 | (nsec3-salt-length (knot-policy-configuration-nsec3-salt-length |
| 425 | policy-config)) |
| 426 | (nsec3-salt-lifetime (knot-policy-configuration-nsec3-salt-lifetime |
| 427 | policy-config))) |
| 428 | (format #t " - id: ~a\n" id) |
| 429 | (format #t " keystore: ~a\n" keystore) |
| 430 | (format #t " manual: ~a\n" (if manual? "on" "off")) |
| 431 | (format #t " single-type-signing: ~a\n" (if single-type-signing? |
| 432 | "on" "off")) |
| 433 | (format #t " algorithm: ~a\n" algorithm) |
| 434 | (format #t " ksk-size: ~a\n" (number->string ksk-size)) |
| 435 | (format #t " zsk-size: ~a\n" (number->string zsk-size)) |
| 436 | (unless (eq? dnskey-ttl 'default) |
| 437 | (format #t " dnskey-ttl: ~a\n" dnskey-ttl)) |
| 438 | (format #t " zsk-lifetime: ~a\n" zsk-lifetime) |
| 439 | (format #t " propagation-delay: ~a\n" propagation-delay) |
| 440 | (format #t " rrsig-lifetime: ~a\n" rrsig-lifetime) |
| 441 | (format #t " nsec3: ~a\n" (if nsec3? "on" "off")) |
| 442 | (format #t " nsec3-iterations: ~a\n" |
| 443 | (number->string nsec3-iterations)) |
| 444 | (format #t " nsec3-salt-length: ~a\n" |
| 445 | (number->string nsec3-salt-length)) |
| 446 | (format #t " nsec3-salt-lifetime: ~a\n" nsec3-salt-lifetime))) |
| 447 | policies)))) |
| 448 | |
| 449 | (define (knot-remote-config remotes) |
| 450 | (with-output-to-string |
| 451 | (lambda () |
| 452 | (for-each |
| 453 | (lambda (remote-config) |
| 454 | (let ((id (knot-remote-configuration-id remote-config)) |
| 455 | (address (knot-remote-configuration-address remote-config)) |
| 456 | (via (knot-remote-configuration-via remote-config)) |
| 457 | (key (knot-remote-configuration-key remote-config))) |
| 458 | (format #t " - id: ~a\n" id) |
| 459 | (unless (eq? address '()) |
| 460 | (format #t " address: ~a\n" (format-string-list address))) |
| 461 | (unless (eq? via '()) |
| 462 | (format #t " via: ~a\n" (format-string-list via))) |
| 463 | (if key |
| 464 | (format #t " key: ~a\n" key)))) |
| 465 | remotes)))) |
| 466 | |
| 467 | (define (serialize-zone-entries entries) |
| 468 | (with-output-to-string |
| 469 | (lambda () |
| 470 | (for-each |
| 471 | (lambda (entry) |
| 472 | (let ((name (zone-entry-name entry)) |
| 473 | (ttl (zone-entry-ttl entry)) |
| 474 | (class (zone-entry-class entry)) |
| 475 | (type (zone-entry-type entry)) |
| 476 | (data (zone-entry-data entry))) |
| 477 | (format #t "~a ~a ~a ~a ~a\n" name ttl class type data))) |
| 478 | entries)))) |
| 479 | |
| 480 | (define (serialize-zone-file zone domain) |
| 481 | (computed-file (string-append domain ".zone") |
| 482 | #~(begin |
| 483 | (call-with-output-file #$output |
| 484 | (lambda (port) |
| 485 | (format port "$ORIGIN ~a.\n" |
| 486 | #$(zone-file-origin zone)) |
| 487 | (format port "@ IN SOA ~a ~a (~a ~a ~a ~a ~a)\n" |
| 488 | #$(zone-file-ns zone) |
| 489 | #$(zone-file-mail zone) |
| 490 | #$(zone-file-serial zone) |
| 491 | #$(zone-file-refresh zone) |
| 492 | #$(zone-file-retry zone) |
| 493 | #$(zone-file-expiry zone) |
| 494 | #$(zone-file-nx zone)) |
| 495 | (format port "~a\n" |
| 496 | #$(serialize-zone-entries (zone-file-entries zone)))))))) |
| 497 | |
| 498 | (define (knot-zone-config zone) |
| 499 | (let ((content (knot-zone-configuration-zone zone))) |
| 500 | #~(with-output-to-string |
| 501 | (lambda () |
| 502 | (let ((domain #$(knot-zone-configuration-domain zone)) |
| 503 | (file #$(knot-zone-configuration-file zone)) |
| 504 | (master (list #$@(knot-zone-configuration-master zone))) |
| 505 | (ddns-master #$(knot-zone-configuration-ddns-master zone)) |
| 506 | (notify (list #$@(knot-zone-configuration-notify zone))) |
| 507 | (acl (list #$@(knot-zone-configuration-acl zone))) |
| 508 | (semantic-checks? #$(knot-zone-configuration-semantic-checks? zone)) |
| 509 | (disable-any? #$(knot-zone-configuration-disable-any? zone)) |
| 510 | (zonefile-sync #$(knot-zone-configuration-zonefile-sync zone)) |
| 511 | (zonefile-load '#$(knot-zone-configuration-zonefile-load zone)) |
| 512 | (journal-content #$(knot-zone-configuration-journal-content zone)) |
| 513 | (max-journal-usage #$(knot-zone-configuration-max-journal-usage zone)) |
| 514 | (max-journal-depth #$(knot-zone-configuration-max-journal-depth zone)) |
| 515 | (max-zone-size #$(knot-zone-configuration-max-zone-size zone)) |
| 516 | (dnssec-policy #$(knot-zone-configuration-dnssec-policy zone)) |
| 517 | (serial-policy '#$(knot-zone-configuration-serial-policy zone))) |
| 518 | (format #t " - domain: ~a\n" domain) |
| 519 | (if (eq? master '()) |
| 520 | ;; This server is a master |
| 521 | (if (equal? file "") |
| 522 | (format #t " file: ~a\n" |
| 523 | #$(serialize-zone-file content |
| 524 | (knot-zone-configuration-domain zone))) |
| 525 | (format #t " file: ~a\n" file)) |
| 526 | ;; This server is a slave (has masters) |
| 527 | (begin |
| 528 | (format #t " master: ~a\n" |
| 529 | #$(format-string-list |
| 530 | (knot-zone-configuration-master zone))) |
| 531 | (if ddns-master (format #t " ddns-master ~a\n" ddns-master)))) |
| 532 | (unless (eq? notify '()) |
| 533 | (format #t " notify: ~a\n" |
| 534 | #$(format-string-list |
| 535 | (knot-zone-configuration-notify zone)))) |
| 536 | (unless (eq? acl '()) |
| 537 | (format #t " acl: ~a\n" |
| 538 | #$(format-string-list |
| 539 | (knot-zone-configuration-acl zone)))) |
| 540 | (format #t " semantic-checks: ~a\n" (if semantic-checks? "on" "off")) |
| 541 | (format #t " disable-any: ~a\n" (if disable-any? "on" "off")) |
| 542 | (if zonefile-sync |
| 543 | (format #t " zonefile-sync: ~a\n" zonefile-sync)) |
| 544 | (if zonefile-load |
| 545 | (format #t " zonefile-load: ~a\n" |
| 546 | (symbol->string zonefile-load))) |
| 547 | (if journal-content |
| 548 | (format #t " journal-content: ~a\n" |
| 549 | (symbol->string journal-content))) |
| 550 | (if max-journal-usage |
| 551 | (format #t " max-journal-usage: ~a\n" max-journal-usage)) |
| 552 | (if max-journal-depth |
| 553 | (format #t " max-journal-depth: ~a\n" max-journal-depth)) |
| 554 | (if max-zone-size |
| 555 | (format #t " max-zone-size: ~a\n" max-zone-size)) |
| 556 | (if dnssec-policy |
| 557 | (begin |
| 558 | (format #t " dnssec-signing: on\n") |
| 559 | (format #t " dnssec-policy: ~a\n" dnssec-policy))) |
| 560 | (format #t " serial-policy: ~a\n" |
| 561 | (symbol->string serial-policy))))))) |
| 562 | |
| 563 | (define (knot-config-file config) |
| 564 | (verify-knot-configuration config) |
| 565 | (computed-file "knot.conf" |
| 566 | #~(begin |
| 567 | (call-with-output-file #$output |
| 568 | (lambda (port) |
| 569 | (for-each (lambda (inc) |
| 570 | (format port "include: ~a\n" inc)) |
| 571 | '#$(knot-configuration-includes config)) |
| 572 | (format port "server:\n") |
| 573 | (format port " rundir: ~a\n" #$(knot-configuration-run-directory config)) |
| 574 | (format port " user: knot\n") |
| 575 | (format port " listen: ~a@~a\n" |
| 576 | #$(knot-configuration-listen-v4 config) |
| 577 | #$(knot-configuration-listen-port config)) |
| 578 | (format port " listen: ~a@~a\n" |
| 579 | #$(knot-configuration-listen-v6 config) |
| 580 | #$(knot-configuration-listen-port config)) |
| 581 | (format port "\nkey:\n") |
| 582 | (format port #$(knot-key-config (knot-configuration-keys config))) |
| 583 | (format port "\nkeystore:\n") |
| 584 | (format port #$(knot-keystore-config (knot-configuration-keystores config))) |
| 585 | (format port "\nacl:\n") |
| 586 | (format port #$(knot-acl-config (knot-configuration-acls config))) |
| 587 | (format port "\nremote:\n") |
| 588 | (format port #$(knot-remote-config (knot-configuration-remotes config))) |
| 589 | (format port "\npolicy:\n") |
| 590 | (format port #$(knot-policy-config (knot-configuration-policies config))) |
| 591 | (unless #$(eq? (knot-configuration-zones config) '()) |
| 592 | (format port "\nzone:\n") |
| 593 | (format port "~a\n" |
| 594 | (string-concatenate |
| 595 | (list #$@(map knot-zone-config |
| 596 | (knot-configuration-zones config))))))))))) |
| 597 | |
| 598 | (define %knot-accounts |
| 599 | (list (user-group (name "knot") (system? #t)) |
| 600 | (user-account |
| 601 | (name "knot") |
| 602 | (group "knot") |
| 603 | (system? #t) |
| 604 | (comment "knot dns server user") |
| 605 | (home-directory "/var/empty") |
| 606 | (shell (file-append shadow "/sbin/nologin"))))) |
| 607 | |
| 608 | (define (knot-activation config) |
| 609 | #~(begin |
| 610 | (use-modules (guix build utils)) |
| 611 | (define (mkdir-p/perms directory owner perms) |
| 612 | (mkdir-p directory) |
| 613 | (chown directory (passwd:uid owner) (passwd:gid owner)) |
| 614 | (chmod directory perms)) |
| 615 | (mkdir-p/perms #$(knot-configuration-run-directory config) |
| 616 | (getpwnam "knot") #o755) |
| 617 | (mkdir-p/perms "/var/lib/knot" (getpwnam "knot") #o755) |
| 618 | (mkdir-p/perms "/var/lib/knot/keys" (getpwnam "knot") #o755) |
| 619 | (mkdir-p/perms "/var/lib/knot/keys/keys" (getpwnam "knot") #o755))) |
| 620 | |
| 621 | (define (knot-shepherd-service config) |
| 622 | (let* ((config-file (knot-config-file config)) |
| 623 | (knot (knot-configuration-knot config))) |
| 624 | (list (shepherd-service |
| 625 | (documentation "Run the Knot DNS daemon.") |
| 626 | (provision '(knot dns)) |
| 627 | (requirement '(networking)) |
| 628 | (start #~(make-forkexec-constructor |
| 629 | (list (string-append #$knot "/sbin/knotd") |
| 630 | "-c" #$config-file))) |
| 631 | (stop #~(make-kill-destructor)))))) |
| 632 | |
| 633 | (define knot-service-type |
| 634 | (service-type (name 'knot) |
| 635 | (extensions |
| 636 | (list (service-extension shepherd-root-service-type |
| 637 | knot-shepherd-service) |
| 638 | (service-extension activation-service-type |
| 639 | knot-activation) |
| 640 | (service-extension account-service-type |
| 641 | (const %knot-accounts)))))) |
| 642 | |
| 643 | \f |
| 644 | ;;; |
| 645 | ;;; Knot Resolver. |
| 646 | ;;; |
| 647 | |
| 648 | (define-record-type* <knot-resolver-configuration> |
| 649 | knot-resolver-configuration |
| 650 | make-knot-resolver-configuration |
| 651 | knot-resolver-configuration? |
| 652 | (package knot-resolver-configuration-package |
| 653 | (default knot-resolver)) |
| 654 | (kresd-config-file knot-resolver-kresd-config-file |
| 655 | (default %kresd.conf)) |
| 656 | (garbage-collection-interval knot-resolver-garbage-collection-interval |
| 657 | (default 1000))) |
| 658 | |
| 659 | (define %kresd.conf |
| 660 | (plain-file "kresd.conf" "-- -*- mode: lua -*- |
| 661 | net = { '127.0.0.1', '::1' } |
| 662 | user('knot-resolver', 'knot-resolver') |
| 663 | modules = { 'hints > iterate', 'stats', 'predict' } |
| 664 | cache.size = 100 * MB |
| 665 | ")) |
| 666 | |
| 667 | (define %knot-resolver-accounts |
| 668 | (list (user-group |
| 669 | (name "knot-resolver") |
| 670 | (system? #t)) |
| 671 | (user-account |
| 672 | (name "knot-resolver") |
| 673 | (group "knot-resolver") |
| 674 | (system? #t) |
| 675 | (home-directory "/var/cache/knot-resolver") |
| 676 | (shell (file-append shadow "/sbin/nologin"))))) |
| 677 | |
| 678 | (define (knot-resolver-activation config) |
| 679 | #~(begin |
| 680 | (use-modules (guix build utils)) |
| 681 | (let ((rundir "/var/cache/knot-resolver") |
| 682 | (owner (getpwnam "knot-resolver"))) |
| 683 | (mkdir-p rundir) |
| 684 | (chown rundir (passwd:uid owner) (passwd:gid owner))))) |
| 685 | |
| 686 | (define knot-resolver-shepherd-services |
| 687 | (match-lambda |
| 688 | (($ <knot-resolver-configuration> package |
| 689 | kresd-config-file |
| 690 | garbage-collection-interval) |
| 691 | (list |
| 692 | (shepherd-service |
| 693 | (provision '(kresd)) |
| 694 | (requirement '(networking)) |
| 695 | (documentation "Run the Knot Resolver daemon.") |
| 696 | (start #~(make-forkexec-constructor |
| 697 | '(#$(file-append package "/sbin/kresd") |
| 698 | "-c" #$kresd-config-file "-f" "1" |
| 699 | "/var/cache/knot-resolver"))) |
| 700 | (stop #~(make-kill-destructor))) |
| 701 | (shepherd-service |
| 702 | (provision '(kres-cache-gc)) |
| 703 | (requirement '(user-processes)) |
| 704 | (documentation "Run the Knot Resolver Garbage Collector daemon.") |
| 705 | (start #~(make-forkexec-constructor |
| 706 | '(#$(file-append package "/sbin/kres-cache-gc") |
| 707 | "-d" #$(number->string garbage-collection-interval) |
| 708 | "-c" "/var/cache/knot-resolver") |
| 709 | #:user "knot-resolver" |
| 710 | #:group "knot-resolver")) |
| 711 | (stop #~(make-kill-destructor))))))) |
| 712 | |
| 713 | (define knot-resolver-service-type |
| 714 | (service-type |
| 715 | (name 'knot-resolver) |
| 716 | (extensions |
| 717 | (list (service-extension shepherd-root-service-type |
| 718 | knot-resolver-shepherd-services) |
| 719 | (service-extension activation-service-type |
| 720 | knot-resolver-activation) |
| 721 | (service-extension account-service-type |
| 722 | (const %knot-resolver-accounts)))) |
| 723 | (default-value (knot-resolver-configuration)) |
| 724 | (description "Run the Knot DNS Resolver."))) |
| 725 | |
| 726 | \f |
| 727 | ;;; |
| 728 | ;;; Dnsmasq. |
| 729 | ;;; |
| 730 | |
| 731 | (define-record-type* <dnsmasq-configuration> |
| 732 | dnsmasq-configuration make-dnsmasq-configuration |
| 733 | dnsmasq-configuration? |
| 734 | (package dnsmasq-configuration-package |
| 735 | (default dnsmasq)) ;package |
| 736 | (no-hosts? dnsmasq-configuration-no-hosts? |
| 737 | (default #f)) ;boolean |
| 738 | (port dnsmasq-configuration-port |
| 739 | (default 53)) ;integer |
| 740 | (local-service? dnsmasq-configuration-local-service? |
| 741 | (default #t)) ;boolean |
| 742 | (listen-addresses dnsmasq-configuration-listen-address |
| 743 | (default '())) ;list of string |
| 744 | (resolv-file dnsmasq-configuration-resolv-file |
| 745 | (default "/etc/resolv.conf")) ;string |
| 746 | (no-resolv? dnsmasq-configuration-no-resolv? |
| 747 | (default #f)) ;boolean |
| 748 | (servers dnsmasq-configuration-servers |
| 749 | (default '())) ;list of string |
| 750 | (cache-size dnsmasq-configuration-cache-size |
| 751 | (default 150)) ;integer |
| 752 | (negative-cache? dnsmasq-configuration-negative-cache? |
| 753 | (default #t))) ;boolean |
| 754 | |
| 755 | (define dnsmasq-shepherd-service |
| 756 | (match-lambda |
| 757 | (($ <dnsmasq-configuration> package |
| 758 | no-hosts? |
| 759 | port local-service? listen-addresses |
| 760 | resolv-file no-resolv? servers |
| 761 | cache-size negative-cache?) |
| 762 | (shepherd-service |
| 763 | (provision '(dnsmasq)) |
| 764 | (requirement '(networking)) |
| 765 | (documentation "Run the dnsmasq DNS server.") |
| 766 | (start #~(make-forkexec-constructor |
| 767 | '(#$(file-append package "/sbin/dnsmasq") |
| 768 | "--keep-in-foreground" |
| 769 | "--pid-file=/run/dnsmasq.pid" |
| 770 | #$@(if no-hosts? |
| 771 | '("--no-hosts") |
| 772 | '()) |
| 773 | #$(format #f "--port=~a" port) |
| 774 | #$@(if local-service? |
| 775 | '("--local-service") |
| 776 | '()) |
| 777 | #$@(map (cut format #f "--listen-address=~a" <>) |
| 778 | listen-addresses) |
| 779 | #$(format #f "--resolv-file=~a" resolv-file) |
| 780 | #$@(if no-resolv? |
| 781 | '("--no-resolv") |
| 782 | '()) |
| 783 | #$@(map (cut format #f "--server=~a" <>) |
| 784 | servers) |
| 785 | #$(format #f "--cache-size=~a" cache-size) |
| 786 | #$@(if negative-cache? |
| 787 | '() |
| 788 | '("--no-negcache"))) |
| 789 | #:pid-file "/run/dnsmasq.pid")) |
| 790 | (stop #~(make-kill-destructor)))))) |
| 791 | |
| 792 | (define dnsmasq-service-type |
| 793 | (service-type |
| 794 | (name 'dnsmasq) |
| 795 | (extensions |
| 796 | (list (service-extension shepherd-root-service-type |
| 797 | (compose list dnsmasq-shepherd-service)))) |
| 798 | (default-value (dnsmasq-configuration)) |
| 799 | (description "Run the dnsmasq DNS server."))) |
| 800 | |
| 801 | \f |
| 802 | ;;; |
| 803 | ;;; ddclient |
| 804 | ;;; |
| 805 | |
| 806 | (define (uglify-field-name field-name) |
| 807 | (string-delete #\? (symbol->string field-name))) |
| 808 | |
| 809 | (define (serialize-field field-name val) |
| 810 | (when (not (member field-name '(group secret-file user))) |
| 811 | (format #t "~a=~a\n" (uglify-field-name field-name) val))) |
| 812 | |
| 813 | (define (serialize-boolean field-name val) |
| 814 | (serialize-field field-name (if val "yes" "no"))) |
| 815 | |
| 816 | (define (serialize-integer field-name val) |
| 817 | (serialize-field field-name (number->string val))) |
| 818 | |
| 819 | (define (serialize-string field-name val) |
| 820 | (if (and (string? val) (string=? val "")) |
| 821 | "" |
| 822 | (serialize-field field-name val))) |
| 823 | |
| 824 | (define (serialize-list field-name val) |
| 825 | (if (null? val) "" (serialize-field field-name (string-join val)))) |
| 826 | |
| 827 | (define (serialize-extra-options extra-options) |
| 828 | (string-join extra-options "\n" 'suffix)) |
| 829 | |
| 830 | (define-configuration ddclient-configuration |
| 831 | (ddclient |
| 832 | (package ddclient) |
| 833 | "The ddclient package.") |
| 834 | (daemon |
| 835 | (integer 300) |
| 836 | "The period after which ddclient will retry to check IP and domain name.") |
| 837 | (syslog |
| 838 | (boolean #t) |
| 839 | "Use syslog for the output.") |
| 840 | (mail |
| 841 | (string "root") |
| 842 | "Mail to user.") |
| 843 | (mail-failure |
| 844 | (string "root") |
| 845 | "Mail failed update to user.") |
| 846 | (pid |
| 847 | (string "/var/run/ddclient/ddclient.pid") |
| 848 | "The ddclient PID file.") |
| 849 | (ssl |
| 850 | (boolean #t) |
| 851 | "Enable SSL support.") |
| 852 | (user |
| 853 | (string "ddclient") |
| 854 | "Specifies the user name or ID that is used when running ddclient |
| 855 | program.") |
| 856 | (group |
| 857 | (string "ddclient") |
| 858 | "Group of the user who will run the ddclient program.") |
| 859 | (secret-file |
| 860 | (string "/etc/ddclient/secrets.conf") |
| 861 | "Secret file which will be appended to @file{ddclient.conf} file. This |
| 862 | file contains credentials for use by ddclient. You are expected to create it |
| 863 | manually.") |
| 864 | (extra-options |
| 865 | (list '()) |
| 866 | "Extra options will be appended to @file{ddclient.conf} file.")) |
| 867 | |
| 868 | (define (ddclient-account config) |
| 869 | "Return the user accounts and user groups for CONFIG." |
| 870 | (let ((ddclient-user (ddclient-configuration-user config)) |
| 871 | (ddclient-group (ddclient-configuration-group config))) |
| 872 | (list (user-group |
| 873 | (name ddclient-group) |
| 874 | (system? #t)) |
| 875 | (user-account |
| 876 | (name ddclient-user) |
| 877 | (system? #t) |
| 878 | (group ddclient-group) |
| 879 | (comment "ddclientd privilege separation user") |
| 880 | (home-directory (string-append "/var/run/" ddclient-user)))))) |
| 881 | |
| 882 | (define (ddclient-activation config) |
| 883 | "Return the activation GEXP for CONFIG." |
| 884 | (with-imported-modules '((guix build utils) |
| 885 | (ice-9 rdelim)) |
| 886 | #~(begin |
| 887 | (use-modules (guix build utils) |
| 888 | (ice-9 rdelim)) |
| 889 | (let ((ddclient-user |
| 890 | (passwd:uid (getpw #$(ddclient-configuration-user config)))) |
| 891 | (ddclient-group |
| 892 | (passwd:gid (getpw #$(ddclient-configuration-group config)))) |
| 893 | (ddclient-secret-file |
| 894 | #$(ddclient-configuration-secret-file config))) |
| 895 | ;; 'ddclient' complains about ddclient.conf file permissions, which |
| 896 | ;; rules out /gnu/store. Thus we copy the ddclient.conf to /etc. |
| 897 | (for-each (lambda (dir) |
| 898 | (mkdir-p dir) |
| 899 | (chmod dir #o700) |
| 900 | (chown dir ddclient-user ddclient-group)) |
| 901 | '("/var/cache/ddclient" "/var/run/ddclient" |
| 902 | "/etc/ddclient")) |
| 903 | (with-output-to-file "/etc/ddclient/ddclient.conf" |
| 904 | (lambda () |
| 905 | (display |
| 906 | (string-append |
| 907 | "# Generated by 'ddclient-service'.\n\n" |
| 908 | #$(with-output-to-string |
| 909 | (lambda () |
| 910 | (serialize-configuration config |
| 911 | ddclient-configuration-fields))) |
| 912 | (if (string-null? ddclient-secret-file) |
| 913 | "" |
| 914 | (format #f "\n\n# Appended from '~a'.\n\n~a" |
| 915 | ddclient-secret-file |
| 916 | (with-input-from-file ddclient-secret-file |
| 917 | read-string))))))) |
| 918 | (chmod "/etc/ddclient/ddclient.conf" #o600) |
| 919 | (chown "/etc/ddclient/ddclient.conf" |
| 920 | ddclient-user ddclient-group))))) |
| 921 | |
| 922 | (define (ddclient-shepherd-service config) |
| 923 | "Return a <shepherd-service> for ddclient with CONFIG." |
| 924 | (let ((ddclient (ddclient-configuration-ddclient config)) |
| 925 | (ddclient-pid (ddclient-configuration-pid config)) |
| 926 | (ddclient-user (ddclient-configuration-user config)) |
| 927 | (ddclient-group (ddclient-configuration-group config))) |
| 928 | (list (shepherd-service |
| 929 | (provision '(ddclient)) |
| 930 | (documentation "Run ddclient daemon.") |
| 931 | (start #~(make-forkexec-constructor |
| 932 | (list #$(file-append ddclient "/bin/ddclient") |
| 933 | "-foreground" |
| 934 | "-file" "/etc/ddclient/ddclient.conf") |
| 935 | #:pid-file #$ddclient-pid |
| 936 | #:environment-variables |
| 937 | (list "SSL_CERT_DIR=/run/current-system/profile\ |
| 938 | /etc/ssl/certs" |
| 939 | "SSL_CERT_FILE=/run/current-system/profile\ |
| 940 | /etc/ssl/certs/ca-certificates.crt") |
| 941 | #:user #$ddclient-user |
| 942 | #:group #$ddclient-group)) |
| 943 | (stop #~(make-kill-destructor)))))) |
| 944 | |
| 945 | (define ddclient-service-type |
| 946 | (service-type |
| 947 | (name 'ddclient) |
| 948 | (extensions |
| 949 | (list (service-extension account-service-type |
| 950 | ddclient-account) |
| 951 | (service-extension shepherd-root-service-type |
| 952 | ddclient-shepherd-service) |
| 953 | (service-extension activation-service-type |
| 954 | ddclient-activation))) |
| 955 | (default-value (ddclient-configuration)) |
| 956 | (description "Configure address updating utility for dynamic DNS services, |
| 957 | ddclient."))) |
| 958 | |
| 959 | (define (generate-ddclient-documentation) |
| 960 | (generate-documentation |
| 961 | `((ddclient-configuration ,ddclient-configuration-fields)) |
| 962 | 'ddclient-configuration)) |