Subject: [bug#50882] [PATCH] services: Add darkhttpd service.

From: jgart <[email-masked]>
Date: Tue, 28 Sep 2021 20:46:33 -0400

* gnu/services/web.scm (<darkhttpd-configuration>): New record type.
(darkhttpd-accounts, darkhttpd-shepherd-service): New procedures.
(darkhttpd-service-type): New variable.
* doc/guix.texi (Web Services): Adds documentation for darkhttpd.
---
 doc/guix.texi        | 124 +++++++++++++++++++++++++++++
 gnu/services/web.scm | 184 ++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 306 insertions(+), 2 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 3124ed2ef8..6f22edba2e 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -26259,6 +26259,130 @@ The file which should store the logging output of Agate.
 @end table
 @end deftp
 
+@subsubheading darkhttpd
+
+@cindex darkhttpd
+@uref{https://unix4lyfe.org/darkhttpd/, darkhttpd} is a web server with a 
+focus on security and having a small memory footprint.
+
+Some security features are the following:
+
+@itemize
+@item Logging accesses, including Referer and User-Agent.
+@item Can chroot.
+@item Can drop privileges.
+@item Impervious to /../ sniffing.
+@item Times out idle connections.
+@item Drops overly long requests.
+@end itemize 
+
+@deffn {Scheme Variable} darkhttpd-service-type
+This is the type of the darkhttpd service, whose value should be a
+@code{darkhttpd-service-type} object, as in this example:
+
+@lisp
+(service darkhttpd-service-type
+	 (darkhttpd-configuration
+	   (content "/var/www/localhost/blog")
+	   (port 4567)
+	   (no-server-id? #t)
+	   (enable-ipv6? #t)
+	   (chroot? #f)))
+@end lisp
+
+The example above shows @code{content} directory modified as 
+well as @code{port}, @code{no-server-id?}, and @code{enable-ipv6?} 
+enabled and @code{chroot?} disabled.
+
+A minimal config might look like the following:
+
+@lisp
+(service darkhttpd-service-type)
+@end lisp
+
+@deftp {Data Type} darkhttpd-configuration
+Data type representing the configuration of darkhttpd.
+
+@table @asis
+@item @code{package} (default: @code{darkhttpd})
+The package object of the darkhttpd server.
+
+@item @code{content} (default: @file{"/srv/gemini"})
+The directory from which Agate will serve files.
+
+@item @code{port} (default: @code{"80"})
+Specifies which port to listen on for connections.
+Assign 0 to let the system choose any free port for you.
+
+@item @code{address} (default: @code{"all"})
+If multiple interfaces are present, specifies
+which one to bind the listening port to.
+
+@item @code{maximum-connections} (default: @code{#f})
+Specifies how many concurrent connections to accept.
+
+@item @code{log-file} (default: @file{"/var/log/darkhttpd.log"})
+The file which should store the logging output of @code{darkhttpd}.
+
+@item @code{chroot?} (default: @code{#t})
+Locks the web server into the content directory for added security.
+
+@item @code{daemonize?} (default: @code{#t})
+Detach from the controlling terminal and run in the background.
+
+@item @code{index-file} (default: @code{"index.html"})
+Default file to serve when a directory is requested.
+
+@item @code{do-not-serve-listing?} (default: @code{#f})
+Do not serve listing if directory is requested.
+
+@item @code{mimetypes} (default: @code{#f})
+This option is optional. @code{mimetypes} parses the 
+specified file for extension-MIME associations.
+
+@item @code{default-mimetype} (default: @code{"application/octet-stream"})
+Files with unknown extensions are served as this mimetype.
+
+@item @code{drop-user-privileges?} (default: @code{#t})
+Drops privileges to given uid after initialization.
+
+@item @code{drop-group-privileges?} (default: @code{#t})
+Drops privileges to given gid after initialization.
+
+@item @code{write-pid-file} (default: @code{#f})
+Write PID to the specified file.  Note that if you are
+using @{chroot?}, then the pidfile will be set relative
+to the directory set with the @{content} option.
+
+@item @code{disable-keep-alive?} (default: @code{#f})
+Disables HTTP Keep-Alive functionality.
+
+@item @code{forward} (default: @code{#f})
+By default, @{darkhttpd} does not forward requests.
+This option allows requests to the host to be redirected 
+to the corresponding url given as an argument.
+@{forward} may be specified multiple times, in which case
+the host is matched in order of appearance.
+
+@item @code{forward-all} (default: @code{#f})
+@{forward-all} is similar to @{forward} but all 
+requests are redirected to the url given as an argument.
+
+@item @code{no-server-id?} (default: @code{#f})
+Do not identify the server type in headers or 
+directory listings.
+
+@item @code{enable-ipv6?} (default: @code{#f})
+Listen on IPv6 address.
+
+@item @code{user} (default: @code{"darkhttpd"})
+Owner of the @code{darkhttpd} process.
+
+@item @code{group} (default: @code{"darkhttpd"})
+Owner's group of the @code{darkhttpd} process.
+
+@end table
+@end deftp
 @node Certificate Services
 @subsection Certificate Services
 
diff --git a/gnu/services/web.scm b/gnu/services/web.scm
index bb42eacf83..68afba3cad 100644
--- a/gnu/services/web.scm
+++ b/gnu/services/web.scm
@@ -281,8 +281,35 @@
             agate-configuration-group
             agate-configuration-log-file
 
-            agate-service-type))
-
+            agate-service-type
+            
+            darkhttpd-configuration
+            darkhttpd-configuration?
+            darkhttpd-configuration-package
+            darkhttpd-configuration-content
+            darkhttpd-configuration-port
+            darkhttpd-configuration-address
+            darkhttpd-configuration-maximum-connections
+            darkhttpd-configuration-log-file
+            darkhttpd-configuration-chroot
+            darkhttpd-configuration-daemonize
+            darkhttpd-configuration-index-file
+            darkhttpd-configuration-do-not-serve-listing
+            darkhttpd-configuration-mimetypes
+            darkhttpd-configuration-default-mimetype
+            darkhttpd-configuration-drop-user-privileges
+            darkhttpd-configuration-drop-group-privileges
+            darkhttpd-configuration-write-pid-file
+            darkhttpd-configuration-disable-keep-alive
+            darkhttpd-configuration-forward
+            darkhttpd-configuration-forward-all
+            darkhttpd-configuration-no-server-id
+            darkhttpd-configuration-enable-ipv6
+            darkhttpd-configuration-user
+            darkhttpd-configuration-group
+
+            darkhttpd-service-type))
+            
 ;;; Commentary:
 ;;;
 ;;; Web services.
@@ -1993,3 +2020,156 @@ root=/srv/gemini
           (service-extension shepherd-root-service-type
                              agate-shepherd-service)))
    (default-value (agate-configuration))))
+
+(define-record-type* <darkhttpd-configuration>
+  darkhttpd-configuration make-darkhttpd-configuration
+  darkhttpd-configuration?
+  (package                 darkhttpd-configuration-package
+                           (default darkhttpd))
+  (content                 darkhttpd-configuration-content
+                           (default "/var/www/localhost/htdocs"))
+  (port                    darkhttpd-configuration-port
+                           (default "80"))
+  (address                 darkhttpd-configuration-address
+                           (default "all"))
+  (maximum-connections     darkhttpd-configuration-maximum-connections
+                           (default #f))
+  (log-file                darkhttpd-configuration-log-file
+                           (default "access.log"))
+  (chroot?                 darkhttpd-configuration-chroot
+                           (default #t))
+  (daemonize?              darkhttpd-configuration-daemonize
+                           (default #t))
+  (index-file              darkhttpd-configuration-index-file
+                           (default "index.html"))
+  (do-not-serve-listing?   darkhttpd-configuration-do-not-serve-listing
+                           (default #f))
+  (mimetypes               darkhttpd-configuration-mimetypes
+                           (default #f))
+  (default-mimetype        darkhttpd-configuration-default-mimetype
+                           (default "application/octet-stream"))
+  (drop-user-privileges?   darkhttpd-configuration-drop-user-privileges
+                           (default #t))
+  (drop-group-privileges?  darkhttpd-configuration-drop-group-privileges
+                           (default #t))
+  (write-pid-file          darkhttpd-configuration-write-pid-file
+                           (default #f))
+  (disable-keep-alive?     darkhttpd-configuration-disable-keep-alive
+                           (default #f))
+  (forward                 darkhttpd-configuration-forward
+                           (default #f))
+  (forward-all             darkhttpd-configuration-forward-all
+                           (default #f))
+  (no-server-id?           darkhttpd-configuration-no-server-id
+                           (default #f))
+  (enable-ipv6?            darkhttpd-configuration-enable-ipv6
+                           (default #f))
+  (user                    darkhttpd-configuration-user
+                           (default "darkhttpd"))
+  (group                   darkhttpd-configuration-group
+                           (default "www-data")))
+
+(define darkhttpd-shepherd-service
+  (match-lambda
+    (($ <darkhttpd-configuration> package content port address 
+                                  maximum-connections log-file chroot? 
+                                  daemonize? index-file do-not-serve-listing?
+                                  mimetypes default-mimetype 
+                                  drop-user-priviledges drop-group-priviledges 
+                                  write-pid-file disable-keep-alive? 
+                                  forward forward-all 
+                                  no-server-id? enable-ipv6? 
+                                  user group)
+     (list (shepherd-service
+            (provision '(darkhttpd))
+            (requirement '(networking))
+            (documentation "Run the darkhttpd web server.")
+            (start (let ((darkhttpd (file-append package "/bin/darkhttpd")))
+                     #~(make-forkexec-constructor
+                        (list #$darkhttpd 
+                              #$content
+                              #$@(if port
+                                     (list "--port" (number->string port))
+                                     '())
+                              #$@(if address
+                                     (list "--addr" address)
+                                     '())
+                              #$@(if maximum-connections
+                                     (list "--maxconn" maximum-connections)
+                                     '())
+                              #$@(if log-file
+                                     (list "--log" 
+                                           (string-append "/var/log/darkhttpd/" 
+                                                          log-file)
+                                     '()))
+                              #$@(if chroot? '("--chroot") '())
+                              #$@(if daemonize? '("--daemon") '())
+                              #$@(if index-file
+                                     (list "--index" index-file)
+                                     '())
+                              #$@(if do-not-serve-listing?
+                                     (list "--no-listing" 
+                                           do-not-serve-listing?)
+                                     '())
+                              #$@(if mimetypes '("--mimetypes" mimetypes) '())
+                              #$@(if default-mimetype 
+                                     (list "--default-mimetype" 
+                                           default-mimetype) 
+                                     '())
+                              #$@(if drop-user-privileges? 
+                                     '("--uid" user) '())
+                              #$@(if drop-group-privileges? 
+                                     '("--gid" group) '())
+                              ;; if using --chroot, then the pidfile must be 
+                              ;; relative to, and inside the wwwroot.
+                              #$@(if write-pid-file 
+                                     "--pidfile" 
+                                     (if (and chroot? write-pid-file)
+                                            '(string-append content 
+                                                            "/"
+                                                            write-pid-file)
+                                         write-pid-file)
+                                     '())                                     
+                              #$@(if disable-keep-alive? 
+                                     (list "--no-keepalive" 
+                                           disable-keep-alive?) 
+                                     '())
+                              #$@(if forward '("--forward" forward) '())
+                              #$@(if forward-all
+                                     (list "--forward-all" forward-all) 
+                                     '())
+                              #$@(if no-server-id? '("--no-server-id") '())
+                              #$@(if enable-ipv6? '("--ipv6") '())
+                        #:user #$user #:group #$group))))
+            (stop #~(make-kill-destructor)))))))
+
+(define darkhttpd-accounts
+  (match-lambda
+    (($ <darkhttpd-configuration> _ _ _ _ _ _ _ _ 
+                                  _ _ _ _ _ _ _ _ 
+                                  _ _ user group)
+     `(,@(if (equal? group "darkhttpd")
+             '()
+             (list (user-group (name "darkhttpd") (system? #t))))
+       ,(user-group
+         (name group)
+         (system? #t))
+       ,(user-account
+         (name user)
+         (group group)
+         (supplementary-groups '("darkhttpd"))
+         (system? #t)
+         (comment "darkhttpd server user")
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))))
+
+(define darkhttpd-service-type
+  (service-type
+   (name 'guix)
+   (extensions
+    (list (service-extension account-service-type
+                             darkhttpd-accounts)
+          (service-extension shepherd-root-service-type
+                             darkhttpd-shepherd-service)))
+   (default-value (darkhttpd-configuration))))
+
-- 
2.33.0